Page MenuHomePhabricator

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/externals/diff_match_patch/diff_match_patch.php b/externals/diff_match_patch/diff_match_patch.php
deleted file mode 100644
index 35bdb82068..0000000000
--- a/externals/diff_match_patch/diff_match_patch.php
+++ /dev/null
@@ -1,2117 +0,0 @@
-<?php
-/**
- * Diff Match and Patch
- *
- * Copyright 2006 Google Inc.
- * http://code.google.com/p/google-diff-match-patch/
- *
- * php port by Tobias Buschor shwups.ch
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * @fileoverview Computes the difference between two texts to create a patch.
- * Applies the patch onto another text, allowing for errors.
- * @author fraser@google.com (Neil Fraser)
- */
-
-/**
- * Class containing the diff, match and patch methods.
- * @constructor
- */
-class diff_match_patch {
-
- // Defaults.
- // Redefine these in your program to override the defaults.
-
- // Number of seconds to map a diff before giving up (0 for infinity).
- public $Diff_Timeout = 1.0;
- // Cost of an empty edit operation in terms of edit characters.
- public $Diff_EditCost = 4;
- // The size beyond which the double-ended diff activates.
- // Double-ending is twice as fast, but less accurate.
- public $Diff_DualThreshold = 32;
- // At what point is no match declared (0.0 = perfection, 1.0 = very loose).
- public $Match_Threshold = 0.5;
- // How far to search for a match (0 = exact location, 1000+ = broad match).
- // A match this many characters away from the expected location will add
- // 1.0 to the score (0.0 is a perfect match).
- public $Match_Distance = 1000;
- // When deleting a large block of text (over ~64 characters), how close does
- // the contents have to match the expected contents. (0.0 = perfection,
- // 1.0 = very loose). Note that Match_Threshold controls how closely the
- // end points of a delete need to match.
- public $Patch_DeleteThreshold = 0.5;
- // Chunk size for context length.
- public $Patch_Margin = 4;
-
- /**
- * Compute the number of bits in an int.
- * The normal answer for JavaScript is 32.
- * @return {number} Max bits
-
- function getMaxBits() {
- var maxbits = 0;
- var oldi = 1;
- var newi = 2;
- while (oldi != newi) {
- maxbits++;
- oldi = newi;
- newi = newi << 1;
- }
- return maxbits;
- }
- // How many bits in a number?
- this.Match_MaxBits = getMaxBits();
- */
- // DIFF FUNCTIONS
-
- /**
- * Find the differences between two texts. Simplifies the problem by stripping
- * any common prefix or suffix off the texts before diffing.
- * @param {string} text1 Old string to be diffed.
- * @param {string} text2 New string to be diffed.
- * @param {boolean} opt_checklines Optional speedup flag. If present and false,
- * then don't run a line-level diff first to identify the changed areas.
- * Defaults to true, which does a faster, slightly less optimal diff
- * @return {Array.<Array.<number|string>>} Array of diff tuples.
- */
- function diff_main($text1, $text2, $checklines = true) {
- // Check for equality (speedup)
- if ($text1 === $text2) {
- return array ( array ( DIFF_EQUAL, $text1) );
- }
-
- // Trim off common prefix (speedup)
- $commonlength = $this->diff_commonPrefix($text1, $text2);
- $commonprefix = mb_substr($text1, 0, $commonlength);
- $text1 = mb_substr($text1, $commonlength);
- $text2 = mb_substr($text2, $commonlength);
-
- // Trim off common suffix (speedup)
- $commonlength = $this->diff_commonSuffix($text1, $text2);
- $commonsuffix = mb_substr($text1, mb_strlen($text1) - $commonlength);
- $text1 = mb_substr($text1, 0, mb_strlen($text1) - $commonlength);
- $text2 = mb_substr($text2, 0, mb_strlen($text2) - $commonlength);
-
- // Compute the diff on the middle block
- $diffs = $this->diff_compute($text1, $text2, $checklines);
-
- // Restore the prefix and suffix
- if ($commonprefix !== '') {
- array_unshift($diffs, array ( DIFF_EQUAL, $commonprefix ));
- }
- if ($commonsuffix !== '') {
- array_push($diffs, array ( DIFF_EQUAL, $commonsuffix ));
- }
- $this->diff_cleanupMerge($diffs);
- return $diffs;
- }
-
- /**
- * Find the differences between two texts. Assumes that the texts do not
- * have any common prefix or suffix.
- * @param {string} text1 Old string to be diffed.
- * @param {string} text2 New string to be diffed.
- * @param {boolean} checklines Speedup flag. If false, then don't run a
- * line-level diff first to identify the changed areas.
- * If true, then run a faster, slightly less optimal diff
- * @return {Array.<Array.<number|string>>} Array of diff tuples.
- * @private
- */
- function diff_compute($text1, $text2, $checklines) {
-
- if ($text1 === '') {
- // Just add some text (speedup)
- return array ( array ( DIFF_INSERT, $text2 ) );
- }
-
- if ($text2 === '') {
- // Just delete some text (speedup)
- return array ( array ( DIFF_DELETE, $text1 ) );
- }
-
- $longtext = mb_strlen($text1) > mb_strlen($text2) ? $text1 : $text2;
- $shorttext = mb_strlen($text1) > mb_strlen($text2) ? $text2 : $text1;
- $i = mb_strpos($longtext, $shorttext);
- if ($i !== false) {
- // Shorter text is inside the longer text (speedup)
- $diffs = array (
- array ( DIFF_INSERT, mb_substr($longtext, 0, $i) ),
- array ( DIFF_EQUAL, $shorttext ),
- array ( DIFF_INSERT, mb_substr($longtext, $i +mb_strlen($shorttext)) )
- );
-
- // Swap insertions for deletions if diff is reversed.
- if (mb_strlen($text1) > mb_strlen($text2)) {
- $diffs[0][0] = $diffs[2][0] = DIFF_DELETE;
- }
- return $diffs;
- }
- $longtext = $shorttext = null; // Garbage collect
-
- // Check to see if the problem can be split in two.
- $hm = $this->diff_halfMatch($text1, $text2);
- if ($hm) {
- // A half-match was found, sort out the return data.
- $text1_a = $hm[0];
- $text1_b = $hm[1];
- $text2_a = $hm[2];
- $text2_b = $hm[3];
- $mid_common = $hm[4];
- // Send both pairs off for separate processing.
- $diffs_a = $this->diff_main($text1_a, $text2_a, $checklines);
- $diffs_b = $this->diff_main($text1_b, $text2_b, $checklines);
- // Merge the results.
- return array_merge($diffs_a, array (
- array (
- DIFF_EQUAL,
- $mid_common
- )
- ), $diffs_b);
- }
-
- // Perform a real diff.
- if ($checklines && (mb_strlen($text1) < 100 || mb_strlen($text2) < 100)) {
- // Too trivial for the overhead.
- $checklines = false;
- }
- $linearray = null;
- if ($checklines) {
- // Scan the text on a line-by-line basis first.
- $a = $this->diff_linesToChars($text1, $text2);
- $text1 = $a[0];
- $text2 = $a[1];
- $linearray = $a[2];
- }
- $diffs = $this->diff_map($text1, $text2);
- if (!$diffs) {
- // No acceptable result.
- $diffs = array (
- array (
- DIFF_DELETE,
- $text1
- ),
- array (
- DIFF_INSERT,
- $text2
- )
- );
- }
- if ($checklines) {
- // Convert the diff back to original text.
- $this->diff_charsToLines($diffs, $linearray);
- // Eliminate freak matches (e.g. blank lines)
- $this->diff_cleanupSemantic($diffs);
-
- // Rediff any replacement blocks, this time character-by-character.
- // Add a dummy entry at the end.
- array_push($diffs, array (
- DIFF_EQUAL,
- ''
- ));
- $pointer = 0;
- $count_delete = 0;
- $count_insert = 0;
- $text_delete = '';
- $text_insert = '';
- while ($pointer < count($diffs)) {
- switch ($diffs[$pointer][0]) {
- case DIFF_INSERT :
- $count_insert++;
- $text_insert .= $diffs[$pointer][1];
- break;
- case DIFF_DELETE :
- $count_delete++;
- $text_delete .= $diffs[$pointer][1];
- break;
- case DIFF_EQUAL :
- // Upon reaching an equality, check for prior redundancies.
- if ($count_delete >= 1 && $count_insert >= 1) {
- // Delete the offending records and add the merged ones.
- $a = $this->diff_main($text_delete, $text_insert, false);
- array_splice($diffs, $pointer - $count_delete - $count_insert, $count_delete + $count_insert);
-
- $pointer = $pointer - $count_delete - $count_insert;
- for ($j = count($a) - 1; $j >= 0; $j--) {
- array_splice($diffs, $pointer, 0, array($a[$j]));
- }
- $pointer = $pointer +count($a);
- }
- $count_insert = 0;
- $count_delete = 0;
- $text_delete = '';
- $text_insert = '';
- break;
- }
- $pointer++;
- }
- array_pop($diffs); // Remove the dummy entry at the end.
- }
- return $diffs;
- }
-
- /**
- * Split two texts into an array of strings. Reduce the texts to a string of
- * hashes where each Unicode character represents one line.
- * @param {string} text1 First string.
- * @param {string} text2 Second string.
- * @return {Array.<string|Array.<string>>} Three element Array, containing the
- * encoded text1, the encoded text2 and the array of unique strings. The
- * zeroth element of the array of unique strings is intentionally blank.
- * @private
- */
- function diff_linesToChars($text1, $text2) {
- $lineArray = array(); // e.g. lineArray[4] == 'Hello\n'
- $lineHash = array(); // e.g. lineHash['Hello\n'] == 4
-
- // '\x00' is a valid character, but various debuggers don't like it.
- // So we'll insert a junk entry to avoid generating a null character.
- $lineArray[0] = '';
-
- $chars1 = $this->diff_linesToCharsMunge($text1, $lineArray, $lineHash);
- $chars2 = $this->diff_linesToCharsMunge($text2, $lineArray, $lineHash);
- return array (
- $chars1,
- $chars2,
- $lineArray
- );
- }
-
- /**
- * Split a text into an array of strings. Reduce the texts to a string of
- * hashes where each Unicode character represents one line.
- * Modifies linearray and linehash through being a closure.
- * @param {string} text String to encode
- * @return {string} Encoded string
- * @private
- */
- function diff_linesToCharsMunge($text, &$lineArray, &$lineHash) {
- $chars = '';
- // Walk the text, pulling out a mb_substring for each line.
- // text.split('\n') would would temporarily double our memory footprint.
- // Modifying text would create many large strings to garbage collect.
- $lineStart = 0;
- $lineEnd = -1;
- // Keeping our own length variable is faster than looking it up.
- $lineArrayLength = count($lineArray);
- while ($lineEnd < mb_strlen($text) - 1) {
- $lineEnd = mb_strpos($text, "\n", $lineStart);
- if ($lineEnd === false) {
- $lineEnd = mb_strlen($text) - 1;
- }
- $line = mb_substr($text, $lineStart, $lineEnd +1 -$lineStart);
- $lineStart = $lineEnd +1;
-
- if ( isset($lineHash[$line]) ) {
- $chars .= mb_chr($lineHash[$line]);
- } else {
- $chars .= mb_chr($lineArrayLength);
- $lineHash[$line] = $lineArrayLength;
- $lineArray[$lineArrayLength++] = $line;
- }
- }
- return $chars;
- }
- /**
- * Rehydrate the text in a diff from a string of line hashes to real lines of
- * text.
- * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
- * @param {Array.<string>} lineArray Array of unique strings.
- * @private
- */
- function diff_charsToLines(&$diffs, $lineArray) {
- for ($x = 0; $x < count($diffs); $x++) {
- $chars = $diffs[$x][1];
- $text = array ();
- for ($y = 0; $y < mb_strlen($chars); $y++) {
- $text[$y] = $lineArray[charCodeAt($chars, $y)];
- }
- $diffs[$x][1] = implode('',$text);
- }
- }
-
- /**
- * Explore the intersection points between the two texts.
- * @param {string} text1 Old string to be diffed.
- * @param {string} text2 New string to be diffed.
- * @return {Array.<Array.<number|string>>?} Array of diff tuples or null if no
- * diff available.
- * @private
- */
- function diff_map($text1, $text2) {
- // Don't run for too long.
- $ms_end = microtime(true) + $this->Diff_Timeout;
-
- // Cache the text lengths to prevent multiple calls.
- $text1_length = mb_strlen($text1);
- $text2_length = mb_strlen($text2);
- $max_d = $text1_length + $text2_length -1;
- $doubleEnd = $this->Diff_DualThreshold * 2 < $max_d;
- $v_map1 = array();
- $v_map2 = array();
- $v1 = array();
- $v2 = array();
- $v1[1] = 0;
- $v2[1] = 0;
- $x = null;
- $y = null;
- $footstep = null; // Used to track overlapping paths.
- $footsteps = array();
- $done = false;
- // Safari 1.x doesn't have hasOwnProperty
- //? $hasOwnProperty = !!(footsteps.hasOwnProperty);
- // If the total number of characters is odd, then the front path will collide
- // with the reverse path.
- $front = ($text1_length + $text2_length) % 2;
- for ($d = 0; $d < $max_d; $d++) {
- // Bail out if timeout reached.
- if ($this->Diff_Timeout > 0 && microtime(true) > $ms_end) {
- return null; // zzz
- }
-
- // Walk the front path one step.
- $v_map1[$d] = array ();
- for ($k = -$d; $k <= $d; $k += 2) {
- if ($k == -$d || $k != $d && $v1[$k -1] < $v1[$k +1]) {
- $x = $v1[$k +1];
- } else {
- $x = $v1[$k -1] + 1;
- }
- $y = $x - $k;
- if ($doubleEnd) {
- $footstep = $x . ',' . $y;
- if ($front && isset ($footsteps[$footstep])) {
- $done = true;
- }
- if (!$front) {
- $footsteps[$footstep] = $d;
- }
- }
- while (!$done && ($x < $text1_length) && ($y < $text2_length) && (mb_substr($text1, $x, 1) == mb_substr($text2, $y, 1)) ) {
- $x++;
- $y++;
- if ($doubleEnd) {
- $footstep = $x . ',' . $y;
- if ($front && isset ($footsteps[$footstep])) {
- $done = true;
- }
- if (!$front) {
- $footsteps[$footstep] = $d;
- }
- }
- }
- $v1[$k] = $x;
- $v_map1[$d][$x . ',' . $y] = true;
- if ($x == $text1_length && $y == $text2_length) {
- // Reached the end in single-path mode.
- return $this->diff_path1($v_map1, $text1, $text2);
- }
- elseif ($done) {
- // Front path ran over reverse path.
-
- $v_map2 = array_slice($v_map2, 0, $footsteps[$footstep] + 1);
- $a = $this->diff_path1($v_map1, mb_substr($text1, 0, $x), mb_substr($text2, 0, $y));
-
- return array_merge($a, $this->diff_path2($v_map2, mb_substr($text1, $x), mb_substr($text2, $y)));
- }
- }
-
- if ($doubleEnd) {
- // Walk the reverse path one step.
- $v_map2[$d] = array();
- for ($k = -$d; $k <= $d; $k += 2) {
- if ($k == -$d || $k != $d && $v2[$k -1] < $v2[$k +1]) {
- $x = $v2[$k +1];
- } else {
- $x = $v2[$k -1] + 1;
- }
- $y = $x - $k;
- $footstep = ($text1_length - $x) . ',' . ($text2_length - $y);
- if (!$front && isset ($footsteps[$footstep])) {
- $done = true;
- }
- if ($front) {
- $footsteps[$footstep] = $d;
- }
- while (!$done && $x < $text1_length && $y < $text2_length && mb_substr($text1, $text1_length - $x -1, 1) == mb_substr($text2, $text2_length - $y -1, 1) ) {
- $x++;
- $y++;
- $footstep = ($text1_length - $x) . ',' . ($text2_length - $y);
- if (!$front && isset ($footsteps[$footstep])) {
- $done = true;
- }
- if ($front) {
- $footsteps[$footstep] = $d;
- }
- }
- $v2[$k] = $x;
- $v_map2[$d][$x . ',' . $y] = true;
- if ($done) {
- // Reverse path ran over front path.
- $v_map1 = array_slice($v_map1, 0, $footsteps[$footstep] + 1);
- $a = $this->diff_path1($v_map1, mb_substr($text1, 0, $text1_length - $x), mb_substr($text2, 0, $text2_length - $y));
- return array_merge($a, $this->diff_path2($v_map2, mb_substr($text1, $text1_length - $x), mb_substr($text2, $text2_length - $y)));
- }
- }
- }
- }
- // Number of diffs equals number of characters, no commonality at all.
- return null;
- }
-
- /**
- * Work from the middle back to the start to determine the path.
- * @param {Array.<Object>} v_map Array of paths.ers
- * @param {string} text1 Old string fragment to be diffed.
- * @param {string} text2 New string fragment to be diffed.
- * @return {Array.<Array.<number|string>>} Array of diff tuples.
- * @private
- */
- function diff_path1($v_map, $text1, $text2) {
- $path = array ();
- $x = mb_strlen($text1);
- $y = mb_strlen($text2);
- /** @type {number?} */
- $last_op = null;
- for ($d = count($v_map) - 2; $d >= 0; $d--) {
- while (1) {
- if (isset ($v_map[$d][($x -1) . ',' . $y])) {
- $x--;
- if ($last_op === DIFF_DELETE) {
- $path[0][1] = mb_substr($text1, $x, 1) . $path[0][1];
- } else {
- array_unshift($path, array (
- DIFF_DELETE,
- mb_substr($text1, $x, 1)
- ));
- }
- $last_op = DIFF_DELETE;
- break;
- } elseif (isset ($v_map[$d][$x . ',' . ($y -1)])) {
- $y--;
- if ($last_op === DIFF_INSERT) {
- $path[0][1] = mb_substr($text2, $y, 1) . $path[0][1];
- } else {
- array_unshift($path, array (
- DIFF_INSERT,
- mb_substr($text2, $y, 1)
- ));
- }
- $last_op = DIFF_INSERT;
- break;
- } else {
- $x--;
- $y--;
- //if (text1.charAt(x) != text2.charAt(y)) {
- // throw new Error('No diagonal. Can\'t happen. (diff_path1)');
- //}
- if ($last_op === DIFF_EQUAL) {
- $path[0][1] = mb_substr($text1, $x, 1) . $path[0][1];
- } else {
- array_unshift($path, array (
- DIFF_EQUAL,
- mb_substr($text1, $x, 1)
- ));
- }
- $last_op = DIFF_EQUAL;
- }
- }
- }
- return $path;
- }
-
- /**
- * Work from the middle back to the end to determine the path.
- * @param {Array.<Object>} v_map Array of paths.
- * @param {string} text1 Old string fragment to be diffed.
- * @param {string} text2 New string fragment to be diffed.
- * @return {Array.<Array.<number|string>>} Array of diff tuples.
- * @private
- */
- function diff_path2($v_map, $text1, $text2) {
- $path = array ();
- $pathLength = 0;
- $x = mb_strlen($text1);
- $y = mb_strlen($text2);
- /** @type {number?} */
- $last_op = null;
- for ($d = count($v_map) - 2; $d >= 0; $d--) {
- while (1) {
- if (isset ($v_map[$d][($x -1) . ',' . $y])) {
- $x--;
- if ($last_op === DIFF_DELETE) {
- $path[$pathLength -1][1] .= $text1[mb_strlen($text1) - $x -1];
- } else {
- $path[$pathLength++] = array (
- DIFF_DELETE,
- $text1[mb_strlen($text1) - $x -1]
- );
- }
- $last_op = DIFF_DELETE;
- break;
- }
- elseif (isset ($v_map[$d][$x . ',' . ($y -1)])) {
- $y--;
- if ($last_op === DIFF_INSERT) {
- $path[$pathLength -1][1] .= $text2[mb_strlen($text2) - $y -1];
- } else {
- $path[$pathLength++] = array (
- DIFF_INSERT,
- $text2[mb_strlen($text2) - $y -1]
- );
- }
- $last_op = DIFF_INSERT;
- break;
- } else {
- $x--;
- $y--;
- //if (text1.charAt(text1.length - x - 1) !=
- // text2.charAt(text2.length - y - 1)) {
- // throw new Error('No diagonal. Can\'t happen. (diff_path2)');
- //}
- if ($last_op === DIFF_EQUAL) {
- $path[$pathLength -1][1] .= $text1[mb_strlen($text1) - $x -1];
- } else {
- $path[$pathLength++] = array (
- DIFF_EQUAL,
- $text1[mb_strlen($text1) - $x -1]
- );
- }
- $last_op = DIFF_EQUAL;
- }
- }
- }
- return $path;
- }
-
- /**
- * Determine the common prefix of two strings
- * @param {string} text1 First string.
- * @param {string} text2 Second string.
- * @return {number} The number of characters common to the start of each
- * string.
- */
- function diff_commonPrefix($text1, $text2) {
- for ($i = 0; 1; $i++) {
- $t1 = mb_substr($text1, $i, 1);
- $t2 = mb_substr($text2, $i, 1);
- if($t1==='' || $t2==='' || $t1 !== $t2 ){
- return $i;
- }
- }
- }
-
- /**
- * Determine the common suffix of two strings
- * @param {string} text1 First string.
- * @param {string} text2 Second string.
- * @return {number} The number of characters common to the end of each string.
- */
- function diff_commonSuffix($text1, $text2) {
- return $this->diff_commonPrefix( strrev($text1), strrev($text2) );
- }
-
- /**
- * Do the two texts share a mb_substring which is at least half the length of the
- * longer text?
- * @param {string} text1 First string.
- * @param {string} text2 Second string.
- * @return {Array.<string>?} Five element Array, containing the prefix of
- * text1, the suffix of text1, the prefix of text2, the suffix of
- * text2 and the common middle. Or null if there was no match.
- */
- function diff_halfMatch($text1, $text2) {
- $longtext = mb_strlen($text1) > mb_strlen($text2) ? $text1 : $text2;
- $shorttext = mb_strlen($text1) > mb_strlen($text2) ? $text2 : $text1;
- if (mb_strlen($longtext) < 10 || mb_strlen($shorttext) < 1) {
- return null; // Pointless.
- }
-
- // First check if the second quarter is the seed for a half-match.
- $hm1 = $this->diff_halfMatchI($longtext, $shorttext, ceil(mb_strlen($longtext) / 4));
- // Check again based on the third quarter.
- $hm2 = $this->diff_halfMatchI($longtext, $shorttext, ceil(mb_strlen($longtext) / 2));
-
- if (!$hm1 && !$hm2) {
- return null;
- } elseif (!$hm2) {
- $hm = $hm1;
- } elseif (!$hm1) {
- $hm = $hm2;
- } else {
- // Both matched. Select the longest.
- $hm = mb_strlen($hm1[4]) > mb_strlen($hm2[4]) ? $hm1 : $hm2;
- }
-
- // A half-match was found, sort out the return data.
- if (mb_strlen($text1) > mb_strlen($text2)) {
- $text1_a = $hm[0];
- $text1_b = $hm[1];
- $text2_a = $hm[2];
- $text2_b = $hm[3];
- } else {
- $text2_a = $hm[0];
- $text2_b = $hm[1];
- $text1_a = $hm[2];
- $text1_b = $hm[3];
- }
- $mid_common = $hm[4];
- return array( $text1_a, $text1_b, $text2_a, $text2_b, $mid_common );
- }
-
- /**
- * Does a mb_substring of shorttext exist within longtext such that the mb_substring
- * is at least half the length of longtext?
- * Closure, but does not reference any external variables.
- * @param {string} longtext Longer string.
- * @param {string} shorttext Shorter string.
- * @param {number} i Start index of quarter length mb_substring within longtext
- * @return {Array.<string>?} Five element Array, containing the prefix of
- * longtext, the suffix of longtext, the prefix of shorttext, the suffix
- * of shorttext and the common middle. Or null if there was no match.
- * @private
- */
- function diff_halfMatchI($longtext, $shorttext, $i) {
- // Start with a 1/4 length mb_substring at position i as a seed.
- $seed = mb_substr($longtext, $i, floor(mb_strlen($longtext) / 4));
-
- $j = -1;
- $best_common = '';
- $best_longtext_a = null;
- $best_longtext_b = null;
- $best_shorttext_a = null;
- $best_shorttext_b = null;
- while ( ($j = mb_strpos($shorttext, $seed, $j + 1)) !== false ) {
- $prefixLength = $this->diff_commonPrefix(mb_substr($longtext, $i), mb_substr($shorttext, $j));
- $suffixLength = $this->diff_commonSuffix(mb_substr($longtext, 0, $i), mb_substr($shorttext, 0, $j));
- if (mb_strlen($best_common) < $suffixLength + $prefixLength) {
- $best_common = mb_substr($shorttext, $j - $suffixLength, $suffixLength) . mb_substr($shorttext, $j, $prefixLength);
- $best_longtext_a = mb_substr($longtext, 0, $i - $suffixLength);
- $best_longtext_b = mb_substr($longtext, $i + $prefixLength);
- $best_shorttext_a = mb_substr($shorttext, 0, $j - $suffixLength);
- $best_shorttext_b = mb_substr($shorttext, $j + $prefixLength);
- }
- }
- if (mb_strlen($best_common) >= mb_strlen($longtext) / 2) {
- return array (
- $best_longtext_a,
- $best_longtext_b,
- $best_shorttext_a,
- $best_shorttext_b,
- $best_common
- );
- } else {
- return null;
- }
- }
-
- /**
- * Reduce the number of edits by eliminating semantically trivial equalities.
- * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
- */
- function diff_cleanupSemantic(&$diffs) {
- $changes = false;
- $equalities = array (); // Stack of indices where equalities are found.
- $equalitiesLength = 0; // Keeping our own length var is faster in JS.
- $lastequality = null; // Always equal to equalities[equalitiesLength-1][1]
- $pointer = 0; // Index of current position.
- // Number of characters that changed prior to the equality.
- $length_changes1 = 0;
- // Number of characters that changed after the equality.
- $length_changes2 = 0;
- while ($pointer < count($diffs)) {
- if ($diffs[$pointer][0] == DIFF_EQUAL) { // equality found
- $equalities[$equalitiesLength++] = $pointer;
- $length_changes1 = $length_changes2;
- $length_changes2 = 0;
- $lastequality = $diffs[$pointer][1];
- } else { // an insertion or deletion
- $length_changes2 += mb_strlen($diffs[$pointer][1]);
- if ($lastequality !== null && (mb_strlen($lastequality) <= $length_changes1) && (mb_strlen($lastequality) <= $length_changes2)) {
- // Duplicate record
- $zzz_diffs = array_splice($diffs, $equalities[$equalitiesLength -1], 0, array(array (
- DIFF_DELETE,
- $lastequality
- )));
- // Change second copy to insert.
- $diffs[$equalities[$equalitiesLength -1] + 1][0] = DIFF_INSERT;
- // Throw away the equality we just deleted.
- $equalitiesLength--;
- // Throw away the previous equality (it needs to be reevaluated).
- $equalitiesLength--;
- $pointer = $equalitiesLength > 0 ? $equalities[$equalitiesLength -1] : -1;
- $length_changes1 = 0; // Reset the counters.
- $length_changes2 = 0;
- $lastequality = null;
- $changes = true;
- }
- }
- $pointer++;
- }
- if ($changes) {
- $this->diff_cleanupMerge($diffs);
- }
- $this->diff_cleanupSemanticLossless($diffs);
- }
-
- /**
- * Look for single edits surrounded on both sides by equalities
- * which can be shifted sideways to align the edit to a word boundary.
- * e.g: The c<ins>at c</ins>ame. -> The <ins>cat </ins>came.
- * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
- */
- function diff_cleanupSemanticLossless(&$diffs) {
-
- $pointer = 1;
- // Intentionally ignore the first and last element (don't need checking).
- while ($pointer < count($diffs) - 1) {
- if ($diffs[$pointer -1][0] == DIFF_EQUAL && $diffs[$pointer +1][0] == DIFF_EQUAL) {
- // This is a single edit surrounded by equalities.
- $equality1 = $diffs[$pointer -1][1];
- $edit = $diffs[$pointer][1];
- $equality2 = $diffs[$pointer +1][1];
-
- // First, shift the edit as far left as possible.
- $commonOffset = $this->diff_commonSuffix($equality1, $edit);
- if ($commonOffset !== '') {
- $commonString = mb_substr($edit, mb_strlen($edit) - $commonOffset);
- $equality1 = mb_substr($equality1, 0, mb_strlen($equality1) - $commonOffset);
- $edit = $commonString . mb_substr($edit, 0, mb_strlen($edit) - $commonOffset);
- $equality2 = $commonString . $equality2;
- }
-
- // Second, step character by character right, looking for the best fit.
- $bestEquality1 = $equality1;
- $bestEdit = $edit;
- $bestEquality2 = $equality2;
- $bestScore = $this->diff_cleanupSemanticScore($equality1, $edit) + $this->diff_cleanupSemanticScore($edit, $equality2);
- while (isset($equality2[0]) && $edit[0] === $equality2[0]) {
- $equality1 .= $edit[0];
- $edit = mb_substr($edit, 1) . $equality2[0];
- $equality2 = mb_substr($equality2, 1);
- $score = $this->diff_cleanupSemanticScore($equality1, $edit) + $this->diff_cleanupSemanticScore($edit, $equality2);
- // The >= encourages trailing rather than leading whitespace on edits.
- if ($score >= $bestScore) {
- $bestScore = $score;
- $bestEquality1 = $equality1;
- $bestEdit = $edit;
- $bestEquality2 = $equality2;
- }
- }
-
- if ($diffs[$pointer -1][1] != $bestEquality1) {
- // We have an improvement, save it back to the diff.
- if ($bestEquality1) {
- $diffs[$pointer -1][1] = $bestEquality1;
- } else {
- $zzz_diffs = array_splice($diffs, $pointer -1, 1);
- $pointer--;
- }
- $diffs[$pointer][1] = $bestEdit;
- if ($bestEquality2) {
- $diffs[$pointer +1][1] = $bestEquality2;
- } else {
- $zzz_diffs = array_splice($diffs, $pointer +1, 1);
- $pointer--;
- }
- }
- }
- $pointer++;
- }
- }
-
- /**
- * Given two strings, compute a score representing whether the internal
- * boundary falls on logical boundaries.
- * Scores range from 5 (best) to 0 (worst).
- * Closure, makes reference to regex patterns defined above.
- * @param {string} one First string
- * @param {string} two Second string
- * @return {number} The score.
- */
- function diff_cleanupSemanticScore($one, $two) {
- // Define some regex patterns for matching boundaries.
- $punctuation = '/[^a-zA-Z0-9]/';
- $whitespace = '/\s/';
- $linebreak = '/[\r\n]/';
- $blanklineEnd = '/\n\r?\n$/';
- $blanklineStart = '/^\r?\n\r?\n/';
-
- if (!$one || !$two) {
- // Edges are the best.
- return 5;
- }
-
- // Each port of this function behaves slightly differently due to
- // subtle differences in each language's definition of things like
- // 'whitespace'. Since this function's purpose is largely cosmetic,
- // the choice has been made to use each language's native features
- // rather than force total conformity.
- $score = 0;
- // One point for non-alphanumeric.
- if (preg_match($punctuation, $one[mb_strlen($one) - 1]) || preg_match($punctuation, $two[0])) {
- $score++;
- // Two points for whitespace.
- if (preg_match($whitespace, $one[mb_strlen($one) - 1] ) || preg_match($whitespace, $two[0])) {
- $score++;
- // Three points for line breaks.
- if (preg_match($linebreak, $one[mb_strlen($one) - 1]) || preg_match($linebreak, $two[0])) {
- $score++;
- // Four points for blank lines.
- if (preg_match($blanklineEnd, $one) || preg_match($blanklineStart, $two)) {
- $score++;
- }
- }
- }
- }
- return $score;
- }
-
- /**
- * Reduce the number of edits by eliminating operationally trivial equalities.
- * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
- */
- function diff_cleanupEfficiency(&$diffs) {
- $changes = false;
- $equalities = array (); // Stack of indices where equalities are found.
- $equalitiesLength = 0; // Keeping our own length var is faster in JS.
- $lastequality = ''; // Always equal to equalities[equalitiesLength-1][1]
- $pointer = 0; // Index of current position.
- // Is there an insertion operation before the last equality.
- $pre_ins = false;
- // Is there a deletion operation before the last equality.
- $pre_del = false;
- // Is there an insertion operation after the last equality.
- $post_ins = false;
- // Is there a deletion operation after the last equality.
- $post_del = false;
- while ($pointer < count($diffs)) {
- if ($diffs[$pointer][0] == DIFF_EQUAL) { // equality found
- if (mb_strlen($diffs[$pointer][1]) < $this->Diff_EditCost && ($post_ins || $post_del)) {
- // Candidate found.
- $equalities[$equalitiesLength++] = $pointer;
- $pre_ins = $post_ins;
- $pre_del = $post_del;
- $lastequality = $diffs[$pointer][1];
- } else {
- // Not a candidate, and can never become one.
- $equalitiesLength = 0;
- $lastequality = '';
- }
- $post_ins = $post_del = false;
- } else { // an insertion or deletion
- if ($diffs[$pointer][0] == DIFF_DELETE) {
- $post_del = true;
- } else {
- $post_ins = true;
- }
- /*
- * Five types to be split:
- * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
- * <ins>A</ins>X<ins>C</ins><del>D</del>
- * <ins>A</ins><del>B</del>X<ins>C</ins>
- * <ins>A</del>X<ins>C</ins><del>D</del>
- * <ins>A</ins><del>B</del>X<del>C</del>
- */
- if ($lastequality && (($pre_ins && $pre_del && $post_ins && $post_del) || ((mb_strlen($lastequality) < $this->Diff_EditCost / 2) && ($pre_ins + $pre_del + $post_ins + $post_del) == 3))) {
- // Duplicate record
- $zzz_diffs = array_splice($diffs, $equalities[$equalitiesLength -1], 0, array(array (
- DIFF_DELETE,
- $lastequality
- )));
- // Change second copy to insert.
- $diffs[$equalities[$equalitiesLength -1] + 1][0] = DIFF_INSERT;
- $equalitiesLength--; // Throw away the equality we just deleted;
- $lastequality = '';
- if ($pre_ins && $pre_del) {
- // No changes made which could affect previous entry, keep going.
- $post_ins = $post_del = true;
- $equalitiesLength = 0;
- } else {
- $equalitiesLength--; // Throw away the previous equality;
- $pointer = $equalitiesLength > 0 ? $equalities[$equalitiesLength -1] : -1;
- $post_ins = $post_del = false;
- }
- $changes = true;
- }
- }
- $pointer++;
- }
-
- if ($changes) {
- $this->diff_cleanupMerge($diffs);
- }
- }
-
- /**
- * Reorder and merge like edit sections. Merge equalities.
- * Any edit section can move as long as it doesn't cross an equality.
- * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
- */
- function diff_cleanupMerge(&$diffs) {
- array_push($diffs, array ( DIFF_EQUAL, '' )); // Add a dummy entry at the end.
- $pointer = 0;
- $count_delete = 0;
- $count_insert = 0;
- $text_delete = '';
- $text_insert = '';
- $commonlength = null;
- while ($pointer < count($diffs)) {
- switch ($diffs[$pointer][0]) {
- case DIFF_INSERT :
- $count_insert++;
- $text_insert .= $diffs[$pointer][1];
- $pointer++;
- break;
- case DIFF_DELETE :
- $count_delete++;
- $text_delete .= $diffs[$pointer][1];
- $pointer++;
- break;
- case DIFF_EQUAL :
- // Upon reaching an equality, check for prior redundancies.
- if ($count_delete !== 0 || $count_insert !== 0) {
- if ($count_delete !== 0 && $count_insert !== 0) {
- // Factor out any common prefixies.
- $commonlength = $this->diff_commonPrefix($text_insert, $text_delete);
- if ($commonlength !== 0) {
- if (($pointer - $count_delete - $count_insert) > 0 && $diffs[$pointer - $count_delete - $count_insert -1][0] == DIFF_EQUAL) {
- $diffs[$pointer - $count_delete - $count_insert -1][1] .= mb_substr($text_insert, 0, $commonlength);
- } else {
- array_splice($diffs, 0, 0, array(array (
- DIFF_EQUAL,
- mb_substr($text_insert, 0, $commonlength)
- )));
- $pointer++;
- }
- $text_insert = mb_substr($text_insert, $commonlength);
- $text_delete = mb_substr($text_delete, $commonlength);
- }
- // Factor out any common suffixies.
- $commonlength = $this->diff_commonSuffix($text_insert, $text_delete);
- if ($commonlength !== 0) {
- $diffs[$pointer][1] = mb_substr($text_insert, mb_strlen($text_insert) - $commonlength) . $diffs[$pointer][1];
- $text_insert = mb_substr($text_insert, 0, mb_strlen($text_insert) - $commonlength);
- $text_delete = mb_substr($text_delete, 0, mb_strlen($text_delete) - $commonlength);
- }
- }
- // Delete the offending records and add the merged ones.
- if ($count_delete === 0) {
- array_splice($diffs, $pointer-$count_delete-$count_insert, $count_delete+$count_insert, array(array(
- DIFF_INSERT,
- $text_insert
- )));
- } elseif ($count_insert === 0) {
- array_splice($diffs, $pointer-$count_delete-$count_insert, $count_delete+$count_insert, array(array(
- DIFF_DELETE,
- $text_delete
- )));
- } else {
- array_splice($diffs, $pointer-$count_delete-$count_insert, $count_delete+$count_insert, array(array(
- DIFF_DELETE,
- $text_delete
- ), array (
- DIFF_INSERT,
- $text_insert
- )));
- }
- $pointer = $pointer - $count_delete - $count_insert + ($count_delete ? 1 : 0) + ($count_insert ? 1 : 0) + 1;
- } elseif ($pointer !== 0 && $diffs[$pointer -1][0] == DIFF_EQUAL) {
- // Merge this equality with the previous one.
- $diffs[$pointer -1][1] .= $diffs[$pointer][1];
- array_splice($diffs, $pointer, 1);
- } else {
- $pointer++;
- }
- $count_insert = 0;
- $count_delete = 0;
- $text_delete = '';
- $text_insert = '';
- break;
- }
- }
- if ($diffs[count($diffs) - 1][1] === '') {
- array_pop($diffs); // Remove the dummy entry at the end.
- }
-
- // Second pass: look for single edits surrounded on both sides by equalities
- // which can be shifted sideways to eliminate an equality.
- // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
- $changes = false;
- $pointer = 1;
- // Intentionally ignore the first and last element (don't need checking).
- while ($pointer < count($diffs) - 1) {
- if ($diffs[$pointer-1][0] == DIFF_EQUAL && $diffs[$pointer+1][0] == DIFF_EQUAL) {
- // This is a single edit surrounded by equalities.
- if ( mb_substr($diffs[$pointer][1], mb_strlen($diffs[$pointer][1]) - mb_strlen($diffs[$pointer -1][1])) == $diffs[$pointer -1][1]) {
- // Shift the edit over the previous equality.
- $diffs[$pointer][1] = $diffs[$pointer -1][1] . mb_substr($diffs[$pointer][1], 0, mb_strlen($diffs[$pointer][1]) - mb_strlen($diffs[$pointer -1][1]));
- $diffs[$pointer +1][1] = $diffs[$pointer -1][1] . $diffs[$pointer +1][1];
- array_splice($diffs, $pointer -1, 1);
- $changes = true;
- } elseif (mb_substr($diffs[$pointer][1], 0, mb_strlen($diffs[$pointer +1][1])) == $diffs[$pointer +1][1]) {
- // Shift the edit over the next equality.
- $diffs[$pointer -1][1] .= $diffs[$pointer +1][1];
-
- $diffs[$pointer][1] = mb_substr($diffs[$pointer][1], mb_strlen($diffs[$pointer +1][1])) . $diffs[$pointer +1][1];
- array_splice($diffs, $pointer +1, 1);
- $changes = true;
- }
- }
- $pointer++;
- }
- // If shifts were made, the diff needs reordering and another shift sweep.
- if ($changes) {
- $this->diff_cleanupMerge($diffs);
- }
- }
-
- /**
- * loc is a location in text1, compute and return the equivalent location in
- * text2.
- * e.g. 'The cat' vs 'The big cat', 1->1, 5->8
- * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
- * @param {number} loc Location within text1.
- * @return {number} Location within text2.
- */
- function diff_xIndex($diffs, $loc) {
- $chars1 = 0;
- $chars2 = 0;
- $last_chars1 = 0;
- $last_chars2 = 0;
- for ($x = 0; $x < count($diffs); $x++) {
- if ($diffs[$x][0] !== DIFF_INSERT) { // Equality or deletion.
- $chars1 += mb_strlen($diffs[$x][1]);
- }
- if ($diffs[$x][0] !== DIFF_DELETE) { // Equality or insertion.
- $chars2 += mb_strlen($diffs[$x][1]);
- }
- if ($chars1 > $loc) { // Overshot the location.
- break;
- }
- $last_chars1 = $chars1;
- $last_chars2 = $chars2;
- }
- // Was the location was deleted?
- if (count($diffs) != $x && $diffs[$x][0] === DIFF_DELETE) {
- return $last_chars2;
- }
- // Add the remaining character length.
- return $last_chars2 + ($loc - $last_chars1);
- }
-
- /**
- * Convert a diff array into a pretty HTML report.
- * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
- * @return {string} HTML representation.
- */
- function diff_prettyHtml($diffs) {
- $html = array ();
- $i = 0;
- for ($x = 0; $x < count($diffs); $x++) {
- $op = $diffs[$x][0]; // Operation (insert, delete, equal)
- $data = $diffs[$x][1]; // Text of change.
- $text = preg_replace(array (
- '/&/',
- '/</',
- '/>/',
- "/\n/"
- ), array (
- '&amp;',
- '&lt;',
- '&gt;',
- '&para;<BR>'
- ), $data);
-
- switch ($op) {
- case DIFF_INSERT :
- $html[$x] = '<INS STYLE="background:#E6FFE6;" TITLE="i=' . $i . '">' . $text . '</INS>';
- break;
- case DIFF_DELETE :
- $html[$x] = '<DEL STYLE="background:#FFE6E6;" TITLE="i=' . $i . '">' . $text . '</DEL>';
- break;
- case DIFF_EQUAL :
- $html[$x] = '<SPAN TITLE="i=' . $i . '">' . $text . '</SPAN>';
- break;
- }
- if ($op !== DIFF_DELETE) {
- $i += mb_strlen($data);
- }
- }
- return implode('',$html);
- }
-
- /**
- * Compute and return the source text (all equalities and deletions).
- * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
- * @return {string} Source text.
- */
- function diff_text1($diffs) {
- $text = array ();
- for ($x = 0; $x < count($diffs); $x++) {
- if ($diffs[$x][0] !== DIFF_INSERT) {
- $text[$x] = $diffs[$x][1];
- }
- }
- return implode('',$text);
- }
-
- /**
- * Compute and return the destination text (all equalities and insertions).
- * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
- * @return {string} Destination text.
- */
- function diff_text2($diffs) {
- $text = array ();
- for ($x = 0; $x < count($diffs); $x++) {
- if ($diffs[$x][0] !== DIFF_DELETE) {
- $text[$x] = $diffs[$x][1];
- }
- }
- return implode('',$text);
- }
-
- /**
- * Compute the Levenshtein distance; the number of inserted, deleted or
- * substituted characters.
- * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
- * @return {number} Number of changes.
- */
- function diff_levenshtein($diffs) {
- $levenshtein = 0;
- $insertions = 0;
- $deletions = 0;
- for ($x = 0; $x < count($diffs); $x++) {
- $op = $diffs[$x][0];
- $data = $diffs[$x][1];
- switch ($op) {
- case DIFF_INSERT :
- $insertions += mb_strlen($data);
- break;
- case DIFF_DELETE :
- $deletions += mb_strlen($data);
- break;
- case DIFF_EQUAL :
- // A deletion and an insertion is one substitution.
- $levenshtein += max($insertions, $deletions);
- $insertions = 0;
- $deletions = 0;
- break;
- }
- }
- $levenshtein += max($insertions, $deletions);
- return $levenshtein;
- }
-
- /**
- * Crush the diff into an encoded string which describes the operations
- * required to transform text1 into text2.
- * E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'.
- * Operations are tab-separated. Inserted text is escaped using %xx notation.
- * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
- * @return {string} Delta text.
- */
- function diff_toDelta($diffs) {
- $text = array ();
- for ($x = 0; $x < count($diffs); $x++) {
- switch ($diffs[$x][0]) {
- case DIFF_INSERT :
- $text[$x] = '+' .encodeURI($diffs[$x][1]);
- break;
- case DIFF_DELETE :
- $text[$x] = '-' .mb_strlen($diffs[$x][1]);
- break;
- case DIFF_EQUAL :
- $text[$x] = '=' .mb_strlen($diffs[$x][1]);
- break;
- }
- }
- return str_replace('%20', ' ', implode("\t", $text));
- }
-
- /**
- * Given the original text1, and an encoded string which describes the
- * operations required to transform text1 into text2, compute the full diff.
- * @param {string} text1 Source string for the diff.
- * @param {string} delta Delta text.
- * @return {Array.<Array.<number|string>>} Array of diff tuples.
- * @throws {Error} If invalid input.
- */
- function diff_fromDelta($text1, $delta) {
- $diffs = array ();
- $diffsLength = 0; // Keeping our own length var is faster in JS.
- $pointer = 0; // Cursor in text1
- $tokens = preg_split("/\t/", $delta);
-
- for ($x = 0; $x < count($tokens); $x++) {
- // Each token begins with a one character parameter which specifies the
- // operation of this token (delete, insert, equality).
- $param = mb_substr($tokens[$x], 1);
- switch ($tokens[$x][0]) {
- case '+' :
- try {
- $diffs[$diffsLength++] = array (
- DIFF_INSERT,
- decodeURI($param)
- );
- } catch (Exception $ex) {
- echo_Exception('Illegal escape in diff_fromDelta: ' . $param);
- // Malformed URI sequence.
- }
- break;
- case '-' :
- // Fall through.
- case '=' :
- $n = (int) $param;
- if ($n < 0) {
- echo_Exception('Invalid number in diff_fromDelta: ' . $param);
- }
- $text = mb_substr($text1, $pointer, $n);
- $pointer += $n;
- if ($tokens[$x][0] == '=') {
- $diffs[$diffsLength++] = array (
- DIFF_EQUAL,
- $text
- );
- } else {
- $diffs[$diffsLength++] = array (
- DIFF_DELETE,
- $text
- );
- }
- break;
- default :
- // Blank tokens are ok (from a trailing \t).
- // Anything else is an error.
- if ($tokens[$x]) {
- echo_Exception('Invalid diff operation in diff_fromDelta: ' . $tokens[$x]);
- }
- }
- }
- if ($pointer != mb_strlen($text1)) {
-// throw new Exception('Delta length (' . $pointer . ') does not equal source text length (' . mb_strlen($text1) . ').');
- echo_Exception('Delta length (' . $pointer . ') does not equal source text length (' . mb_strlen($text1) . ').');
- }
- return $diffs;
- }
-
- // MATCH FUNCTIONS
-
- /**
- * Locate the best instance of 'pattern' in 'text' near 'loc'.
- * @param {string} text The text to search.
- * @param {string} pattern The pattern to search for.
- * @param {number} loc The location to search around.
- * @return {number} Best match index or -1.
- */
- function match_main($text, $pattern, $loc) {
- $loc = max(0, min($loc, mb_strlen($text)));
- if ($text == $pattern) {
- // Shortcut (potentially not guaranteed by the algorithm)
- return 0;
- }
- elseif (!mb_strlen($text)) {
- // Nothing to match.
- return -1;
- }
- elseif (mb_substr($text, $loc, mb_strlen($pattern)) == $pattern) {
- // Perfect match at the perfect spot! (Includes case of null pattern)
- return $loc;
- } else {
- // Do a fuzzy compare.
- return $this->match_bitap($text, $pattern, $loc);
- }
- }
-
- /**
- * Locate the best instance of 'pattern' in 'text' near 'loc' using the
- * Bitap algorithm.
- * @param {string} text The text to search.
- * @param {string} pattern The pattern to search for.
- * @param {number} loc The location to search around.
- * @return {number} Best match index or -1.
- * @private
- */
- function match_bitap($text, $pattern, $loc) {
- if (mb_strlen($pattern) > Match_MaxBits) {
- echo_Exception('Pattern too long for this browser.');
- }
-
- // Initialise the alphabet.
- $s = $this->match_alphabet($pattern);
-
- // Highest score beyond which we give up.
- $score_threshold = $this->Match_Threshold;
-
- // Is there a nearby exact match? (speedup)
- $best_loc = mb_strpos($text, $pattern, $loc);
- if ($best_loc !== false) {
- $score_threshold = min($this->match_bitapScore(0, $best_loc, $pattern, $loc), $score_threshold);
- }
-
- // What about in the other direction? (speedup)
- $best_loc = mb_strrpos( $text, $pattern, min($loc + mb_strlen($pattern), mb_strlen($text)) );
- if ($best_loc !== false) {
- $score_threshold = min($this->match_bitapScore(0, $best_loc, $pattern, $loc), $score_threshold);
- }
-
- // Initialise the bit arrays.
- $matchmask = 1 << (mb_strlen($pattern) - 1);
- $best_loc = -1;
-
- $bin_min = null;
- $bin_mid = null;
- $bin_max = mb_strlen($pattern) + mb_strlen($text);
- $last_rd = null;
- for ($d = 0; $d < mb_strlen($pattern); $d++) {
- // Scan for the best match; each iteration allows for one more error.
- // Run a binary search to determine how far from 'loc' we can stray at this
- // error level.
- $bin_min = 0;
- $bin_mid = $bin_max;
- while ($bin_min < $bin_mid) {
- if ($this->match_bitapScore($d, $loc + $bin_mid, $pattern, $loc) <= $score_threshold) {
- $bin_min = $bin_mid;
- } else {
- $bin_max = $bin_mid;
- }
- $bin_mid = floor(($bin_max - $bin_min) / 2 + $bin_min);
- }
- // Use the result from this iteration as the maximum for the next.
- $bin_max = $bin_mid;
- $start = max(1, $loc - $bin_mid +1);
- $finish = min($loc + $bin_mid, mb_strlen($text)) + mb_strlen($pattern);
-
- $rd = Array (
- $finish +2
- );
- $rd[$finish +1] = (1 << $d) - 1;
- for ($j = $finish; $j >= $start; $j--) {
- // The alphabet (s) is a sparse hash, so the following line generates
- // warnings.
-@ $charMatch = $s[ $text[$j -1] ];
- if ($d === 0) { // First pass: exact match.
- $rd[$j] = (($rd[$j +1] << 1) | 1) & $charMatch;
- } else { // Subsequent passes: fuzzy match.
- $rd[$j] = (($rd[$j +1] << 1) | 1) & $charMatch | ((($last_rd[$j +1] | $last_rd[$j]) << 1) | 1) | $last_rd[$j +1];
- }
- if ($rd[$j] & $matchmask) {
- $score = $this->match_bitapScore($d, $j -1, $pattern, $loc);
- // This match will almost certainly be better than any existing match.
- // But check anyway.
- if ($score <= $score_threshold) {
- // Told you so.
- $score_threshold = $score;
- $best_loc = $j -1;
- if ($best_loc > $loc) {
- // When passing loc, don't exceed our current distance from loc.
- $start = max(1, 2 * $loc - $best_loc);
- } else {
- // Already passed loc, downhill from here on in.
- break;
- }
- }
- }
- }
- // No hope for a (better) match at greater error levels.
- if ($this->match_bitapScore($d +1, $loc, $pattern, $loc) > $score_threshold) {
- break;
- }
- $last_rd = $rd;
- }
- return (int)$best_loc;
- }
-
- /**
- * Compute and return the score for a match with e errors and x location.
- * Accesses loc and pattern through being a closure.
- * @param {number} e Number of errors in match.
- * @param {number} x Location of match.
- * @return {number} Overall score for match (0.0 = good, 1.0 = bad).
- * @private
- */
- function match_bitapScore($e, $x, $pattern, $loc) {
- $accuracy = $e / mb_strlen($pattern);
- $proximity = abs($loc - $x);
- if (!$this->Match_Distance) {
- // Dodge divide by zero error.
- return $proximity ? 1.0 : $accuracy;
- }
- return $accuracy + ($proximity / $this->Match_Distance);
- }
-
- /**
- * Initialise the alphabet for the Bitap algorithm.
- * @param {string} pattern The text to encode.
- * @return {Object} Hash of character locations.
- * @private
- */
- function match_alphabet($pattern) {
- $s = array ();
- for ($i = 0; $i < mb_strlen($pattern); $i++) {
- $s[ $pattern[$i] ] = 0;
- }
- for ($i = 0; $i < mb_strlen($pattern); $i++) {
- $s[ $pattern[$i] ] |= 1 << (mb_strlen($pattern) - $i - 1);
- }
- return $s;
- }
-
- // PATCH FUNCTIONS
-
- /**
- * Increase the context until it is unique,
- * but don't let the pattern expand beyond Match_MaxBits.
- * @param {patch_obj} patch The patch to grow.
- * @param {string} text Source text.
- * @private
- */
- function patch_addContext($patch, $text) {
- $pattern = mb_substr($text, $patch->start2, $patch->length1 );
- $previousPattern = null;
- $padding = 0;
- $i = 0;
- while (
- ( mb_strlen($pattern) === 0 // Javascript's indexOf/lastIndexOd return 0/strlen respectively if pattern = ''
- || mb_strpos($text, $pattern) !== mb_strrpos($text, $pattern)
- )
- && $pattern !== $previousPattern // avoid infinte loop
- && mb_strlen($pattern) < Match_MaxBits - $this->Patch_Margin - $this->Patch_Margin ) {
- $padding += $this->Patch_Margin;
- $previousPattern = $pattern;
- $pattern = mb_substr($text, max($patch->start2 - $padding,0), ($patch->start2 + $patch->length1 + $padding) - max($patch->start2 - $padding,0) );
- }
- // Add one chunk for good luck.
- $padding += $this->Patch_Margin;
- // Add the prefix.
- $prefix = mb_substr($text, max($patch->start2 - $padding,0), $patch->start2 - max($patch->start2 - $padding,0) );
- if ($prefix!=='') {
- array_unshift($patch->diffs, array (
- DIFF_EQUAL,
- $prefix
- ));
- }
- // Add the suffix.
- $suffix = mb_substr($text, $patch->start2 + $patch->length1, ($patch->start2 + $patch->length1 + $padding) - ($patch->start2 + $patch->length1) );
- if ($suffix!=='') {
- array_push($patch->diffs, array (
- DIFF_EQUAL,
- $suffix
- ));
- }
-
- // Roll back the start points.
- $patch->start1 -= mb_strlen($prefix);
- $patch->start2 -= mb_strlen($prefix);
- // Extend the lengths.
- $patch->length1 += mb_strlen($prefix) + mb_strlen($suffix);
- $patch->length2 += mb_strlen($prefix) + mb_strlen($suffix);
- }
-
- /**
- * Compute a list of patches to turn text1 into text2.
- * Use diffs if provided, otherwise compute it ourselves.
- * There are four ways to call this function, depending on what data is
- * available to the caller:
- * Method 1:
- * a = text1, b = text2
- * Method 2:
- * a = diffs
- * Method 3 (optimal):
- * a = text1, b = diffs
- * Method 4 (deprecated, use method 3):
- * a = text1, b = text2, c = diffs
- *
- * @param {string|Array.<Array.<number|string>>} a text1 (methods 1,3,4) or
- * Array of diff tuples for text1 to text2 (method 2).
- * @param {string|Array.<Array.<number|string>>} opt_b text2 (methods 1,4) or
- * Array of diff tuples for text1 to text2 (method 3) or undefined (method 2).
- * @param {string|Array.<Array.<number|string>>} opt_c Array of diff tuples for
- * text1 to text2 (method 4) or undefined (methods 1,2,3).
- * @return {Array.<patch_obj>} Array of patch objects.
- */
- function patch_make($a, $opt_b = null, $opt_c = null ) {
- if (is_string($a) && is_string($opt_b) && $opt_c === null ) {
- // Method 1: text1, text2
- // Compute diffs from text1 and text2.
- $text1 = $a;
- $diffs = $this->diff_main($text1, $opt_b, true);
- if ( count($diffs) > 2) {
- $this->diff_cleanupSemantic($diffs);
- $this->diff_cleanupEfficiency($diffs);
- }
- } elseif ( is_array($a) && $opt_b === null && $opt_c === null) {
- // Method 2: diffs
- // Compute text1 from diffs.
- $diffs = $a;
- $text1 = $this->diff_text1($diffs);
- } elseif ( is_string($a) && is_array($opt_b) && $opt_c === null) {
- // Method 3: text1, diffs
- $text1 = $a;
- $diffs = $opt_b;
- } elseif ( is_string($a) && is_string($opt_b) && is_array($opt_c) ) {
- // Method 4: text1, text2, diffs
- // text2 is not used.
- $text1 = $a;
- $diffs = $opt_c;
- } else {
- echo_Exception('Unknown call format to patch_make.');
- }
-
- if ( count($diffs) === 0) {
- return array(); // Get rid of the null case.
- }
- $patches = array();
- $patch = new patch_obj();
- $patchDiffLength = 0; // Keeping our own length var is faster in JS.
- $char_count1 = 0; // Number of characters into the text1 string.
- $char_count2 = 0; // Number of characters into the text2 string.
- // Start with text1 (prepatch_text) and apply the diffs until we arrive at
- // text2 (postpatch_text). We recreate the patches one by one to determine
- // context info.
- $prepatch_text = $text1;
- $postpatch_text = $text1;
- for ($x = 0; $x < count($diffs); $x++) {
- $diff_type = $diffs[$x][0];
- $diff_text = $diffs[$x][1];
-
- if (!$patchDiffLength && $diff_type !== DIFF_EQUAL) {
- // A new patch starts here.
- $patch->start1 = $char_count1;
- $patch->start2 = $char_count2;
- }
-
- switch ($diff_type) {
- case DIFF_INSERT :
- $patch->diffs[$patchDiffLength++] = $diffs[$x];
- $patch->length2 += mb_strlen($diff_text);
- $postpatch_text = mb_substr($postpatch_text, 0, $char_count2) . $diff_text . mb_substr($postpatch_text,$char_count2);
- break;
- case DIFF_DELETE :
- $patch->length1 += mb_strlen($diff_text);
- $patch->diffs[$patchDiffLength++] = $diffs[$x];
- $postpatch_text = mb_substr($postpatch_text, 0, $char_count2) . mb_substr($postpatch_text, $char_count2 + mb_strlen($diff_text) );
- break;
- case DIFF_EQUAL :
- if ( mb_strlen($diff_text) <= 2 * $this->Patch_Margin && $patchDiffLength && count($diffs) != $x + 1) {
- // Small equality inside a patch.
- $patch->diffs[$patchDiffLength++] = $diffs[$x];
- $patch->length1 += mb_strlen($diff_text);
- $patch->length2 += mb_strlen($diff_text);
- } elseif ( mb_strlen($diff_text) >= 2 * $this->Patch_Margin ) {
- // Time for a new patch.
- if ($patchDiffLength) {
- $this->patch_addContext($patch, $prepatch_text);
- array_push($patches,$patch);
- $patch = new patch_obj();
- $patchDiffLength = 0;
- // Unlike Unidiff, our patch lists have a rolling context.
- // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff
- // Update prepatch text & pos to reflect the application of the
- // just completed patch.
- $prepatch_text = $postpatch_text;
- $char_count1 = $char_count2;
- }
- }
- break;
- }
-
- // Update the current character count.
- if ($diff_type !== DIFF_INSERT) {
- $char_count1 += mb_strlen($diff_text);
- }
- if ($diff_type !== DIFF_DELETE) {
- $char_count2 += mb_strlen($diff_text);
- }
- }
- // Pick up the leftover patch if not empty.
- if ($patchDiffLength) {
- $this->patch_addContext($patch, $prepatch_text);
- array_push($patches, $patch);
- }
-
- return $patches;
- }
-
- /**
- * Given an array of patches, return another array that is identical.
- * @param {Array.<patch_obj>} patches Array of patch objects.
- * @return {Array.<patch_obj>} Array of patch objects.
- */
- function patch_deepCopy($patches) {
- // Making deep copies is hard in JavaScript.
- $patchesCopy = array();
- for ($x = 0; $x < count($patches); $x++) {
- $patch = $patches[$x];
- $patchCopy = new patch_obj();
- for ($y = 0; $y < count($patch->diffs); $y++) {
- $patchCopy->diffs[$y] = $patch->diffs[$y]; // ?? . slice();
- }
- $patchCopy->start1 = $patch->start1;
- $patchCopy->start2 = $patch->start2;
- $patchCopy->length1 = $patch->length1;
- $patchCopy->length2 = $patch->length2;
- $patchesCopy[$x] = $patchCopy;
- }
- return $patchesCopy;
- }
-
- /**
- * Merge a set of patches onto the text. Return a patched text, as well
- * as a list of true/false values indicating which patches were applied.
- * @param {Array.<patch_obj>} patches Array of patch objects.
- * @param {string} text Old text.
- * @return {Array.<string|Array.<boolean>>} Two element Array, containing the
- * new text and an array of boolean values.
- */
- function patch_apply($patches, $text) {
- if ( count($patches) == 0) {
- return array($text,array());
- }
-
- // Deep copy the patches so that no changes are made to originals.
- $patches = $this->patch_deepCopy($patches);
-
- $nullPadding = $this->patch_addPadding($patches);
- $text = $nullPadding . $text . $nullPadding;
-
- $this->patch_splitMax($patches);
- // delta keeps track of the offset between the expected and actual location
- // of the previous patch. If there are patches expected at positions 10 and
- // 20, but the first patch was found at 12, delta is 2 and the second patch
- // has an effective expected position of 22.
- $delta = 0;
- $results = array();
- for ($x = 0; $x < count($patches) ; $x++) {
- $expected_loc = $patches[$x]->start2 + $delta;
- $text1 = $this->diff_text1($patches[$x]->diffs);
- $start_loc = null;
- $end_loc = -1;
- if (mb_strlen($text1) > Match_MaxBits) {
- // patch_splitMax will only provide an oversized pattern in the case of
- // a monster delete.
- $start_loc = $this->match_main($text, mb_substr($text1, 0, Match_MaxBits ), $expected_loc);
- if ($start_loc != -1) {
- $end_loc = $this->match_main($text, mb_substr($text1,mb_strlen($text1) - Match_MaxBits), $expected_loc + mb_strlen($text1) - Match_MaxBits);
- if ($end_loc == -1 || $start_loc >= $end_loc) {
- // Can't find valid trailing context. Drop this patch.
- $start_loc = -1;
- }
- }
- } else {
- $start_loc = $this->match_main($text, $text1, $expected_loc);
- }
- if ($start_loc == -1) {
- // No match found. :(
- $results[$x] = false;
- // Subtract the delta for this failed patch from subsequent patches.
- $delta -= $patches[$x]->length2 - $patches[$x]->length1;
- } else {
- // Found a match. :)
- $results[$x] = true;
- $delta = $start_loc - $expected_loc;
- $text2 = null;
- if ($end_loc == -1) {
- $text2 = mb_substr($text, $start_loc, mb_strlen($text1) );
- } else {
- $text2 = mb_substr($text, $start_loc, $end_loc + Match_MaxBits - $start_loc);
- }
- if ($text1 == $text2) {
- // Perfect match, just shove the replacement text in.
- $text = mb_substr($text, 0, $start_loc) . $this->diff_text2($patches[$x]->diffs) . mb_substr($text,$start_loc + mb_strlen($text1) );
- } else {
- // Imperfect match. Run a diff to get a framework of equivalent
- // indices.
- $diffs = $this->diff_main($text1, $text2, false);
- if ( mb_strlen($text1) > Match_MaxBits && $this->diff_levenshtein($diffs) / mb_strlen($text1) > $this->Patch_DeleteThreshold) {
- // The end points match, but the content is unacceptably bad.
- $results[$x] = false;
- } else {
- $this->diff_cleanupSemanticLossless($diffs);
- $index1 = 0;
- $index2 = NULL;
- for ($y = 0; $y < count($patches[$x]->diffs); $y++) {
- $mod = $patches[$x]->diffs[$y];
- if ($mod[0] !== DIFF_EQUAL) {
- $index2 = $this->diff_xIndex($diffs, $index1);
- }
- if ($mod[0] === DIFF_INSERT) { // Insertion
- $text = mb_substr($text, 0, $start_loc + $index2) . $mod[1] . mb_substr($text, $start_loc + $index2);
- } elseif ($mod[0] === DIFF_DELETE) { // Deletion
- $text = mb_substr($text, 0, $start_loc + $index2) . mb_substr($text,$start_loc + $this->diff_xIndex($diffs, $index1 + mb_strlen($mod[1]) ));
- }
- if ($mod[0] !== DIFF_DELETE) {
- $index1 += mb_strlen($mod[1]);
- }
- }
- }
- }
- }
- }
- // Strip the padding off.
- $text = mb_substr($text, mb_strlen($nullPadding), mb_strlen($text) - 2*mb_strlen($nullPadding) );
- return array($text, $results);
- }
-
- /**
- * Add some padding on text start and end so that edges can match something.
- * Intended to be called only from within patch_apply.
- * @param {Array.<patch_obj>} patches Array of patch objects.
- * @return {string} The padding string added to each side.
- */
- function patch_addPadding(&$patches){
- $paddingLength = $this->Patch_Margin;
- $nullPadding = '';
- for ($x = 1; $x <= $paddingLength; $x++) {
- $nullPadding .= mb_chr($x);
- }
-
- // Bump all the patches forward.
- for ($x = 0; $x < count($patches); $x++) {
- $patches[$x]->start1 += $paddingLength;
- $patches[$x]->start2 += $paddingLength;
- }
-
- // Add some padding on start of first diff.
- $patch = &$patches[0];
- $diffs = &$patch->diffs;
- if (count($diffs) == 0 || $diffs[0][0] != DIFF_EQUAL) {
- // Add nullPadding equality.
- array_unshift($diffs, array(DIFF_EQUAL, $nullPadding));
- $patch->start1 -= $paddingLength; // Should be 0.
- $patch->start2 -= $paddingLength; // Should be 0.
- $patch->length1 += $paddingLength;
- $patch->length2 += $paddingLength;
- } elseif ($paddingLength > mb_strlen($diffs[0][1]) ) {
- // Grow first equality.
- $extraLength = $paddingLength - mb_strlen($diffs[0][1]);
- $diffs[0][1] = mb_substr( $nullPadding , mb_strlen($diffs[0][1]) ) . $diffs[0][1];
- $patch->start1 -= $extraLength;
- $patch->start2 -= $extraLength;
- $patch->length1 += $extraLength;
- $patch->length2 += $extraLength;
- }
-
- // Add some padding on end of last diff.
- $patch = &$patches[count($patches) - 1];
- $diffs = &$patch->diffs;
- if ( count($diffs) == 0 || $diffs[ count($diffs) - 1][0] != DIFF_EQUAL) {
- // Add nullPadding equality.
- array_push($diffs, array(DIFF_EQUAL, $nullPadding) );
- $patch->length1 += $paddingLength;
- $patch->length2 += $paddingLength;
- } elseif ($paddingLength > mb_strlen( $diffs[count($diffs)-1][1] ) ) {
- // Grow last equality.
- $extraLength = $paddingLength - mb_strlen( $diffs[count($diffs)-1][1] );
- $diffs[ count($diffs)-1][1] .= mb_substr($nullPadding,0,$extraLength);
- $patch->length1 += $extraLength;
- $patch->length2 += $extraLength;
- }
-
- return $nullPadding;
- }
-
- /**
- * Look through the patches and break up any which are longer than the maximum
- * limit of the match algorithm.
- * @param {Array.<patch_obj>} patches Array of patch objects.
- */
- function patch_splitMax(&$patches) {
- for ($x = 0; $x < count($patches); $x++) {
- if ( $patches[$x]->length1 > Match_MaxBits) {
- $bigpatch = $patches[$x];
- // Remove the big old patch.
- array_splice($patches,$x--,1);
- $patch_size = Match_MaxBits;
- $start1 = $bigpatch->start1;
- $start2 = $bigpatch->start2;
- $precontext = '';
- while ( count($bigpatch->diffs) !== 0) {
- // Create one of several smaller patches.
- $patch = new patch_obj();
- $empty = true;
- $patch->start1 = $start1 - mb_strlen($precontext);
- $patch->start2 = $start2 - mb_strlen($precontext);
- if ($precontext !== '') {
- $patch->length1 = $patch->length2 = mb_strlen($precontext);
- array_push($patch->diffs, array(DIFF_EQUAL, $precontext) );
- }
- while ( count($bigpatch->diffs) !== 0 && $patch->length1 < $patch_size - $this->Patch_Margin) {
- $diff_type = $bigpatch->diffs[0][0];
- $diff_text = $bigpatch->diffs[0][1];
- if ($diff_type === DIFF_INSERT) {
- // Insertions are harmless.
- $patch->length2 += mb_strlen($diff_text);
- $start2 += mb_strlen($diff_text);
- array_push($patch->diffs, array_shift($bigpatch->diffs) );
- $empty = false;
- } else
- if ($diff_type === DIFF_DELETE && count($patch->diffs) == 1 && $patch->diffs[0][0] == DIFF_EQUAL && (mb_strlen($diff_text) > 2 * $patch_size) ) {
- // This is a large deletion. Let it pass in one chunk.
- $patch->length1 += mb_strlen($diff_text);
- $start1 += mb_strlen($diff_text);
- $empty = false;
- array_push( $patch->diffs, array($diff_type, $diff_text) );
- array_shift($bigpatch->diffs);
- } else {
- // Deletion or equality. Only take as much as we can stomach.
- $diff_text = mb_substr($diff_text, 0, $patch_size - $patch->length1 - $this->Patch_Margin);
- $patch->length1 += mb_strlen($diff_text);
- $start1 += mb_strlen($diff_text);
- if ($diff_type === DIFF_EQUAL) {
- $patch->length2 += mb_strlen($diff_text);
- $start2 += mb_strlen($diff_text);
- } else {
- $empty = false;
- }
- array_push($patch->diffs, array($diff_type, $diff_text) );
- if ($diff_text == $bigpatch->diffs[0][1]) {
- array_shift($bigpatch->diffs);
- } else {
- $bigpatch->diffs[0][1] = mb_substr( $bigpatch->diffs[0][1],mb_strlen($diff_text) );
- }
- }
- }
- // Compute the head context for the next patch.
- $precontext = $this->diff_text2($patch->diffs);
- $precontext = mb_substr($precontext, mb_strlen($precontext)-$this->Patch_Margin);
- // Append the end context for this patch.
- $postcontext = mb_substr( $this->diff_text1($bigpatch->diffs), 0, $this->Patch_Margin );
- if ($postcontext !== '') {
- $patch->length1 += mb_strlen($postcontext);
- $patch->length2 += mb_strlen($postcontext);
- if ( count($patch->diffs) !== 0 && $patch->diffs[ count($patch->diffs) - 1][0] === DIFF_EQUAL) {
- $patch->diffs[ count($patch->diffs) - 1][1] .= $postcontext;
- } else {
- array_push($patch->diffs, array(DIFF_EQUAL, $postcontext));
- }
- }
- if (!$empty) {
- array_splice($patches, ++$x, 0, array($patch));
- }
- }
- }
- }
- }
-
- /**
- * Take a list of patches and return a textual representation.
- * @param {Array.<patch_obj>} patches Array of patch objects.
- * @return {string} Text representation of patches.
- */
- function patch_toText($patches) {
- $text = array();
- for ($x = 0; $x < count($patches) ; $x++) {
- $text[$x] = $patches[$x];
- }
- return implode('',$text);
- }
-
- /**
- * Parse a textual representation of patches and return a list of patch objects.
- * @param {string} textline Text representation of patches.
- * @return {Array.<patch_obj>} Array of patch objects.
- * @throws {Error} If invalid input.
- */
- function patch_fromText($textline) {
- $patches = array();
- if ($textline === '') {
- return $patches;
- }
- $text = explode("\n",$textline);
- foreach($text as $i=>$t){ if($t===''){ unset($text[$i]); } }
- $textPointer = 0;
- while ($textPointer < count($text) ) {
- $m = null;
- preg_match('/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/',$text[$textPointer],$m);
- if (!$m) {
- echo_Exception('Invalid patch string: ' . $text[$textPointer]);
- }
- $patch = new patch_obj();
- array_push($patches, $patch);
- @$patch->start1 = (int)$m[1];
- if (@$m[2] === '') {
- $patch->start1--;
- $patch->length1 = 1;
- } elseif ( @$m[2] == '0') {
- $patch->length1 = 0;
- } else {
- $patch->start1--;
- @$patch->length1 = (int)$m[2];
- }
-
- @$patch->start2 = (int)$m[3];
- if (@$m[4] === '') {
- $patch->start2--;
- $patch->length2 = 1;
- } elseif ( @$m[4] == '0') {
- $patch->length2 = 0;
- } else {
- $patch->start2--;
- @$patch->length2 = (int)$m[4];
- }
- $textPointer++;
-
- while ($textPointer < count($text) ) {
- $sign = $text[$textPointer][0];
- try {
- $line = decodeURI( mb_substr($text[$textPointer],1) );
- } catch (Exception $ex) {
- // Malformed URI sequence.
- throw new Exception('Illegal escape in patch_fromText: ' . $line);
- }
- if ($sign == '-') {
- // Deletion.
- array_push( $patch->diffs, array(DIFF_DELETE, $line) );
- } elseif ($sign == '+') {
- // Insertion.
- array_push($patch->diffs, array(DIFF_INSERT, $line) );
- } elseif ($sign == ' ') {
- // Minor equality.
- array_push($patch->diffs, array(DIFF_EQUAL, $line) );
- } elseif ($sign == '@') {
- // Start of next patch.
- break;
- } elseif ($sign === '') {
- // Blank line? Whatever.
- } else {
- // WTF?
- echo_Exception('Invalid patch mode "' . $sign . '" in: ' . $line);
- }
- $textPointer++;
- }
- }
- return $patches;
- }
-}
-
-/**
- * Class representing one patch operation.
- * @constructor
- */
-class patch_obj {
- /** @type {Array.<Array.<number|string>>} */
- public $diffs = array();
- /** @type {number?} */
- public $start1 = null;
- /** @type {number?} */
- public $start2 = null;
- /** @type {number} */
- public $length1 = 0;
- /** @type {number} */
- public $length2 = 0;
-
- /**
- * Emmulate GNU diff's format.
- * Header: @@ -382,8 +481,9 @@
- * Indicies are printed as 1-based, not 0-based.
- * @return {string} The GNU diff string.
- */
- function toString() {
- if ($this->length1 === 0) {
- $coords1 = $this->start1 . ',0';
- } elseif ($this->length1 == 1) {
- $coords1 = $this->start1 + 1;
- } else {
- $coords1 = ($this->start1 + 1) . ',' . $this->length1;
- }
- if ($this->length2 === 0) {
- $coords2 = $this->start2 . ',0';
- } elseif ($this->length2 == 1) {
- $coords2 = $this->start2 + 1;
- } else {
- $coords2 = ($this->start2 + 1) . ',' . $this->length2;
- }
- $text = array ( '@@ -' . $coords1 . ' +' . $coords2 . " @@\n" );
-
- // Escape the body of the patch with %xx notation.
- for ($x = 0; $x < count($this->diffs); $x++) {
- switch ($this->diffs[$x][0]) {
- case DIFF_INSERT :
- $op = '+';
- break;
- case DIFF_DELETE :
- $op = '-';
- break;
- case DIFF_EQUAL :
- $op = ' ';
- break;
- }
- $text[$x +1] = $op . encodeURI($this->diffs[$x][1]) . "\n";
- }
- return str_replace('%20', ' ', implode('',$text));
- }
- function __toString(){
- return $this->toString();
- }
-}
-
-define('DIFF_DELETE', -1);
-define('DIFF_INSERT', 1);
-define('DIFF_EQUAL', 0);
-
-define('Match_MaxBits', PHP_INT_SIZE * 8);
-
-
-function charCodeAt($str, $pos) {
- return mb_ord(mb_substr($str, $pos, 1));
-}
-function mb_ord($v) {
- $k = mb_convert_encoding($v, 'UCS-2LE', 'UTF-8');
- $k1 = ord(substr($k, 0, 1));
- $k2 = ord(substr($k, 1, 1));
- return $k2 * 256 + $k1;
-}
-function mb_chr($num){
- return mb_convert_encoding('&#'.intval($num).';', 'UTF-8', 'HTML-ENTITIES');
-}
-
-/**
- * as in javascript encodeURI() following the MDN description
- *
- * @link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURI
- * @param $url
- * @return string
- */
-function encodeURI($url) {
- return strtr(rawurlencode($url), array (
- '%3B' => ';', '%2C' => ',', '%2F' => '/', '%3F' => '?', '%3A' => ':', '%40' => '@', '%26' => '&', '%3D' => '=',
- '%2B' => '+', '%24' => '$', '%21' => '!', '%2A' => '*', '%27' => '\'', '%28' => '(', '%29' => ')', '%23' => '#',
- ));
-}
-
-function decodeURI($encoded) {
- static $dontDecode;
- if (!$dontDecode) {
- $table = array (
- '%3B' => ';', '%2C' => ',', '%2F' => '/', '%3F' => '?', '%3A' => ':', '%40' => '@', '%26' => '&', '%3D' => '=',
- '%2B' => '+', '%24' => '$', '%21' => '!', '%2A' => '*', '%27' => '\'', '%28' => '(', '%29' => ')', '%23' => '#',
- );
- $dontDecode = array();
- foreach ($table as $k => $v) {
- $dontDecode[$k] = encodeURI($k);
- }
- }
- return rawurldecode(strtr($encoded, $dontDecode));
-}
-
-function echo_Exception($str){
- global $lastException;
- $lastException = $str;
- echo $str;
-}
-//mb_internal_encoding("UTF-8");
-
-?>
\ No newline at end of file
diff --git a/resources/celerity/map.php b/resources/celerity/map.php
index 61a349bcad..c29df70b1e 100644
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -1,2477 +1,2443 @@
<?php
/**
* This file is automatically generated. Use 'bin/celerity map' to rebuild it.
*
* @generated
*/
return array(
'names' => array(
'conpherence.pkg.css' => '0e3cf785',
'conpherence.pkg.js' => '020aebcf',
- 'core.pkg.css' => '00a2e7f4',
+ 'core.pkg.css' => 'b816811e',
'core.pkg.js' => 'd2de90d9',
'dark-console.pkg.js' => '187792c2',
'differential.pkg.css' => 'ffb69e3d',
- 'differential.pkg.js' => '8deec4cd',
+ 'differential.pkg.js' => 'c60bec1b',
'diffusion.pkg.css' => '42c75c37',
'diffusion.pkg.js' => '78c9885d',
'maniphest.pkg.css' => '35995d6d',
'maniphest.pkg.js' => 'c9308721',
'rsrc/audio/basic/alert.mp3' => '17889334',
'rsrc/audio/basic/bing.mp3' => 'a817a0c3',
'rsrc/audio/basic/pock.mp3' => '0fa843d0',
'rsrc/audio/basic/tap.mp3' => '02d16994',
'rsrc/audio/basic/ting.mp3' => 'a6b6540e',
'rsrc/css/aphront/aphront-bars.css' => '4a327b4a',
'rsrc/css/aphront/dark-console.css' => '7f06cda2',
'rsrc/css/aphront/dialog-view.css' => '6f4ea703',
'rsrc/css/aphront/list-filter-view.css' => 'feb64255',
'rsrc/css/aphront/multi-column.css' => 'fbc00ba3',
'rsrc/css/aphront/notification.css' => '30240bd2',
'rsrc/css/aphront/panel-view.css' => '46923d46',
'rsrc/css/aphront/phabricator-nav-view.css' => '423f92cc',
'rsrc/css/aphront/table-view.css' => '0bb61df1',
'rsrc/css/aphront/tokenizer.css' => '34e2a838',
'rsrc/css/aphront/tooltip.css' => 'e3f2412f',
'rsrc/css/aphront/typeahead-browse.css' => 'b7ed02d2',
'rsrc/css/aphront/typeahead.css' => '8779483d',
'rsrc/css/application/almanac/almanac.css' => '2e050f4f',
'rsrc/css/application/auth/auth.css' => 'c2f23d74',
'rsrc/css/application/base/main-menu-view.css' => 'bcec20f0',
'rsrc/css/application/base/notification-menu.css' => '4df1ee30',
'rsrc/css/application/base/phui-theme.css' => '35883b37',
'rsrc/css/application/base/standard-page-view.css' => 'a374f94c',
'rsrc/css/application/chatlog/chatlog.css' => 'abdc76ee',
'rsrc/css/application/conduit/conduit-api.css' => 'ce2cfc41',
'rsrc/css/application/config/config-options.css' => '16c920ae',
'rsrc/css/application/config/config-template.css' => '20babf50',
'rsrc/css/application/config/setup-issue.css' => '5eed85b2',
'rsrc/css/application/config/unhandled-exception.css' => '9ecfc00d',
'rsrc/css/application/conpherence/color.css' => 'b17746b0',
'rsrc/css/application/conpherence/durable-column.css' => '2d57072b',
'rsrc/css/application/conpherence/header-pane.css' => 'c9a3db8e',
'rsrc/css/application/conpherence/menu.css' => '67f4680d',
'rsrc/css/application/conpherence/message-pane.css' => 'd244db1e',
'rsrc/css/application/conpherence/notification.css' => '6a3d4e58',
'rsrc/css/application/conpherence/participant-pane.css' => '69e0058a',
'rsrc/css/application/conpherence/transaction.css' => '3a3f5e7e',
'rsrc/css/application/contentsource/content-source-view.css' => 'cdf0d579',
'rsrc/css/application/countdown/timer.css' => 'bff8012f',
'rsrc/css/application/daemon/bulk-job.css' => '73af99f5',
'rsrc/css/application/dashboard/dashboard.css' => '5a205b9d',
'rsrc/css/application/diff/diff-tree-view.css' => 'e2d3e222',
'rsrc/css/application/diff/inline-comment-summary.css' => '81eb368d',
'rsrc/css/application/differential/add-comment.css' => '7e5900d9',
'rsrc/css/application/differential/changeset-view.css' => '60c3d405',
'rsrc/css/application/differential/core.css' => '7300a73e',
'rsrc/css/application/differential/phui-inline-comment.css' => '9863a85e',
'rsrc/css/application/differential/revision-comment.css' => '7dbc8d1d',
'rsrc/css/application/differential/revision-history.css' => '237a2979',
'rsrc/css/application/differential/revision-list.css' => '93d2df7d',
'rsrc/css/application/differential/table-of-contents.css' => 'bba788b9',
'rsrc/css/application/diffusion/diffusion-icons.css' => '23b31a1b',
'rsrc/css/application/diffusion/diffusion-readme.css' => 'b68a76e4',
'rsrc/css/application/diffusion/diffusion-repository.css' => 'b89e8c6c',
'rsrc/css/application/diffusion/diffusion.css' => 'e46232d6',
'rsrc/css/application/feed/feed.css' => 'd8b6e3f8',
'rsrc/css/application/files/global-drag-and-drop.css' => '1d2713a4',
'rsrc/css/application/flag/flag.css' => '2b77be8d',
'rsrc/css/application/harbormaster/harbormaster.css' => '8dfe16b2',
'rsrc/css/application/herald/herald-test.css' => '7e7bbdae',
'rsrc/css/application/herald/herald.css' => '648d39e2',
'rsrc/css/application/maniphest/report.css' => '3d53188b',
'rsrc/css/application/maniphest/task-edit.css' => '272daa84',
'rsrc/css/application/maniphest/task-summary.css' => '61d1667e',
'rsrc/css/application/objectselector/object-selector.css' => 'ee77366f',
'rsrc/css/application/owners/owners-path-editor.css' => 'fa7c13ef',
'rsrc/css/application/paste/paste.css' => 'b37bcd38',
'rsrc/css/application/people/people-picture-menu-item.css' => 'fe8e07cf',
'rsrc/css/application/people/people-profile.css' => '2ea2daa1',
'rsrc/css/application/phame/phame.css' => 'bb442327',
'rsrc/css/application/pholio/pholio-edit.css' => '4df55b3b',
'rsrc/css/application/pholio/pholio-inline-comments.css' => '722b48c2',
'rsrc/css/application/pholio/pholio.css' => '88ef5ef1',
'rsrc/css/application/phortune/phortune-credit-card-form.css' => '3b9868a8',
'rsrc/css/application/phortune/phortune-invoice.css' => '4436b241',
'rsrc/css/application/phortune/phortune.css' => '508a1a5e',
'rsrc/css/application/phrequent/phrequent.css' => 'bd79cc67',
'rsrc/css/application/phriction/phriction-document-css.css' => '03380da0',
'rsrc/css/application/policy/policy-edit.css' => '8794e2ed',
'rsrc/css/application/policy/policy-transaction-detail.css' => 'c02b8384',
'rsrc/css/application/policy/policy.css' => 'ceb56a08',
'rsrc/css/application/ponder/ponder-view.css' => '05a09d0a',
'rsrc/css/application/project/project-card-view.css' => 'a9f2c2dd',
'rsrc/css/application/project/project-triggers.css' => 'cd9c8bb9',
'rsrc/css/application/project/project-view.css' => '567858b3',
- 'rsrc/css/application/releeph/releeph-core.css' => 'f81ff2db',
- 'rsrc/css/application/releeph/releeph-preview-branch.css' => '22db5c07',
- 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '0ac1ea31',
- 'rsrc/css/application/releeph/releeph-request-typeahead.css' => 'bce37359',
'rsrc/css/application/search/application-search-view.css' => '0f7c06d8',
'rsrc/css/application/search/search-results.css' => '9ea70ace',
'rsrc/css/application/slowvote/slowvote.css' => '1694baed',
'rsrc/css/application/tokens/tokens.css' => 'ce5a50bd',
'rsrc/css/application/uiexample/example.css' => 'b4795059',
'rsrc/css/core/core.css' => 'b3ebd90d',
'rsrc/css/core/remarkup.css' => '5baa3bd9',
'rsrc/css/core/syntax.css' => '548567f6',
'rsrc/css/core/z-index.css' => 'ac3bfcd4',
'rsrc/css/diviner/diviner-shared.css' => '4bd263b0',
'rsrc/css/font/font-awesome.css' => '3883938a',
'rsrc/css/font/font-lato.css' => '23631304',
'rsrc/css/font/phui-font-icon-base.css' => '303c9b87',
'rsrc/css/fuel/fuel-grid.css' => '66697240',
'rsrc/css/fuel/fuel-handle-list.css' => '2c4cbeca',
'rsrc/css/fuel/fuel-map.css' => 'd6e31510',
'rsrc/css/fuel/fuel-menu.css' => '21f5d199',
'rsrc/css/layout/phabricator-source-code-view.css' => '03d7ac28',
'rsrc/css/phui/button/phui-button-bar.css' => 'a4aa75c4',
'rsrc/css/phui/button/phui-button-simple.css' => '1ff278aa',
'rsrc/css/phui/button/phui-button.css' => 'ea704902',
'rsrc/css/phui/calendar/phui-calendar-day.css' => '9597d706',
'rsrc/css/phui/calendar/phui-calendar-list.css' => 'ccd7e4e2',
'rsrc/css/phui/calendar/phui-calendar-month.css' => 'cb758c42',
'rsrc/css/phui/calendar/phui-calendar.css' => 'f11073aa',
'rsrc/css/phui/object-item/phui-oi-big-ui.css' => 'fa74cc35',
'rsrc/css/phui/object-item/phui-oi-color.css' => 'b517bfa0',
'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'da15d3dc',
'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '490e2e2e',
'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'af98a277',
'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => '6a30fa46',
'rsrc/css/phui/phui-action-list.css' => '1b0085b2',
'rsrc/css/phui/phui-action-panel.css' => '6c386cbf',
'rsrc/css/phui/phui-badge.css' => '666e25ad',
'rsrc/css/phui/phui-basic-nav-view.css' => '56ebd66d',
'rsrc/css/phui/phui-big-info-view.css' => '362ad37b',
'rsrc/css/phui/phui-box.css' => '5ed3b8cb',
'rsrc/css/phui/phui-bulk-editor.css' => '374d5e30',
'rsrc/css/phui/phui-chart.css' => '14df9ae3',
'rsrc/css/phui/phui-cms.css' => '8c05c41e',
'rsrc/css/phui/phui-comment-form.css' => '68a2d99a',
'rsrc/css/phui/phui-comment-panel.css' => 'ec4e31c0',
'rsrc/css/phui/phui-crumbs-view.css' => '614f43cf',
- 'rsrc/css/phui/phui-curtain-object-ref-view.css' => '5f752bdb',
+ 'rsrc/css/phui/phui-curtain-object-ref-view.css' => '51d93266',
'rsrc/css/phui/phui-curtain-view.css' => '68c5efb6',
'rsrc/css/phui/phui-document-pro.css' => 'b9613a10',
'rsrc/css/phui/phui-document-summary.css' => 'b068eed1',
'rsrc/css/phui/phui-document.css' => '52b748a5',
'rsrc/css/phui/phui-feed-story.css' => 'a0c05029',
'rsrc/css/phui/phui-fontkit.css' => '1ec937e5',
'rsrc/css/phui/phui-form-view.css' => '01b796c0',
'rsrc/css/phui/phui-form.css' => '1f177cb7',
'rsrc/css/phui/phui-formation-view.css' => 'd2dec8ed',
'rsrc/css/phui/phui-head-thing.css' => 'd7f293df',
'rsrc/css/phui/phui-header-view.css' => '36c86a58',
'rsrc/css/phui/phui-hovercard.css' => '6ca90fa0',
'rsrc/css/phui/phui-icon-set-selector.css' => '7aa5f3ec',
'rsrc/css/phui/phui-icon.css' => '4cbc684a',
'rsrc/css/phui/phui-image-mask.css' => '62c7f4d2',
'rsrc/css/phui/phui-info-view.css' => 'a10a909b',
'rsrc/css/phui/phui-invisible-character-view.css' => 'c694c4a4',
'rsrc/css/phui/phui-left-right.css' => '68513c34',
'rsrc/css/phui/phui-lightbox.css' => '4ebf22da',
'rsrc/css/phui/phui-list.css' => '0c04affd',
'rsrc/css/phui/phui-object-box.css' => 'b8d7eea0',
'rsrc/css/phui/phui-pager.css' => 'd022c7ad',
'rsrc/css/phui/phui-pinboard-view.css' => '1f08f5d8',
'rsrc/css/phui/phui-policy-section-view.css' => '139fdc64',
'rsrc/css/phui/phui-property-list-view.css' => '5adf7078',
'rsrc/css/phui/phui-remarkup-preview.css' => '91767007',
'rsrc/css/phui/phui-segment-bar-view.css' => '5166b370',
'rsrc/css/phui/phui-spacing.css' => 'b05cadc3',
'rsrc/css/phui/phui-status.css' => '293b5dad',
'rsrc/css/phui/phui-tag-view.css' => 'fb811341',
'rsrc/css/phui/phui-timeline-view.css' => '2d32d7a9',
'rsrc/css/phui/phui-two-column-view.css' => 'f96d319f',
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'e86de308',
'rsrc/css/phui/workboards/phui-workboard.css' => '74fc9d98',
'rsrc/css/phui/workboards/phui-workcard.css' => '913441b6',
'rsrc/css/phui/workboards/phui-workpanel.css' => '3ae89b20',
'rsrc/css/sprite-login.css' => '18b368a6',
'rsrc/css/sprite-tokens.css' => 'f1896dc5',
'rsrc/css/syntax/syntax-default.css' => '055fc231',
'rsrc/externals/d3/d3.min.js' => '9d068042',
'rsrc/externals/font/fontawesome/fontawesome-webfont.eot' => '23f8c698',
'rsrc/externals/font/fontawesome/fontawesome-webfont.ttf' => '70983df0',
'rsrc/externals/font/fontawesome/fontawesome-webfont.woff' => 'cd02f93b',
'rsrc/externals/font/fontawesome/fontawesome-webfont.woff2' => '351fd46a',
'rsrc/externals/font/lato/lato-bold.eot' => '7367aa5e',
'rsrc/externals/font/lato/lato-bold.svg' => '681aa4f5',
'rsrc/externals/font/lato/lato-bold.ttf' => '66d3c296',
'rsrc/externals/font/lato/lato-bold.woff' => '89d9fba7',
'rsrc/externals/font/lato/lato-bold.woff2' => '389fcdb1',
'rsrc/externals/font/lato/lato-bolditalic.eot' => '03eeb4da',
'rsrc/externals/font/lato/lato-bolditalic.svg' => 'f56fa11c',
'rsrc/externals/font/lato/lato-bolditalic.ttf' => '9c3aec21',
'rsrc/externals/font/lato/lato-bolditalic.woff' => 'bfbd0616',
'rsrc/externals/font/lato/lato-bolditalic.woff2' => 'bc7d1274',
'rsrc/externals/font/lato/lato-italic.eot' => '7db5b247',
'rsrc/externals/font/lato/lato-italic.svg' => 'b1ae496f',
'rsrc/externals/font/lato/lato-italic.ttf' => '43eed813',
'rsrc/externals/font/lato/lato-italic.woff' => 'c28975e1',
'rsrc/externals/font/lato/lato-italic.woff2' => 'fffc0d8c',
'rsrc/externals/font/lato/lato-regular.eot' => '06e0c291',
'rsrc/externals/font/lato/lato-regular.svg' => '3ad95f53',
'rsrc/externals/font/lato/lato-regular.ttf' => 'e2e9c398',
'rsrc/externals/font/lato/lato-regular.woff' => '0b13d332',
'rsrc/externals/font/lato/lato-regular.woff2' => '8f846797',
'rsrc/externals/javelin/core/Event.js' => 'c03f2fb4',
'rsrc/externals/javelin/core/Stratcom.js' => '0889b835',
'rsrc/externals/javelin/core/__tests__/event-stop-and-kill.js' => '048472d2',
'rsrc/externals/javelin/core/__tests__/install.js' => '14a7e671',
'rsrc/externals/javelin/core/__tests__/stratcom.js' => 'a28464bb',
'rsrc/externals/javelin/core/__tests__/util.js' => 'e29a4354',
'rsrc/externals/javelin/core/init.js' => '98e6504a',
'rsrc/externals/javelin/core/init_node.js' => '16961339',
'rsrc/externals/javelin/core/install.js' => '5902260c',
'rsrc/externals/javelin/core/util.js' => 'edb4d8c9',
'rsrc/externals/javelin/docs/Base.js' => '5a401d7d',
'rsrc/externals/javelin/docs/onload.js' => 'ee58fb62',
'rsrc/externals/javelin/ext/fx/Color.js' => '78f811c9',
'rsrc/externals/javelin/ext/fx/FX.js' => '34450586',
'rsrc/externals/javelin/ext/reactor/core/DynVal.js' => '202a2e85',
'rsrc/externals/javelin/ext/reactor/core/Reactor.js' => '1c850a26',
'rsrc/externals/javelin/ext/reactor/core/ReactorNode.js' => '72960bc1',
'rsrc/externals/javelin/ext/reactor/core/ReactorNodeCalmer.js' => '225bbb98',
'rsrc/externals/javelin/ext/reactor/dom/RDOM.js' => '6cfa0008',
'rsrc/externals/javelin/ext/view/HTMLView.js' => 'f8c4e135',
'rsrc/externals/javelin/ext/view/View.js' => '289bf236',
'rsrc/externals/javelin/ext/view/ViewInterpreter.js' => '876506b6',
'rsrc/externals/javelin/ext/view/ViewPlaceholder.js' => 'a9942052',
'rsrc/externals/javelin/ext/view/ViewRenderer.js' => '9aae2b66',
'rsrc/externals/javelin/ext/view/ViewVisitor.js' => '308f9fe4',
'rsrc/externals/javelin/ext/view/__tests__/HTMLView.js' => '6e50a13f',
'rsrc/externals/javelin/ext/view/__tests__/View.js' => 'd284be5d',
'rsrc/externals/javelin/ext/view/__tests__/ViewInterpreter.js' => 'a9f35511',
'rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js' => '3a1b81f6',
'rsrc/externals/javelin/lib/Cookie.js' => '05d290ef',
'rsrc/externals/javelin/lib/DOM.js' => 'e4c7622a',
'rsrc/externals/javelin/lib/History.js' => '030b4f7a',
'rsrc/externals/javelin/lib/JSON.js' => '541f81c3',
'rsrc/externals/javelin/lib/Leader.js' => '0d2490ce',
'rsrc/externals/javelin/lib/Mask.js' => '7c4d8998',
'rsrc/externals/javelin/lib/Quicksand.js' => 'd3799cb4',
'rsrc/externals/javelin/lib/Request.js' => '84e6891f',
'rsrc/externals/javelin/lib/Resource.js' => '20514cc2',
'rsrc/externals/javelin/lib/Routable.js' => '6a18c42e',
'rsrc/externals/javelin/lib/Router.js' => '32755edb',
'rsrc/externals/javelin/lib/Scrollbar.js' => 'a43ae2ae',
'rsrc/externals/javelin/lib/Sound.js' => 'd4cc2d2a',
'rsrc/externals/javelin/lib/URI.js' => '2e255291',
'rsrc/externals/javelin/lib/Vector.js' => 'e9c80beb',
'rsrc/externals/javelin/lib/WebSocket.js' => 'fdc13e4e',
'rsrc/externals/javelin/lib/Workflow.js' => '945ff654',
'rsrc/externals/javelin/lib/__tests__/Cookie.js' => 'ca686f71',
'rsrc/externals/javelin/lib/__tests__/DOM.js' => '4566e249',
'rsrc/externals/javelin/lib/__tests__/JSON.js' => '710377ae',
'rsrc/externals/javelin/lib/__tests__/URI.js' => '6fff0c2b',
'rsrc/externals/javelin/lib/__tests__/behavior.js' => '8426ebeb',
'rsrc/externals/javelin/lib/behavior.js' => '1b6acc2a',
'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => '89a1ae3a',
'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => 'a4356cde',
'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => 'a241536a',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '22ee68a5',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '23387297',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '5a79f6c3',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => '8badee71',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '80bff3af',
'rsrc/favicons/favicon-16x16.png' => '4c51a03a',
'rsrc/favicons/mask-icon.svg' => 'db699fe1',
'rsrc/image/BFCFDA.png' => '74b5c88b',
'rsrc/image/actions/edit.png' => 'fd987dff',
'rsrc/image/avatar.png' => '0d17c6c4',
'rsrc/image/checker_dark.png' => '7fc8fa7b',
'rsrc/image/checker_light.png' => '3157a202',
'rsrc/image/checker_lighter.png' => 'c45928c1',
'rsrc/image/chevron-in.png' => '1aa2f88f',
'rsrc/image/chevron-out.png' => 'c815e272',
'rsrc/image/controls/checkbox-checked.png' => '1770d7a0',
'rsrc/image/controls/checkbox-unchecked.png' => 'e1deba0a',
'rsrc/image/d5d8e1.png' => '6764616e',
'rsrc/image/darkload.gif' => '5bd41a89',
'rsrc/image/divot.png' => '0fbe2453',
'rsrc/image/examples/hero.png' => '5d8c4b21',
'rsrc/image/grippy_texture.png' => 'a7d222b5',
'rsrc/image/icon/fatcow/arrow_branch.png' => '98149d9f',
'rsrc/image/icon/fatcow/arrow_merge.png' => 'e142f4f8',
'rsrc/image/icon/fatcow/calendar_edit.png' => '5ff44a08',
'rsrc/image/icon/fatcow/document_black.png' => 'd3515fa5',
'rsrc/image/icon/fatcow/flag_blue.png' => '54db2e5c',
'rsrc/image/icon/fatcow/flag_finish.png' => '2953a51b',
'rsrc/image/icon/fatcow/flag_ghost.png' => '7d9ada92',
'rsrc/image/icon/fatcow/flag_green.png' => '010f7161',
'rsrc/image/icon/fatcow/flag_orange.png' => '6c384ca5',
'rsrc/image/icon/fatcow/flag_pink.png' => '11ac6b12',
'rsrc/image/icon/fatcow/flag_purple.png' => 'c4f423a4',
'rsrc/image/icon/fatcow/flag_red.png' => '9e6d8817',
'rsrc/image/icon/fatcow/flag_yellow.png' => '906733f4',
'rsrc/image/icon/fatcow/key_question.png' => 'c10c26db',
'rsrc/image/icon/fatcow/link.png' => '8edbf327',
'rsrc/image/icon/fatcow/page_white_edit.png' => '17ef5625',
'rsrc/image/icon/fatcow/page_white_put.png' => '82430c91',
'rsrc/image/icon/fatcow/source/conduit.png' => '5b55130c',
'rsrc/image/icon/fatcow/source/email.png' => '8a32b77f',
'rsrc/image/icon/fatcow/source/fax.png' => '8bc2a49b',
'rsrc/image/icon/fatcow/source/mobile.png' => '0a918412',
'rsrc/image/icon/fatcow/source/tablet.png' => 'fc50b050',
'rsrc/image/icon/fatcow/source/web.png' => '70433af3',
'rsrc/image/icon/subscribe.png' => '07ef454e',
'rsrc/image/icon/tango/attachment.png' => 'bac9032d',
'rsrc/image/icon/tango/edit.png' => 'e6296206',
'rsrc/image/icon/tango/go-down.png' => '0b903712',
'rsrc/image/icon/tango/log.png' => '86b6a6f4',
'rsrc/image/icon/tango/upload.png' => '3fe6b92d',
'rsrc/image/icon/unsubscribe.png' => 'db04378a',
'rsrc/image/lightblue-header.png' => 'e6d483c6',
'rsrc/image/logo/light-eye.png' => '72337472',
'rsrc/image/main_texture.png' => '894d03c4',
'rsrc/image/menu_texture.png' => '896c9ade',
'rsrc/image/people/harding.png' => '95b2db63',
'rsrc/image/people/jefferson.png' => 'e883a3a2',
'rsrc/image/people/lincoln.png' => 'be2c07c5',
'rsrc/image/people/mckinley.png' => '6af510a0',
'rsrc/image/people/taft.png' => 'b15ab07e',
'rsrc/image/people/user0.png' => '4bc64b40',
'rsrc/image/people/user1.png' => '8063f445',
'rsrc/image/people/user2.png' => 'd28246c0',
'rsrc/image/people/user3.png' => 'fb1ac12d',
'rsrc/image/people/user4.png' => 'fe4fac8f',
'rsrc/image/people/user5.png' => '3d07065c',
'rsrc/image/people/user6.png' => 'e4bd47c8',
'rsrc/image/people/user7.png' => '71d8fe8b',
'rsrc/image/people/user8.png' => '85f86bf7',
'rsrc/image/people/user9.png' => '523db8aa',
'rsrc/image/people/washington.png' => '86159e68',
'rsrc/image/phrequent_active.png' => 'de66dc50',
'rsrc/image/phrequent_inactive.png' => '79c61baf',
'rsrc/image/resize.png' => '9cc83373',
'rsrc/image/sprite-login-X2.png' => '604545f6',
'rsrc/image/sprite-login.png' => '7a001a9a',
'rsrc/image/sprite-tokens-X2.png' => '21621dd9',
'rsrc/image/sprite-tokens.png' => 'bede2580',
'rsrc/image/texture/card-gradient.png' => 'e6892cb4',
'rsrc/image/texture/dark-menu-hover.png' => '390a4fa1',
'rsrc/image/texture/dark-menu.png' => '542f699c',
'rsrc/image/texture/grip.png' => 'bc80753a',
'rsrc/image/texture/panel-header-gradient.png' => '65004dbf',
'rsrc/image/texture/phlnx-bg.png' => '6c9cd31d',
'rsrc/image/texture/pholio-background.gif' => '84910bfc',
'rsrc/image/texture/table_header.png' => '7652d1ad',
'rsrc/image/texture/table_header_hover.png' => '12ea5236',
'rsrc/image/texture/table_header_tall.png' => '5cc420c4',
'rsrc/js/application/aphlict/Aphlict.js' => '022516b4',
'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'e9a2940f',
'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '4e61fa88',
'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'c3703a16',
'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => '070679fe',
'rsrc/js/application/calendar/behavior-day-view.js' => '727a5a61',
'rsrc/js/application/calendar/behavior-event-all-day.js' => '0b1bc990',
'rsrc/js/application/calendar/behavior-month-view.js' => '158c64e0',
'rsrc/js/application/config/behavior-reorder-fields.js' => '2539f834',
'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'aec8e38c',
'rsrc/js/application/conpherence/behavior-conpherence-search.js' => '91befbcc',
'rsrc/js/application/conpherence/behavior-durable-column.js' => 'fa6f30b2',
'rsrc/js/application/conpherence/behavior-menu.js' => '8c2ed2bf',
'rsrc/js/application/conpherence/behavior-participant-pane.js' => '43ba89a2',
'rsrc/js/application/conpherence/behavior-pontificate.js' => '4ae58b5a',
'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '5a6f6a06',
'rsrc/js/application/conpherence/behavior-toggle-widget.js' => '8f959ad0',
'rsrc/js/application/countdown/timer.js' => '6a162524',
'rsrc/js/application/daemon/behavior-bulk-job-reload.js' => '3829a3cf',
'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '9c01e364',
'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => 'a2ab19be',
'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '1e413dc9',
'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '0116d3e8',
'rsrc/js/application/diff/DiffChangeset.js' => 'd7d3ba75',
'rsrc/js/application/diff/DiffChangesetList.js' => 'cc2c5de5',
'rsrc/js/application/diff/DiffInline.js' => '9c775532',
'rsrc/js/application/diff/DiffInlineContentState.js' => 'aa51efb4',
'rsrc/js/application/diff/DiffPathView.js' => '8207abf9',
'rsrc/js/application/diff/DiffTreeView.js' => '5d83623b',
'rsrc/js/application/differential/behavior-diff-radios.js' => '925fe8cd',
'rsrc/js/application/differential/behavior-populate.js' => 'b86ef6c2',
'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => '94243d89',
'rsrc/js/application/diffusion/ExternalEditorLinkEngine.js' => '48a8641f',
'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'b7b73831',
'rsrc/js/application/diffusion/behavior-commit-branches.js' => '4b671572',
'rsrc/js/application/diffusion/behavior-commit-graph.js' => 'ac10c917',
'rsrc/js/application/diffusion/behavior-locate-file.js' => '87428eb2',
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123',
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a',
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '47a0728b',
'rsrc/js/application/fact/Chart.js' => '52e3ff03',
'rsrc/js/application/fact/ChartCurtainView.js' => '86954222',
'rsrc/js/application/fact/ChartFunctionLabel.js' => '81de1dab',
'rsrc/js/application/files/behavior-document-engine.js' => '243d6c22',
'rsrc/js/application/files/behavior-icon-composer.js' => '38a6cedb',
'rsrc/js/application/files/behavior-launch-icon-composer.js' => 'a17b84f1',
'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => 'b347a301',
'rsrc/js/application/herald/HeraldRuleEditor.js' => '2633bef7',
'rsrc/js/application/herald/PathTypeahead.js' => 'ad486db3',
'rsrc/js/application/herald/herald-rule-editor.js' => '0922e81d',
'rsrc/js/application/maniphest/behavior-batch-selector.js' => '139ef688',
'rsrc/js/application/maniphest/behavior-line-chart.js' => 'ad258e28',
'rsrc/js/application/maniphest/behavior-list-edit.js' => 'c687e867',
'rsrc/js/application/owners/OwnersPathEditor.js' => '2a8b62d9',
'rsrc/js/application/owners/owners-path-editor.js' => 'ff688a7a',
'rsrc/js/application/passphrase/passphrase-credential-control.js' => '48fe33d0',
'rsrc/js/application/pholio/behavior-pholio-mock-edit.js' => '3eed1f2b',
'rsrc/js/application/pholio/behavior-pholio-mock-view.js' => '5aa1544e',
'rsrc/js/application/phortune/behavior-stripe-payment-form.js' => '02cb4398',
'rsrc/js/application/phortune/behavior-test-payment-form.js' => '4a7fb02b',
'rsrc/js/application/phortune/phortune-credit-card-form.js' => 'd12d214f',
'rsrc/js/application/policy/behavior-policy-control.js' => '0eaa33a9',
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '9347f172',
'rsrc/js/application/projects/WorkboardBoard.js' => 'b46d88c5',
'rsrc/js/application/projects/WorkboardCard.js' => '0392a5d8',
'rsrc/js/application/projects/WorkboardCardTemplate.js' => '84f82dad',
'rsrc/js/application/projects/WorkboardColumn.js' => 'c3d24e63',
'rsrc/js/application/projects/WorkboardController.js' => 'b9d0c2f3',
'rsrc/js/application/projects/WorkboardDropEffect.js' => '8e0aa661',
'rsrc/js/application/projects/WorkboardHeader.js' => '111bfd2d',
'rsrc/js/application/projects/WorkboardHeaderTemplate.js' => 'ebe83a6b',
'rsrc/js/application/projects/WorkboardOrderTemplate.js' => '03e8891f',
'rsrc/js/application/projects/behavior-project-boards.js' => '58cb6a88',
'rsrc/js/application/projects/behavior-project-create.js' => '34c53422',
'rsrc/js/application/projects/behavior-reorder-columns.js' => '8ac32fd9',
- 'rsrc/js/application/releeph/releeph-preview-branch.js' => '75184d68',
- 'rsrc/js/application/releeph/releeph-request-state-change.js' => '9f081f05',
- 'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'aa3a100c',
'rsrc/js/application/repository/repository-crossreference.js' => '44d48cd1',
'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e5bdb730',
'rsrc/js/application/search/behavior-reorder-queries.js' => 'b86f297f',
'rsrc/js/application/transactions/behavior-comment-actions.js' => '4dffaeb2',
'rsrc/js/application/transactions/behavior-reorder-configs.js' => '4842f137',
'rsrc/js/application/transactions/behavior-reorder-fields.js' => '0ad8d31f',
'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '8b5c7d65',
'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => '2bdadf1a',
'rsrc/js/application/transactions/behavior-transaction-list.js' => '9cec214e',
'rsrc/js/application/trigger/TriggerRule.js' => '41b7b4f6',
'rsrc/js/application/trigger/TriggerRuleControl.js' => '5faf27b9',
'rsrc/js/application/trigger/TriggerRuleEditor.js' => 'b49fd60c',
'rsrc/js/application/trigger/TriggerRuleType.js' => '4feea7d3',
'rsrc/js/application/trigger/trigger-rule-editor.js' => '398fdf13',
'rsrc/js/application/typeahead/behavior-typeahead-browse.js' => '70245195',
'rsrc/js/application/typeahead/behavior-typeahead-search.js' => '7b139193',
'rsrc/js/application/uiexample/gesture-example.js' => '242dedd0',
'rsrc/js/application/uiexample/notification-example.js' => '29819b75',
'rsrc/js/core/Busy.js' => '5202e831',
'rsrc/js/core/DragAndDropFileUpload.js' => '4370900d',
'rsrc/js/core/DraggableList.js' => '0169e425',
'rsrc/js/core/Favicon.js' => '7930776a',
'rsrc/js/core/FileUpload.js' => 'ab85e184',
'rsrc/js/core/Hovercard.js' => '6199f752',
'rsrc/js/core/HovercardList.js' => 'de4b4919',
'rsrc/js/core/KeyboardShortcut.js' => '1a844c06',
'rsrc/js/core/KeyboardShortcutManager.js' => '81debc48',
'rsrc/js/core/MultirowRowManager.js' => '5b54c823',
'rsrc/js/core/Notification.js' => 'a9b91e3f',
'rsrc/js/core/Prefab.js' => '5793d835',
'rsrc/js/core/ShapedRequest.js' => '995f5102',
'rsrc/js/core/TextAreaUtils.js' => 'f340a484',
'rsrc/js/core/Title.js' => '43bc9360',
'rsrc/js/core/ToolTip.js' => '83754533',
'rsrc/js/core/behavior-audio-source.js' => '3dc5ad43',
'rsrc/js/core/behavior-autofocus.js' => '65bb0011',
'rsrc/js/core/behavior-badge-view.js' => '92cdd7b6',
'rsrc/js/core/behavior-bulk-editor.js' => 'aa6d2308',
'rsrc/js/core/behavior-choose-control.js' => '04f8a1e3',
'rsrc/js/core/behavior-copy.js' => 'cf32921f',
'rsrc/js/core/behavior-detect-timezone.js' => '78bc5d94',
'rsrc/js/core/behavior-device.js' => 'ac2b1e01',
- 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '7ad020a5',
+ 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '3277c62d',
'rsrc/js/core/behavior-fancy-datepicker.js' => '36821f8d',
'rsrc/js/core/behavior-form.js' => '55d7b788',
'rsrc/js/core/behavior-gesture.js' => 'b58d1a2a',
'rsrc/js/core/behavior-global-drag-and-drop.js' => '1cab0e9a',
'rsrc/js/core/behavior-high-security-warning.js' => 'dae2d55b',
'rsrc/js/core/behavior-history-install.js' => '6a1583a8',
'rsrc/js/core/behavior-hovercard.js' => '183738e6',
'rsrc/js/core/behavior-keyboard-pager.js' => '1325b731',
'rsrc/js/core/behavior-keyboard-shortcuts.js' => '42c44e8b',
'rsrc/js/core/behavior-lightbox-attachments.js' => '14c7ab36',
'rsrc/js/core/behavior-line-linker.js' => '0d915ff5',
'rsrc/js/core/behavior-linked-container.js' => '74446546',
'rsrc/js/core/behavior-more.js' => '506aa3f4',
'rsrc/js/core/behavior-object-selector.js' => '98ef467f',
'rsrc/js/core/behavior-oncopy.js' => 'da8f5259',
'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '54262396',
'rsrc/js/core/behavior-read-only-warning.js' => 'b9109f8f',
'rsrc/js/core/behavior-redirect.js' => '407ee861',
'rsrc/js/core/behavior-refresh-csrf.js' => '46116c01',
'rsrc/js/core/behavior-remarkup-load-image.js' => '202bfa3f',
'rsrc/js/core/behavior-remarkup-preview.js' => 'd8a86cfb',
'rsrc/js/core/behavior-reorder-applications.js' => 'aa371860',
'rsrc/js/core/behavior-reveal-content.js' => 'b105a3a6',
'rsrc/js/core/behavior-scrollbar.js' => '92388bae',
'rsrc/js/core/behavior-search-typeahead.js' => '1cb7d027',
'rsrc/js/core/behavior-select-content.js' => 'e8240b50',
'rsrc/js/core/behavior-select-on-click.js' => '66365ee2',
'rsrc/js/core/behavior-setup-check-https.js' => '01384686',
'rsrc/js/core/behavior-time-typeahead.js' => '5803b9e7',
'rsrc/js/core/behavior-toggle-class.js' => '32db8374',
'rsrc/js/core/behavior-tokenizer.js' => '3b4899b0',
'rsrc/js/core/behavior-tooltip.js' => '73ecc1f8',
'rsrc/js/core/behavior-user-menu.js' => '60cd9241',
'rsrc/js/core/behavior-watch-anchor.js' => 'a77e2cbd',
'rsrc/js/core/behavior-workflow.js' => '9623adc1',
'rsrc/js/core/darkconsole/DarkLog.js' => '3b869402',
'rsrc/js/core/darkconsole/DarkMessage.js' => '26cd4b73',
'rsrc/js/core/darkconsole/behavior-dark-console.js' => '457f4d16',
'rsrc/js/core/phtize.js' => '2f1db1ed',
'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '5cf0501a',
'rsrc/js/phui/behavior-phui-file-upload.js' => 'e150bd50',
'rsrc/js/phui/behavior-phui-selectable-list.js' => 'b26a41e4',
'rsrc/js/phui/behavior-phui-submenu.js' => 'b5e9bff9',
'rsrc/js/phui/behavior-phui-tab-group.js' => '242aa08b',
'rsrc/js/phui/behavior-phui-timer-control.js' => 'f84bcbf4',
'rsrc/js/phuix/PHUIXActionListView.js' => 'c68f183f',
'rsrc/js/phuix/PHUIXActionView.js' => 'a8f573a9',
'rsrc/js/phuix/PHUIXAutocomplete.js' => '2fbe234d',
'rsrc/js/phuix/PHUIXButtonView.js' => '55a24e84',
'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'b557770a',
'rsrc/js/phuix/PHUIXExample.js' => 'c2c500a7',
'rsrc/js/phuix/PHUIXFormControl.js' => '38c1f3fb',
'rsrc/js/phuix/PHUIXFormationColumnView.js' => '4bcc1f78',
'rsrc/js/phuix/PHUIXFormationFlankView.js' => '6648270a',
'rsrc/js/phuix/PHUIXFormationView.js' => 'cef53b3e',
'rsrc/js/phuix/PHUIXIconView.js' => 'a5257c4e',
),
'symbols' => array(
'almanac-css' => '2e050f4f',
'aphront-bars' => '4a327b4a',
'aphront-dark-console-css' => '7f06cda2',
'aphront-dialog-view-css' => '6f4ea703',
'aphront-list-filter-view-css' => 'feb64255',
'aphront-multi-column-view-css' => 'fbc00ba3',
'aphront-panel-view-css' => '46923d46',
'aphront-table-view-css' => '0bb61df1',
'aphront-tokenizer-control-css' => '34e2a838',
'aphront-tooltip-css' => 'e3f2412f',
'aphront-typeahead-control-css' => '8779483d',
'application-search-view-css' => '0f7c06d8',
'auth-css' => 'c2f23d74',
'bulk-job-css' => '73af99f5',
'conduit-api-css' => 'ce2cfc41',
'config-options-css' => '16c920ae',
'conpherence-color-css' => 'b17746b0',
'conpherence-durable-column-view' => '2d57072b',
'conpherence-header-pane-css' => 'c9a3db8e',
'conpherence-menu-css' => '67f4680d',
'conpherence-message-pane-css' => 'd244db1e',
'conpherence-notification-css' => '6a3d4e58',
'conpherence-participant-pane-css' => '69e0058a',
'conpherence-thread-manager' => 'aec8e38c',
'conpherence-transaction-css' => '3a3f5e7e',
'd3' => '9d068042',
'diff-tree-view-css' => 'e2d3e222',
'differential-changeset-view-css' => '60c3d405',
'differential-core-view-css' => '7300a73e',
'differential-revision-add-comment-css' => '7e5900d9',
'differential-revision-comment-css' => '7dbc8d1d',
'differential-revision-history-css' => '237a2979',
'differential-revision-list-css' => '93d2df7d',
'differential-table-of-contents-css' => 'bba788b9',
'diffusion-css' => 'e46232d6',
'diffusion-icons-css' => '23b31a1b',
'diffusion-readme-css' => 'b68a76e4',
'diffusion-repository-css' => 'b89e8c6c',
'diviner-shared-css' => '4bd263b0',
'font-fontawesome' => '3883938a',
'font-lato' => '23631304',
'fuel-grid-css' => '66697240',
'fuel-handle-list-css' => '2c4cbeca',
'fuel-map-css' => 'd6e31510',
'fuel-menu-css' => '21f5d199',
'global-drag-and-drop-css' => '1d2713a4',
'harbormaster-css' => '8dfe16b2',
'herald-css' => '648d39e2',
'herald-rule-editor' => '2633bef7',
'herald-test-css' => '7e7bbdae',
'inline-comment-summary-css' => '81eb368d',
'javelin-aphlict' => '022516b4',
'javelin-behavior' => '1b6acc2a',
'javelin-behavior-aphlict-dropdown' => 'e9a2940f',
'javelin-behavior-aphlict-listen' => '4e61fa88',
'javelin-behavior-aphlict-status' => 'c3703a16',
'javelin-behavior-aphront-basic-tokenizer' => '3b4899b0',
- 'javelin-behavior-aphront-drag-and-drop-textarea' => '7ad020a5',
+ 'javelin-behavior-aphront-drag-and-drop-textarea' => '3277c62d',
'javelin-behavior-aphront-form-disable-on-submit' => '55d7b788',
'javelin-behavior-aphront-more' => '506aa3f4',
'javelin-behavior-audio-source' => '3dc5ad43',
'javelin-behavior-audit-preview' => 'b7b73831',
'javelin-behavior-badge-view' => '92cdd7b6',
'javelin-behavior-bulk-editor' => 'aa6d2308',
'javelin-behavior-bulk-job-reload' => '3829a3cf',
'javelin-behavior-calendar-month-view' => '158c64e0',
'javelin-behavior-choose-control' => '04f8a1e3',
'javelin-behavior-comment-actions' => '4dffaeb2',
'javelin-behavior-config-reorder-fields' => '2539f834',
'javelin-behavior-conpherence-menu' => '8c2ed2bf',
'javelin-behavior-conpherence-participant-pane' => '43ba89a2',
'javelin-behavior-conpherence-pontificate' => '4ae58b5a',
'javelin-behavior-conpherence-search' => '91befbcc',
'javelin-behavior-countdown-timer' => '6a162524',
'javelin-behavior-dark-console' => '457f4d16',
'javelin-behavior-dashboard-async-panel' => '9c01e364',
'javelin-behavior-dashboard-move-panels' => 'a2ab19be',
'javelin-behavior-dashboard-query-panel-select' => '1e413dc9',
'javelin-behavior-dashboard-tab-panel' => '0116d3e8',
'javelin-behavior-day-view' => '727a5a61',
'javelin-behavior-desktop-notifications-control' => '070679fe',
'javelin-behavior-detect-timezone' => '78bc5d94',
'javelin-behavior-device' => 'ac2b1e01',
'javelin-behavior-differential-diff-radios' => '925fe8cd',
'javelin-behavior-differential-populate' => 'b86ef6c2',
'javelin-behavior-diffusion-commit-branches' => '4b671572',
'javelin-behavior-diffusion-commit-graph' => 'ac10c917',
'javelin-behavior-diffusion-locate-file' => '87428eb2',
'javelin-behavior-diffusion-pull-lastmodified' => 'c715c123',
'javelin-behavior-document-engine' => '243d6c22',
'javelin-behavior-doorkeeper-tag' => '6a85bc5a',
'javelin-behavior-drydock-live-operation-status' => '47a0728b',
'javelin-behavior-durable-column' => 'fa6f30b2',
'javelin-behavior-editengine-reorder-configs' => '4842f137',
'javelin-behavior-editengine-reorder-fields' => '0ad8d31f',
'javelin-behavior-event-all-day' => '0b1bc990',
'javelin-behavior-fancy-datepicker' => '36821f8d',
'javelin-behavior-global-drag-and-drop' => '1cab0e9a',
'javelin-behavior-harbormaster-log' => 'b347a301',
'javelin-behavior-herald-rule-editor' => '0922e81d',
'javelin-behavior-high-security-warning' => 'dae2d55b',
'javelin-behavior-history-install' => '6a1583a8',
'javelin-behavior-icon-composer' => '38a6cedb',
'javelin-behavior-launch-icon-composer' => 'a17b84f1',
'javelin-behavior-lightbox-attachments' => '14c7ab36',
'javelin-behavior-line-chart' => 'ad258e28',
'javelin-behavior-linked-container' => '74446546',
'javelin-behavior-maniphest-batch-selector' => '139ef688',
'javelin-behavior-maniphest-list-editor' => 'c687e867',
'javelin-behavior-owners-path-editor' => 'ff688a7a',
'javelin-behavior-passphrase-credential-control' => '48fe33d0',
'javelin-behavior-phabricator-autofocus' => '65bb0011',
'javelin-behavior-phabricator-clipboard-copy' => 'cf32921f',
'javelin-behavior-phabricator-gesture' => 'b58d1a2a',
'javelin-behavior-phabricator-gesture-example' => '242dedd0',
'javelin-behavior-phabricator-keyboard-pager' => '1325b731',
'javelin-behavior-phabricator-keyboard-shortcuts' => '42c44e8b',
'javelin-behavior-phabricator-line-linker' => '0d915ff5',
'javelin-behavior-phabricator-notification-example' => '29819b75',
'javelin-behavior-phabricator-object-selector' => '98ef467f',
'javelin-behavior-phabricator-oncopy' => 'da8f5259',
'javelin-behavior-phabricator-remarkup-assist' => '54262396',
'javelin-behavior-phabricator-reveal-content' => 'b105a3a6',
'javelin-behavior-phabricator-search-typeahead' => '1cb7d027',
'javelin-behavior-phabricator-show-older-transactions' => '8b5c7d65',
'javelin-behavior-phabricator-tooltips' => '73ecc1f8',
'javelin-behavior-phabricator-transaction-comment-form' => '2bdadf1a',
'javelin-behavior-phabricator-transaction-list' => '9cec214e',
'javelin-behavior-phabricator-watch-anchor' => 'a77e2cbd',
'javelin-behavior-pholio-mock-edit' => '3eed1f2b',
'javelin-behavior-pholio-mock-view' => '5aa1544e',
'javelin-behavior-phui-dropdown-menu' => '5cf0501a',
'javelin-behavior-phui-file-upload' => 'e150bd50',
'javelin-behavior-phui-hovercards' => '183738e6',
'javelin-behavior-phui-selectable-list' => 'b26a41e4',
'javelin-behavior-phui-submenu' => 'b5e9bff9',
'javelin-behavior-phui-tab-group' => '242aa08b',
'javelin-behavior-phui-timer-control' => 'f84bcbf4',
'javelin-behavior-phuix-example' => 'c2c500a7',
'javelin-behavior-policy-control' => '0eaa33a9',
'javelin-behavior-policy-rule-editor' => '9347f172',
'javelin-behavior-project-boards' => '58cb6a88',
'javelin-behavior-project-create' => '34c53422',
'javelin-behavior-quicksand-blacklist' => '5a6f6a06',
'javelin-behavior-read-only-warning' => 'b9109f8f',
'javelin-behavior-redirect' => '407ee861',
'javelin-behavior-refresh-csrf' => '46116c01',
- 'javelin-behavior-releeph-preview-branch' => '75184d68',
- 'javelin-behavior-releeph-request-state-change' => '9f081f05',
- 'javelin-behavior-releeph-request-typeahead' => 'aa3a100c',
'javelin-behavior-remarkup-load-image' => '202bfa3f',
'javelin-behavior-remarkup-preview' => 'd8a86cfb',
'javelin-behavior-reorder-applications' => 'aa371860',
'javelin-behavior-reorder-columns' => '8ac32fd9',
'javelin-behavior-reorder-profile-menu-items' => 'e5bdb730',
'javelin-behavior-repository-crossreference' => '44d48cd1',
'javelin-behavior-scrollbar' => '92388bae',
'javelin-behavior-search-reorder-queries' => 'b86f297f',
'javelin-behavior-select-content' => 'e8240b50',
'javelin-behavior-select-on-click' => '66365ee2',
'javelin-behavior-setup-check-https' => '01384686',
'javelin-behavior-stripe-payment-form' => '02cb4398',
'javelin-behavior-test-payment-form' => '4a7fb02b',
'javelin-behavior-time-typeahead' => '5803b9e7',
'javelin-behavior-toggle-class' => '32db8374',
'javelin-behavior-toggle-widget' => '8f959ad0',
'javelin-behavior-trigger-rule-editor' => '398fdf13',
'javelin-behavior-typeahead-browse' => '70245195',
'javelin-behavior-typeahead-search' => '7b139193',
'javelin-behavior-user-menu' => '60cd9241',
'javelin-behavior-view-placeholder' => 'a9942052',
'javelin-behavior-workflow' => '9623adc1',
'javelin-chart' => '52e3ff03',
'javelin-chart-curtain-view' => '86954222',
'javelin-chart-function-label' => '81de1dab',
'javelin-color' => '78f811c9',
'javelin-cookie' => '05d290ef',
'javelin-diffusion-locate-file-source' => '94243d89',
'javelin-dom' => 'e4c7622a',
'javelin-dynval' => '202a2e85',
'javelin-event' => 'c03f2fb4',
'javelin-external-editor-link-engine' => '48a8641f',
'javelin-fx' => '34450586',
'javelin-history' => '030b4f7a',
'javelin-install' => '5902260c',
'javelin-json' => '541f81c3',
'javelin-leader' => '0d2490ce',
'javelin-magical-init' => '98e6504a',
'javelin-mask' => '7c4d8998',
'javelin-quicksand' => 'd3799cb4',
'javelin-reactor' => '1c850a26',
'javelin-reactor-dom' => '6cfa0008',
'javelin-reactor-node-calmer' => '225bbb98',
'javelin-reactornode' => '72960bc1',
'javelin-request' => '84e6891f',
'javelin-resource' => '20514cc2',
'javelin-routable' => '6a18c42e',
'javelin-router' => '32755edb',
'javelin-scrollbar' => 'a43ae2ae',
'javelin-sound' => 'd4cc2d2a',
'javelin-stratcom' => '0889b835',
'javelin-tokenizer' => '89a1ae3a',
'javelin-typeahead' => 'a4356cde',
'javelin-typeahead-composite-source' => '22ee68a5',
'javelin-typeahead-normalizer' => 'a241536a',
'javelin-typeahead-ondemand-source' => '23387297',
'javelin-typeahead-preloaded-source' => '5a79f6c3',
'javelin-typeahead-source' => '8badee71',
'javelin-typeahead-static-source' => '80bff3af',
'javelin-uri' => '2e255291',
'javelin-util' => 'edb4d8c9',
'javelin-vector' => 'e9c80beb',
'javelin-view' => '289bf236',
'javelin-view-html' => 'f8c4e135',
'javelin-view-interpreter' => '876506b6',
'javelin-view-renderer' => '9aae2b66',
'javelin-view-visitor' => '308f9fe4',
'javelin-websocket' => 'fdc13e4e',
'javelin-workboard-board' => 'b46d88c5',
'javelin-workboard-card' => '0392a5d8',
'javelin-workboard-card-template' => '84f82dad',
'javelin-workboard-column' => 'c3d24e63',
'javelin-workboard-controller' => 'b9d0c2f3',
'javelin-workboard-drop-effect' => '8e0aa661',
'javelin-workboard-header' => '111bfd2d',
'javelin-workboard-header-template' => 'ebe83a6b',
'javelin-workboard-order-template' => '03e8891f',
'javelin-workflow' => '945ff654',
'maniphest-report-css' => '3d53188b',
'maniphest-task-edit-css' => '272daa84',
'maniphest-task-summary-css' => '61d1667e',
'multirow-row-manager' => '5b54c823',
'owners-path-editor' => '2a8b62d9',
'owners-path-editor-css' => 'fa7c13ef',
'paste-css' => 'b37bcd38',
'path-typeahead' => 'ad486db3',
'people-picture-menu-item-css' => 'fe8e07cf',
'people-profile-css' => '2ea2daa1',
'phabricator-action-list-view-css' => '1b0085b2',
'phabricator-busy' => '5202e831',
'phabricator-chatlog-css' => 'abdc76ee',
'phabricator-content-source-view-css' => 'cdf0d579',
'phabricator-core-css' => 'b3ebd90d',
'phabricator-countdown-css' => 'bff8012f',
'phabricator-darklog' => '3b869402',
'phabricator-darkmessage' => '26cd4b73',
'phabricator-dashboard-css' => '5a205b9d',
'phabricator-diff-changeset' => 'd7d3ba75',
'phabricator-diff-changeset-list' => 'cc2c5de5',
'phabricator-diff-inline' => '9c775532',
'phabricator-diff-inline-content-state' => 'aa51efb4',
'phabricator-diff-path-view' => '8207abf9',
'phabricator-diff-tree-view' => '5d83623b',
'phabricator-drag-and-drop-file-upload' => '4370900d',
'phabricator-draggable-list' => '0169e425',
'phabricator-fatal-config-template-css' => '20babf50',
'phabricator-favicon' => '7930776a',
'phabricator-feed-css' => 'd8b6e3f8',
'phabricator-file-upload' => 'ab85e184',
'phabricator-flag-css' => '2b77be8d',
'phabricator-keyboard-shortcut' => '1a844c06',
'phabricator-keyboard-shortcut-manager' => '81debc48',
'phabricator-main-menu-view' => 'bcec20f0',
'phabricator-nav-view-css' => '423f92cc',
'phabricator-notification' => 'a9b91e3f',
'phabricator-notification-css' => '30240bd2',
'phabricator-notification-menu-css' => '4df1ee30',
'phabricator-object-selector-css' => 'ee77366f',
'phabricator-phtize' => '2f1db1ed',
'phabricator-prefab' => '5793d835',
'phabricator-remarkup-css' => '5baa3bd9',
'phabricator-search-results-css' => '9ea70ace',
'phabricator-shaped-request' => '995f5102',
'phabricator-slowvote-css' => '1694baed',
'phabricator-source-code-view-css' => '03d7ac28',
'phabricator-standard-page-view' => 'a374f94c',
'phabricator-textareautils' => 'f340a484',
'phabricator-title' => '43bc9360',
'phabricator-tooltip' => '83754533',
'phabricator-ui-example-css' => 'b4795059',
'phabricator-zindex-css' => 'ac3bfcd4',
'phame-css' => 'bb442327',
'pholio-css' => '88ef5ef1',
'pholio-edit-css' => '4df55b3b',
'pholio-inline-comments-css' => '722b48c2',
'phortune-credit-card-form' => 'd12d214f',
'phortune-credit-card-form-css' => '3b9868a8',
'phortune-css' => '508a1a5e',
'phortune-invoice-css' => '4436b241',
'phrequent-css' => 'bd79cc67',
'phriction-document-css' => '03380da0',
'phui-action-panel-css' => '6c386cbf',
'phui-badge-view-css' => '666e25ad',
'phui-basic-nav-view-css' => '56ebd66d',
'phui-big-info-view-css' => '362ad37b',
'phui-box-css' => '5ed3b8cb',
'phui-bulk-editor-css' => '374d5e30',
'phui-button-bar-css' => 'a4aa75c4',
'phui-button-css' => 'ea704902',
'phui-button-simple-css' => '1ff278aa',
'phui-calendar-css' => 'f11073aa',
'phui-calendar-day-css' => '9597d706',
'phui-calendar-list-css' => 'ccd7e4e2',
'phui-calendar-month-css' => 'cb758c42',
'phui-chart-css' => '14df9ae3',
'phui-cms-css' => '8c05c41e',
'phui-comment-form-css' => '68a2d99a',
'phui-comment-panel-css' => 'ec4e31c0',
'phui-crumbs-view-css' => '614f43cf',
- 'phui-curtain-object-ref-view-css' => '5f752bdb',
+ 'phui-curtain-object-ref-view-css' => '51d93266',
'phui-curtain-view-css' => '68c5efb6',
'phui-document-summary-view-css' => 'b068eed1',
'phui-document-view-css' => '52b748a5',
'phui-document-view-pro-css' => 'b9613a10',
'phui-feed-story-css' => 'a0c05029',
'phui-font-icon-base-css' => '303c9b87',
'phui-fontkit-css' => '1ec937e5',
'phui-form-css' => '1f177cb7',
'phui-form-view-css' => '01b796c0',
'phui-formation-view-css' => 'd2dec8ed',
'phui-head-thing-view-css' => 'd7f293df',
'phui-header-view-css' => '36c86a58',
'phui-hovercard' => '6199f752',
'phui-hovercard-list' => 'de4b4919',
'phui-hovercard-view-css' => '6ca90fa0',
'phui-icon-set-selector-css' => '7aa5f3ec',
'phui-icon-view-css' => '4cbc684a',
'phui-image-mask-css' => '62c7f4d2',
'phui-info-view-css' => 'a10a909b',
'phui-inline-comment-view-css' => '9863a85e',
'phui-invisible-character-view-css' => 'c694c4a4',
'phui-left-right-css' => '68513c34',
'phui-lightbox-css' => '4ebf22da',
'phui-list-view-css' => '0c04affd',
'phui-object-box-css' => 'b8d7eea0',
'phui-oi-big-ui-css' => 'fa74cc35',
'phui-oi-color-css' => 'b517bfa0',
'phui-oi-drag-ui-css' => 'da15d3dc',
'phui-oi-flush-ui-css' => '490e2e2e',
'phui-oi-list-view-css' => 'af98a277',
'phui-oi-simple-ui-css' => '6a30fa46',
'phui-pager-css' => 'd022c7ad',
'phui-pinboard-view-css' => '1f08f5d8',
'phui-policy-section-view-css' => '139fdc64',
'phui-property-list-view-css' => '5adf7078',
'phui-remarkup-preview-css' => '91767007',
'phui-segment-bar-view-css' => '5166b370',
'phui-spacing-css' => 'b05cadc3',
'phui-status-list-view-css' => '293b5dad',
'phui-tag-view-css' => 'fb811341',
'phui-theme-css' => '35883b37',
'phui-timeline-view-css' => '2d32d7a9',
'phui-two-column-view-css' => 'f96d319f',
'phui-workboard-color-css' => 'e86de308',
'phui-workboard-view-css' => '74fc9d98',
'phui-workcard-view-css' => '913441b6',
'phui-workpanel-view-css' => '3ae89b20',
'phuix-action-list-view' => 'c68f183f',
'phuix-action-view' => 'a8f573a9',
'phuix-autocomplete' => '2fbe234d',
'phuix-button-view' => '55a24e84',
'phuix-dropdown-menu' => 'b557770a',
'phuix-form-control-view' => '38c1f3fb',
'phuix-formation-column-view' => '4bcc1f78',
'phuix-formation-flank-view' => '6648270a',
'phuix-formation-view' => 'cef53b3e',
'phuix-icon-view' => 'a5257c4e',
'policy-css' => 'ceb56a08',
'policy-edit-css' => '8794e2ed',
'policy-transaction-detail-css' => 'c02b8384',
'ponder-view-css' => '05a09d0a',
'project-card-view-css' => 'a9f2c2dd',
'project-triggers-css' => 'cd9c8bb9',
'project-view-css' => '567858b3',
- 'releeph-core' => 'f81ff2db',
- 'releeph-preview-branch' => '22db5c07',
- 'releeph-request-differential-create-dialog' => '0ac1ea31',
- 'releeph-request-typeahead-css' => 'bce37359',
'setup-issue-css' => '5eed85b2',
'sprite-login-css' => '18b368a6',
'sprite-tokens-css' => 'f1896dc5',
'syntax-default-css' => '055fc231',
'syntax-highlighting-css' => '548567f6',
'tokens-css' => 'ce5a50bd',
'trigger-rule' => '41b7b4f6',
'trigger-rule-control' => '5faf27b9',
'trigger-rule-editor' => 'b49fd60c',
'trigger-rule-type' => '4feea7d3',
'typeahead-browse-css' => 'b7ed02d2',
'unhandled-exception-css' => '9ecfc00d',
),
'requires' => array(
'0116d3e8' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'01384686' => array(
'javelin-behavior',
'javelin-uri',
'phabricator-notification',
),
'0169e425' => array(
'javelin-install',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
'javelin-vector',
'javelin-magical-init',
),
'022516b4' => array(
'javelin-install',
'javelin-util',
'javelin-websocket',
'javelin-leader',
'javelin-json',
),
'02cb4398' => array(
'javelin-behavior',
'javelin-dom',
'phortune-credit-card-form',
),
'030b4f7a' => array(
'javelin-stratcom',
'javelin-install',
'javelin-uri',
'javelin-util',
),
'0392a5d8' => array(
'javelin-install',
),
'03e8891f' => array(
'javelin-install',
),
'04f8a1e3' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-workflow',
),
'05d290ef' => array(
'javelin-install',
'javelin-util',
),
'070679fe' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-uri',
'phabricator-notification',
),
'0889b835' => array(
'javelin-install',
'javelin-event',
'javelin-util',
'javelin-magical-init',
),
'0922e81d' => array(
'herald-rule-editor',
'javelin-behavior',
),
'0ad8d31f' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'0d2490ce' => array(
'javelin-install',
),
'0d915ff5' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-history',
'javelin-external-editor-link-engine',
),
'0eaa33a9' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'phuix-dropdown-menu',
'phuix-action-list-view',
'phuix-action-view',
'javelin-workflow',
'phuix-icon-view',
),
'111bfd2d' => array(
'javelin-install',
),
'1325b731' => array(
'javelin-behavior',
'javelin-uri',
'phabricator-keyboard-shortcut',
),
'139ef688' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
),
'14c7ab36' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-mask',
'javelin-util',
'phuix-icon-view',
'phabricator-busy',
),
'183738e6' => array(
'javelin-behavior',
'javelin-behavior-device',
'javelin-stratcom',
'javelin-vector',
'phui-hovercard',
'phui-hovercard-list',
),
'1a844c06' => array(
'javelin-install',
'javelin-util',
'phabricator-keyboard-shortcut-manager',
),
'1b6acc2a' => array(
'javelin-magical-init',
'javelin-util',
),
'1c850a26' => array(
'javelin-install',
'javelin-util',
),
'1cab0e9a' => array(
'javelin-behavior',
'javelin-dom',
'javelin-uri',
'javelin-mask',
'phabricator-drag-and-drop-file-upload',
),
'1cb7d027' => array(
'javelin-behavior',
'javelin-typeahead-ondemand-source',
'javelin-typeahead',
'javelin-dom',
'javelin-uri',
'javelin-util',
'javelin-stratcom',
'phabricator-prefab',
'phuix-icon-view',
),
'1e413dc9' => array(
'javelin-behavior',
'javelin-dom',
),
'1ff278aa' => array(
'phui-button-css',
),
'202a2e85' => array(
'javelin-install',
'javelin-reactornode',
'javelin-util',
'javelin-reactor',
),
'202bfa3f' => array(
'javelin-behavior',
'javelin-request',
),
'20514cc2' => array(
'javelin-util',
'javelin-uri',
'javelin-install',
),
'225bbb98' => array(
'javelin-install',
'javelin-reactor',
'javelin-util',
),
'22ee68a5' => array(
'javelin-install',
'javelin-typeahead-source',
'javelin-util',
),
23387297 => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-typeahead-source',
),
23631304 => array(
'phui-fontkit-css',
),
'242aa08b' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'242dedd0' => array(
'javelin-stratcom',
'javelin-behavior',
'javelin-vector',
'javelin-dom',
),
'243d6c22' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'2539f834' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-json',
'phabricator-draggable-list',
),
'2633bef7' => array(
'multirow-row-manager',
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-stratcom',
'javelin-json',
'phabricator-prefab',
),
'289bf236' => array(
'javelin-install',
'javelin-util',
),
'29819b75' => array(
'phabricator-notification',
'javelin-stratcom',
'javelin-behavior',
),
'2a8b62d9' => array(
'multirow-row-manager',
'javelin-install',
'path-typeahead',
'javelin-dom',
'javelin-util',
'phabricator-prefab',
'phuix-form-control-view',
),
'2bdadf1a' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-request',
'phabricator-shaped-request',
),
'2e255291' => array(
'javelin-install',
'javelin-util',
'javelin-stratcom',
),
'2f1db1ed' => array(
'javelin-util',
),
'2fbe234d' => array(
'javelin-install',
'javelin-dom',
'phuix-icon-view',
'phabricator-prefab',
),
'308f9fe4' => array(
'javelin-install',
'javelin-util',
),
'32755edb' => array(
'javelin-install',
'javelin-util',
),
+ '3277c62d' => array(
+ 'javelin-behavior',
+ 'javelin-dom',
+ 'javelin-json',
+ 'phabricator-drag-and-drop-file-upload',
+ 'phabricator-textareautils',
+ ),
'32db8374' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
34450586 => array(
'javelin-color',
'javelin-install',
'javelin-util',
),
'34c53422' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
),
'34e2a838' => array(
'aphront-typeahead-control-css',
'phui-tag-view-css',
),
'36821f8d' => array(
'javelin-behavior',
'javelin-util',
'javelin-dom',
'javelin-stratcom',
'javelin-vector',
),
'3829a3cf' => array(
'javelin-behavior',
'javelin-uri',
),
'38a6cedb' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'38c1f3fb' => array(
'javelin-install',
'javelin-dom',
),
'398fdf13' => array(
'javelin-behavior',
'trigger-rule-editor',
'trigger-rule',
'trigger-rule-type',
),
'3ae89b20' => array(
'phui-workcard-view-css',
),
'3b4899b0' => array(
'javelin-behavior',
'phabricator-prefab',
),
'3dc5ad43' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-vector',
'javelin-dom',
),
'3eed1f2b' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-workflow',
'javelin-quicksand',
'phabricator-phtize',
'phabricator-drag-and-drop-file-upload',
'phabricator-draggable-list',
),
'407ee861' => array(
'javelin-behavior',
'javelin-uri',
),
'42c44e8b' => array(
'javelin-behavior',
'javelin-workflow',
'javelin-json',
'javelin-dom',
'phabricator-keyboard-shortcut',
),
'4370900d' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-dom',
'javelin-uri',
'phabricator-file-upload',
),
'43ba89a2' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-util',
'phabricator-notification',
'conpherence-thread-manager',
),
'43bc9360' => array(
'javelin-install',
),
'44d48cd1' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-uri',
),
'457f4d16' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-util',
'javelin-dom',
'javelin-request',
'phabricator-keyboard-shortcut',
'phabricator-darklog',
'phabricator-darkmessage',
),
'46116c01' => array(
'javelin-request',
'javelin-behavior',
'javelin-dom',
'javelin-router',
'javelin-util',
'phabricator-busy',
),
'47a0728b' => array(
'javelin-behavior',
'javelin-dom',
'javelin-request',
),
'4842f137' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'48a8641f' => array(
'javelin-install',
),
'48fe33d0' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-util',
'javelin-uri',
),
'490e2e2e' => array(
'phui-oi-list-view-css',
),
'4a7fb02b' => array(
'javelin-behavior',
'javelin-dom',
'phortune-credit-card-form',
),
'4ae58b5a' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-workflow',
'javelin-stratcom',
'conpherence-thread-manager',
),
'4b671572' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-request',
),
'4bcc1f78' => array(
'javelin-install',
'javelin-dom',
),
'4dffaeb2' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phuix-form-control-view',
'phuix-icon-view',
'javelin-behavior-phabricator-gesture',
),
'4e61fa88' => array(
'javelin-behavior',
'javelin-aphlict',
'javelin-stratcom',
'javelin-request',
'javelin-uri',
'javelin-dom',
'javelin-json',
'javelin-router',
'javelin-util',
'javelin-leader',
'javelin-sound',
'phabricator-notification',
),
'4feea7d3' => array(
'trigger-rule-control',
),
'506aa3f4' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'5202e831' => array(
'javelin-install',
'javelin-dom',
'javelin-fx',
),
'52e3ff03' => array(
'phui-chart-css',
'd3',
'javelin-chart-curtain-view',
'javelin-chart-function-label',
),
'541f81c3' => array(
'javelin-install',
),
54262396 => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phabricator-phtize',
'phabricator-textareautils',
'javelin-workflow',
'javelin-vector',
'phuix-autocomplete',
'javelin-mask',
),
'548567f6' => array(
'syntax-default-css',
),
'55a24e84' => array(
'javelin-install',
'javelin-dom',
),
'55d7b788' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'5793d835' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-typeahead',
'javelin-tokenizer',
'javelin-typeahead-preloaded-source',
'javelin-typeahead-ondemand-source',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
),
'5803b9e7' => array(
'javelin-behavior',
'javelin-util',
'javelin-dom',
'javelin-stratcom',
'javelin-vector',
'javelin-typeahead-static-source',
),
'58cb6a88' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'javelin-workboard-controller',
'javelin-workboard-drop-effect',
),
'5902260c' => array(
'javelin-util',
'javelin-magical-init',
),
'5a6f6a06' => array(
'javelin-behavior',
'javelin-quicksand',
),
'5a79f6c3' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-typeahead-source',
),
'5aa1544e' => array(
'javelin-behavior',
'javelin-util',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
'javelin-magical-init',
'javelin-request',
'javelin-history',
'javelin-workflow',
'javelin-mask',
'javelin-behavior-device',
'phabricator-keyboard-shortcut',
),
'5b54c823' => array(
'javelin-install',
'javelin-stratcom',
'javelin-dom',
'javelin-util',
),
'5cf0501a' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phuix-dropdown-menu',
),
'5d83623b' => array(
'javelin-dom',
),
'5faf27b9' => array(
'phuix-form-control-view',
),
'60c3d405' => array(
'phui-inline-comment-view-css',
),
'60cd9241' => array(
'javelin-behavior',
),
'6199f752' => array(
'javelin-install',
'javelin-dom',
'javelin-vector',
'javelin-request',
'javelin-uri',
),
'65bb0011' => array(
'javelin-behavior',
'javelin-dom',
),
'66365ee2' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'6648270a' => array(
'javelin-install',
'javelin-dom',
),
'6a1583a8' => array(
'javelin-behavior',
'javelin-history',
),
'6a162524' => array(
'javelin-behavior',
'javelin-dom',
),
'6a18c42e' => array(
'javelin-install',
),
'6a30fa46' => array(
'phui-oi-list-view-css',
),
'6a85bc5a' => array(
'javelin-behavior',
'javelin-dom',
'javelin-json',
'javelin-workflow',
'javelin-magical-init',
),
'6cfa0008' => array(
'javelin-dom',
'javelin-dynval',
'javelin-reactor',
'javelin-reactornode',
'javelin-install',
'javelin-util',
),
70245195 => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
),
'727a5a61' => array(
'phuix-icon-view',
),
'72960bc1' => array(
'javelin-install',
'javelin-reactor',
'javelin-util',
'javelin-reactor-node-calmer',
),
'73ecc1f8' => array(
'javelin-behavior',
'javelin-behavior-device',
'javelin-stratcom',
'phabricator-tooltip',
),
74446546 => array(
'javelin-behavior',
'javelin-dom',
),
- '75184d68' => array(
- 'javelin-behavior',
- 'javelin-dom',
- 'javelin-uri',
- 'javelin-request',
- ),
'78bc5d94' => array(
'javelin-behavior',
'javelin-uri',
'phabricator-notification',
),
'78f811c9' => array(
'javelin-install',
),
'7930776a' => array(
'javelin-install',
'javelin-dom',
),
- '7ad020a5' => array(
- 'javelin-behavior',
- 'javelin-dom',
- 'phabricator-drag-and-drop-file-upload',
- 'phabricator-textareautils',
- ),
'7b139193' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
),
'7c4d8998' => array(
'javelin-install',
'javelin-dom',
),
'80bff3af' => array(
'javelin-install',
'javelin-typeahead-source',
),
'81debc48' => array(
'javelin-install',
'javelin-util',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
),
'8207abf9' => array(
'javelin-dom',
),
83754533 => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-vector',
),
'84e6891f' => array(
'javelin-install',
'javelin-stratcom',
'javelin-util',
'javelin-behavior',
'javelin-json',
'javelin-dom',
'javelin-resource',
'javelin-routable',
),
'84f82dad' => array(
'javelin-install',
),
'87428eb2' => array(
'javelin-behavior',
'javelin-diffusion-locate-file-source',
'javelin-dom',
'javelin-typeahead',
'javelin-uri',
),
'876506b6' => array(
'javelin-view',
'javelin-install',
'javelin-dom',
),
'89a1ae3a' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
),
'8ac32fd9' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'8b5c7d65' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phabricator-busy',
),
'8badee71' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-typeahead-normalizer',
),
'8c2ed2bf' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'javelin-behavior-device',
'javelin-history',
'javelin-vector',
'javelin-scrollbar',
'phabricator-title',
'phabricator-shaped-request',
'conpherence-thread-manager',
),
'8e0aa661' => array(
'javelin-install',
'javelin-dom',
),
'8f959ad0' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-workflow',
'javelin-stratcom',
),
'91befbcc' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-workflow',
'javelin-stratcom',
),
'92388bae' => array(
'javelin-behavior',
'javelin-scrollbar',
),
'925fe8cd' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'92cdd7b6' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'9347f172' => array(
'javelin-behavior',
'multirow-row-manager',
'javelin-dom',
'javelin-util',
'phabricator-prefab',
'javelin-json',
),
'94243d89' => array(
'javelin-install',
'javelin-dom',
'javelin-typeahead-preloaded-source',
'javelin-util',
),
'945ff654' => array(
'javelin-stratcom',
'javelin-request',
'javelin-dom',
'javelin-vector',
'javelin-install',
'javelin-util',
'javelin-mask',
'javelin-uri',
'javelin-routable',
),
'9623adc1' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'javelin-router',
),
'98ef467f' => array(
'javelin-behavior',
'javelin-dom',
'javelin-request',
'javelin-util',
),
'995f5102' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-router',
),
'9aae2b66' => array(
'javelin-install',
'javelin-util',
),
'9c01e364' => array(
'javelin-behavior',
'javelin-dom',
'javelin-workflow',
),
'9c775532' => array(
'javelin-dom',
'phabricator-diff-inline-content-state',
),
'9cec214e' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'javelin-uri',
'phabricator-textareautils',
),
- '9f081f05' => array(
- 'javelin-behavior',
- 'javelin-dom',
- 'javelin-stratcom',
- 'javelin-workflow',
- 'javelin-util',
- 'phabricator-keyboard-shortcut',
- ),
'a17b84f1' => array(
'javelin-behavior',
'javelin-dom',
'javelin-workflow',
),
'a241536a' => array(
'javelin-install',
),
'a2ab19be' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
),
'a4356cde' => array(
'javelin-install',
'javelin-dom',
'javelin-vector',
'javelin-util',
),
'a43ae2ae' => array(
'javelin-install',
'javelin-dom',
'javelin-stratcom',
'javelin-vector',
),
'a4aa75c4' => array(
'phui-button-css',
'phui-button-simple-css',
),
'a5257c4e' => array(
'javelin-install',
'javelin-dom',
),
'a77e2cbd' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
),
'a8f573a9' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
),
'a9942052' => array(
'javelin-behavior',
'javelin-dom',
'javelin-view-renderer',
'javelin-install',
),
'a9b91e3f' => array(
'javelin-install',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
'phabricator-notification-css',
),
'aa371860' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
- 'aa3a100c' => array(
- 'javelin-behavior',
- 'javelin-dom',
- 'javelin-typeahead',
- 'javelin-typeahead-ondemand-source',
- 'javelin-dom',
- ),
'aa51efb4' => array(
'javelin-dom',
),
'aa6d2308' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'multirow-row-manager',
'javelin-json',
'phuix-form-control-view',
),
'ab85e184' => array(
'javelin-install',
'javelin-dom',
'phabricator-notification',
),
'ac10c917' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'ac2b1e01' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
'javelin-install',
),
'ad258e28' => array(
'javelin-behavior',
'javelin-dom',
'javelin-chart',
),
'ad486db3' => array(
'javelin-install',
'javelin-typeahead',
'javelin-dom',
'javelin-request',
'javelin-typeahead-ondemand-source',
'javelin-util',
),
'aec8e38c' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
'javelin-aphlict',
'javelin-workflow',
'javelin-router',
'javelin-behavior-device',
'javelin-vector',
),
'b105a3a6' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'b26a41e4' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'b347a301' => array(
'javelin-behavior',
),
'b46d88c5' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
'javelin-workboard-column',
'javelin-workboard-header-template',
'javelin-workboard-card-template',
'javelin-workboard-order-template',
),
'b49fd60c' => array(
'multirow-row-manager',
'trigger-rule',
),
'b517bfa0' => array(
'phui-oi-list-view-css',
),
'b557770a' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-vector',
'javelin-stratcom',
),
'b58d1a2a' => array(
'javelin-behavior',
'javelin-behavior-device',
'javelin-stratcom',
'javelin-vector',
'javelin-dom',
'javelin-magical-init',
),
'b5e9bff9' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'b7b73831' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'phabricator-shaped-request',
),
'b86ef6c2' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'phabricator-tooltip',
'phabricator-diff-changeset-list',
'phabricator-diff-changeset',
'phuix-formation-view',
),
'b86f297f' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'b9109f8f' => array(
'javelin-behavior',
'javelin-uri',
'phabricator-notification',
),
'b9d0c2f3' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'phabricator-drag-and-drop-file-upload',
'javelin-workboard-board',
),
'bcec20f0' => array(
'phui-theme-css',
),
'c03f2fb4' => array(
'javelin-install',
),
'c2c500a7' => array(
'javelin-install',
'javelin-dom',
'phuix-button-view',
),
'c3703a16' => array(
'javelin-behavior',
'javelin-aphlict',
'phabricator-phtize',
'javelin-dom',
),
'c3d24e63' => array(
'javelin-install',
'javelin-workboard-card',
'javelin-workboard-header',
),
'c687e867' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-fx',
'javelin-util',
),
'c68f183f' => array(
'javelin-install',
'javelin-dom',
),
'c715c123' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-workflow',
'javelin-json',
),
'cc2c5de5' => array(
'javelin-install',
'phuix-button-view',
'phabricator-diff-tree-view',
),
'cef53b3e' => array(
'javelin-install',
'javelin-dom',
'phuix-formation-column-view',
'phuix-formation-flank-view',
),
'cf32921f' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'd12d214f' => array(
'javelin-install',
'javelin-dom',
'javelin-json',
'javelin-workflow',
'javelin-util',
),
'd3799cb4' => array(
'javelin-install',
),
'd4cc2d2a' => array(
'javelin-install',
),
'd7d3ba75' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
'javelin-workflow',
'javelin-router',
'javelin-behavior-device',
'javelin-vector',
'phabricator-diff-inline',
'phabricator-diff-path-view',
'phuix-button-view',
'javelin-external-editor-link-engine',
),
'd8a86cfb' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'phabricator-shaped-request',
),
'da15d3dc' => array(
'phui-oi-list-view-css',
),
'da8f5259' => array(
'javelin-behavior',
'javelin-dom',
),
'dae2d55b' => array(
'javelin-behavior',
'javelin-uri',
'phabricator-notification',
),
'de4b4919' => array(
'javelin-install',
'javelin-dom',
'javelin-vector',
'javelin-request',
'javelin-uri',
'phui-hovercard',
),
'e150bd50' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phuix-dropdown-menu',
),
'e4c7622a' => array(
'javelin-magical-init',
'javelin-install',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
),
'e5bdb730' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'e8240b50' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'e9a2940f' => array(
'javelin-behavior',
'javelin-request',
'javelin-stratcom',
'javelin-vector',
'javelin-dom',
'javelin-uri',
'javelin-behavior-device',
'phabricator-title',
'phabricator-favicon',
),
'e9c80beb' => array(
'javelin-install',
'javelin-event',
),
'ebe83a6b' => array(
'javelin-install',
),
'ec4e31c0' => array(
'phui-timeline-view-css',
),
'ee77366f' => array(
'aphront-dialog-view-css',
),
'f340a484' => array(
'javelin-install',
'javelin-dom',
'javelin-vector',
),
'f84bcbf4' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'f8c4e135' => array(
'javelin-install',
'javelin-dom',
'javelin-view-visitor',
'javelin-util',
),
'fa6f30b2' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-behavior-device',
'javelin-scrollbar',
'javelin-quicksand',
'phabricator-keyboard-shortcut',
'conpherence-thread-manager',
),
'fa74cc35' => array(
'phui-oi-list-view-css',
),
'fdc13e4e' => array(
'javelin-install',
),
'ff688a7a' => array(
'owners-path-editor',
'javelin-behavior',
),
),
'packages' => array(
'conpherence.pkg.css' => array(
'conpherence-menu-css',
'conpherence-color-css',
'conpherence-message-pane-css',
'conpherence-notification-css',
'conpherence-transaction-css',
'conpherence-participant-pane-css',
'conpherence-header-pane-css',
),
'conpherence.pkg.js' => array(
'javelin-behavior-conpherence-menu',
'javelin-behavior-conpherence-participant-pane',
'javelin-behavior-conpherence-pontificate',
'javelin-behavior-toggle-widget',
),
'core.pkg.css' => array(
'phabricator-core-css',
'phabricator-zindex-css',
'phui-button-css',
'phui-button-simple-css',
'phui-theme-css',
'phabricator-standard-page-view',
'aphront-dialog-view-css',
'phui-form-view-css',
'aphront-panel-view-css',
'aphront-table-view-css',
'aphront-tokenizer-control-css',
'aphront-typeahead-control-css',
'aphront-list-filter-view-css',
'application-search-view-css',
'phabricator-remarkup-css',
'syntax-highlighting-css',
'syntax-default-css',
'phui-pager-css',
'aphront-tooltip-css',
'phabricator-flag-css',
'phui-info-view-css',
'phabricator-main-menu-view',
'phabricator-notification-css',
'phabricator-notification-menu-css',
'phui-lightbox-css',
'phui-comment-panel-css',
'phui-header-view-css',
'phabricator-nav-view-css',
'phui-basic-nav-view-css',
'phui-crumbs-view-css',
'phui-oi-list-view-css',
'phui-oi-color-css',
'phui-oi-big-ui-css',
'phui-oi-drag-ui-css',
'phui-oi-simple-ui-css',
'phui-oi-flush-ui-css',
'global-drag-and-drop-css',
'phui-spacing-css',
'phui-form-css',
'phui-icon-view-css',
'phabricator-action-list-view-css',
'phui-property-list-view-css',
'phui-tag-view-css',
'phui-list-view-css',
'font-fontawesome',
'font-lato',
'phui-font-icon-base-css',
'phui-fontkit-css',
'phui-box-css',
'phui-object-box-css',
'phui-timeline-view-css',
'phui-two-column-view-css',
'phui-curtain-view-css',
'sprite-login-css',
'sprite-tokens-css',
'tokens-css',
'auth-css',
'phui-status-list-view-css',
'phui-feed-story-css',
'phabricator-feed-css',
'phabricator-dashboard-css',
'aphront-multi-column-view-css',
'phui-curtain-object-ref-view-css',
'phui-comment-form-css',
'phui-head-thing-view-css',
'conpherence-durable-column-view',
'phui-button-bar-css',
),
'core.pkg.js' => array(
'javelin-util',
'javelin-install',
'javelin-event',
'javelin-stratcom',
'javelin-behavior',
'javelin-resource',
'javelin-request',
'javelin-vector',
'javelin-dom',
'javelin-json',
'javelin-uri',
'javelin-workflow',
'javelin-mask',
'javelin-typeahead',
'javelin-typeahead-normalizer',
'javelin-typeahead-source',
'javelin-typeahead-preloaded-source',
'javelin-typeahead-ondemand-source',
'javelin-tokenizer',
'javelin-history',
'javelin-router',
'javelin-routable',
'javelin-behavior-aphront-basic-tokenizer',
'javelin-behavior-workflow',
'javelin-behavior-aphront-form-disable-on-submit',
'phabricator-keyboard-shortcut-manager',
'phabricator-keyboard-shortcut',
'javelin-behavior-phabricator-keyboard-shortcuts',
'javelin-behavior-refresh-csrf',
'javelin-behavior-phabricator-watch-anchor',
'javelin-behavior-phabricator-autofocus',
'phuix-dropdown-menu',
'phuix-action-list-view',
'phuix-action-view',
'phuix-icon-view',
'phabricator-phtize',
'javelin-behavior-phabricator-oncopy',
'phabricator-tooltip',
'javelin-behavior-phabricator-tooltips',
'phabricator-prefab',
'javelin-behavior-device',
'javelin-behavior-toggle-class',
'javelin-behavior-lightbox-attachments',
'phabricator-busy',
'javelin-sound',
'javelin-aphlict',
'phabricator-notification',
'javelin-behavior-aphlict-listen',
'javelin-behavior-phabricator-search-typeahead',
'javelin-behavior-aphlict-dropdown',
'javelin-behavior-history-install',
'javelin-behavior-phabricator-gesture',
'javelin-behavior-phabricator-remarkup-assist',
'phabricator-textareautils',
'phabricator-file-upload',
'javelin-behavior-global-drag-and-drop',
'javelin-behavior-phabricator-reveal-content',
'phui-hovercard',
'phui-hovercard-list',
'javelin-behavior-phui-hovercards',
'javelin-color',
'javelin-fx',
'phabricator-draggable-list',
'javelin-behavior-phabricator-transaction-list',
'javelin-behavior-phabricator-show-older-transactions',
'javelin-behavior-phui-dropdown-menu',
'javelin-behavior-doorkeeper-tag',
'phabricator-title',
'javelin-leader',
'javelin-websocket',
'javelin-behavior-dashboard-async-panel',
'javelin-behavior-dashboard-tab-panel',
'javelin-quicksand',
'javelin-behavior-quicksand-blacklist',
'javelin-behavior-high-security-warning',
'javelin-behavior-read-only-warning',
'javelin-scrollbar',
'javelin-behavior-scrollbar',
'javelin-behavior-durable-column',
'conpherence-thread-manager',
'javelin-behavior-detect-timezone',
'javelin-behavior-setup-check-https',
'javelin-behavior-aphlict-status',
'javelin-behavior-user-menu',
'phabricator-favicon',
'javelin-behavior-phui-tab-group',
'javelin-behavior-phui-submenu',
'phuix-button-view',
'javelin-behavior-comment-actions',
'phuix-form-control-view',
'phuix-autocomplete',
),
'dark-console.pkg.js' => array(
'javelin-behavior-dark-console',
'phabricator-darklog',
'phabricator-darkmessage',
),
'differential.pkg.css' => array(
'differential-core-view-css',
'differential-changeset-view-css',
'differential-revision-history-css',
'differential-revision-list-css',
'differential-table-of-contents-css',
'differential-revision-comment-css',
'differential-revision-add-comment-css',
'phabricator-object-selector-css',
'phabricator-content-source-view-css',
'inline-comment-summary-css',
'phui-inline-comment-view-css',
'diff-tree-view-css',
'phui-formation-view-css',
),
'differential.pkg.js' => array(
'phabricator-drag-and-drop-file-upload',
'phabricator-shaped-request',
'javelin-behavior-differential-populate',
'javelin-behavior-differential-diff-radios',
'javelin-behavior-aphront-drag-and-drop-textarea',
'javelin-behavior-phabricator-object-selector',
'javelin-behavior-repository-crossreference',
'javelin-behavior-aphront-more',
'phabricator-diff-inline-content-state',
'phabricator-diff-inline',
'phabricator-diff-changeset',
'phabricator-diff-changeset-list',
'phabricator-diff-tree-view',
'phabricator-diff-path-view',
'phuix-formation-view',
'phuix-formation-column-view',
'phuix-formation-flank-view',
'javelin-external-editor-link-engine',
),
'diffusion.pkg.css' => array(
'diffusion-icons-css',
),
'diffusion.pkg.js' => array(
'javelin-behavior-diffusion-pull-lastmodified',
'javelin-behavior-diffusion-commit-graph',
'javelin-behavior-audit-preview',
),
'maniphest.pkg.css' => array(
'maniphest-task-summary-css',
),
'maniphest.pkg.js' => array(
'javelin-behavior-maniphest-batch-selector',
'javelin-behavior-maniphest-list-editor',
),
),
);
diff --git a/resources/sql/autopatches/20140904.macroattach.php b/resources/sql/autopatches/20140904.macroattach.php
index 4761964758..4c4f4e8494 100644
--- a/resources/sql/autopatches/20140904.macroattach.php
+++ b/resources/sql/autopatches/20140904.macroattach.php
@@ -1,26 +1,26 @@
<?php
$table = new PhabricatorFileImageMacro();
foreach (new LiskMigrationIterator($table) as $macro) {
$name = $macro->getName();
echo pht("Linking macro '%s'...", $name)."\n";
$editor = new PhabricatorEdgeEditor();
$phids[] = $macro->getFilePHID();
$phids[] = $macro->getAudioPHID();
$phids = array_filter($phids);
if ($phids) {
foreach ($phids as $phid) {
$editor->addEdge(
$macro->getPHID(),
- PhabricatorObjectHasFileEdgeType::EDGECONST,
+ 25,
$phid);
}
$editor->save();
}
}
echo pht('Done.')."\n";
diff --git a/resources/sql/autopatches/20220510.file.01.attach.sql b/resources/sql/autopatches/20220510.file.01.attach.sql
new file mode 100644
index 0000000000..3ca8bacac4
--- /dev/null
+++ b/resources/sql/autopatches/20220510.file.01.attach.sql
@@ -0,0 +1,9 @@
+CREATE TABLE {$NAMESPACE}_file.file_attachment (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ objectPHID VARBINARY(64) NOT NULL,
+ filePHID VARBINARY(64) NOT NULL,
+ attacherPHID VARBINARY(64),
+ attachmentMode VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20220519.file.02.migrate.sql b/resources/sql/autopatches/20220519.file.02.migrate.sql
new file mode 100644
index 0000000000..89d2d30a82
--- /dev/null
+++ b/resources/sql/autopatches/20220519.file.02.migrate.sql
@@ -0,0 +1,7 @@
+INSERT IGNORE INTO {$NAMESPACE}_file.file_attachment
+ (objectPHID, filePHID, attachmentMode, attacherPHID,
+ dateCreated, dateModified)
+ SELECT dst, src, 'attach', null, dateCreated, dateCreated
+ FROM {$NAMESPACE}_file.edge
+ WHERE type = 26
+ ORDER BY dateCreated ASC;
diff --git a/resources/sql/autopatches/20220525.slowvote.01.mailkey.php b/resources/sql/autopatches/20220525.slowvote.01.mailkey.php
new file mode 100644
index 0000000000..ed0355e105
--- /dev/null
+++ b/resources/sql/autopatches/20220525.slowvote.01.mailkey.php
@@ -0,0 +1,28 @@
+<?php
+
+$poll_table = new PhabricatorSlowvotePoll();
+$poll_conn = $poll_table->establishConnection('w');
+
+$properties_table = new PhabricatorMetaMTAMailProperties();
+$conn = $properties_table->establishConnection('w');
+
+$iterator = new LiskRawMigrationIterator(
+ $poll_conn,
+ $poll_table->getTableName());
+
+foreach ($iterator as $row) {
+ queryfx(
+ $conn,
+ 'INSERT IGNORE INTO %R
+ (objectPHID, mailProperties, dateCreated, dateModified)
+ VALUES
+ (%s, %s, %d, %d)',
+ $properties_table,
+ $row['phid'],
+ phutil_json_encode(
+ array(
+ 'mailKey' => $row['mailKey'],
+ )),
+ PhabricatorTime::getNow(),
+ PhabricatorTime::getNow());
+}
diff --git a/resources/sql/autopatches/20220525.slowvote.02.mailkey-drop.sql b/resources/sql/autopatches/20220525.slowvote.02.mailkey-drop.sql
new file mode 100644
index 0000000000..54e65fd14c
--- /dev/null
+++ b/resources/sql/autopatches/20220525.slowvote.02.mailkey-drop.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_slowvote.slowvote_poll
+ DROP mailKey;
diff --git a/resources/sql/autopatches/20220525.slowvote.03.response-type.sql b/resources/sql/autopatches/20220525.slowvote.03.response-type.sql
new file mode 100644
index 0000000000..8cefc4d578
--- /dev/null
+++ b/resources/sql/autopatches/20220525.slowvote.03.response-type.sql
@@ -0,0 +1,3 @@
+ALTER TABLE {$NAMESPACE}_slowvote.slowvote_poll
+ CHANGE responseVisibility
+ responseVisibility VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20220525.slowvote.04.response-value.sql b/resources/sql/autopatches/20220525.slowvote.04.response-value.sql
new file mode 100644
index 0000000000..b76bcdb784
--- /dev/null
+++ b/resources/sql/autopatches/20220525.slowvote.04.response-value.sql
@@ -0,0 +1,8 @@
+UPDATE {$NAMESPACE}_slowvote.slowvote_poll
+ SET responseVisibility = 'visible' WHERE responseVisibility = '0';
+
+UPDATE {$NAMESPACE}_slowvote.slowvote_poll
+ SET responseVisibility = 'voters' WHERE responseVisibility = '1';
+
+UPDATE {$NAMESPACE}_slowvote.slowvote_poll
+ SET responseVisibility = 'owner' WHERE responseVisibility = '2';
diff --git a/resources/sql/autopatches/20220525.slowvote.05.response-xactions.sql b/resources/sql/autopatches/20220525.slowvote.05.response-xactions.sql
new file mode 100644
index 0000000000..7e819d5893
--- /dev/null
+++ b/resources/sql/autopatches/20220525.slowvote.05.response-xactions.sql
@@ -0,0 +1,23 @@
+UPDATE {$NAMESPACE}_slowvote.slowvote_transaction
+ SET oldValue = '"visible"' WHERE
+ transactionType = 'vote:responses' AND oldValue IN ('0', '"0"');
+
+UPDATE {$NAMESPACE}_slowvote.slowvote_transaction
+ SET newValue = '"visible"' WHERE
+ transactionType = 'vote:responses' AND newValue IN ('0', '"0"');
+
+UPDATE {$NAMESPACE}_slowvote.slowvote_transaction
+ SET oldValue = '"voters"' WHERE
+ transactionType = 'vote:responses' AND oldValue IN ('1', '"1"');
+
+UPDATE {$NAMESPACE}_slowvote.slowvote_transaction
+ SET newValue = '"voters"' WHERE
+ transactionType = 'vote:responses' AND newValue IN ('1', '"1"');
+
+UPDATE {$NAMESPACE}_slowvote.slowvote_transaction
+ SET oldValue = '"owner"' WHERE
+ transactionType = 'vote:responses' AND oldValue IN ('2', '"2"');
+
+UPDATE {$NAMESPACE}_slowvote.slowvote_transaction
+ SET newValue = '"owner"' WHERE
+ transactionType = 'vote:responses' AND newValue IN ('2', '"2"');
diff --git a/resources/sql/autopatches/20220525.slowvote.06.method-type.sql b/resources/sql/autopatches/20220525.slowvote.06.method-type.sql
new file mode 100644
index 0000000000..e2af0643bc
--- /dev/null
+++ b/resources/sql/autopatches/20220525.slowvote.06.method-type.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_slowvote.slowvote_poll
+ CHANGE method method VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20220525.slowvote.07.method-value.sql b/resources/sql/autopatches/20220525.slowvote.07.method-value.sql
new file mode 100644
index 0000000000..04d0f6f430
--- /dev/null
+++ b/resources/sql/autopatches/20220525.slowvote.07.method-value.sql
@@ -0,0 +1,5 @@
+UPDATE {$NAMESPACE}_slowvote.slowvote_poll
+ SET method = 'plurality' WHERE method = '0';
+
+UPDATE {$NAMESPACE}_slowvote.slowvote_poll
+ SET method = 'approval' WHERE method = '1';
diff --git a/resources/sql/autopatches/20220525.slowvote.08.status-type.sql b/resources/sql/autopatches/20220525.slowvote.08.status-type.sql
new file mode 100644
index 0000000000..e8575fc50a
--- /dev/null
+++ b/resources/sql/autopatches/20220525.slowvote.08.status-type.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_slowvote.slowvote_poll
+ CHANGE isClosed status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20220525.slowvote.09.status-value.sql b/resources/sql/autopatches/20220525.slowvote.09.status-value.sql
new file mode 100644
index 0000000000..686e663af7
--- /dev/null
+++ b/resources/sql/autopatches/20220525.slowvote.09.status-value.sql
@@ -0,0 +1,5 @@
+UPDATE {$NAMESPACE}_slowvote.slowvote_poll
+ SET status = 'open' WHERE status = '0';
+
+UPDATE {$NAMESPACE}_slowvote.slowvote_poll
+ SET status = 'closed' WHERE status = '1';
diff --git a/resources/sql/autopatches/20220525.slowvote.10.status-xactions.sql b/resources/sql/autopatches/20220525.slowvote.10.status-xactions.sql
new file mode 100644
index 0000000000..fd06f9ebb2
--- /dev/null
+++ b/resources/sql/autopatches/20220525.slowvote.10.status-xactions.sql
@@ -0,0 +1,19 @@
+UPDATE {$NAMESPACE}_slowvote.slowvote_transaction
+ SET transactionType = 'vote:status'
+ WHERE transactionType = 'vote:close';
+
+UPDATE {$NAMESPACE}_slowvote.slowvote_transaction
+ SET oldValue = '"open"' WHERE
+ transactionType = 'vote:status' AND oldValue IN ('0', '"0"', 'false');
+
+UPDATE {$NAMESPACE}_slowvote.slowvote_transaction
+ SET newValue = '"open"' WHERE
+ transactionType = 'vote:status' AND newValue IN ('0', '"0"', 'false');
+
+UPDATE {$NAMESPACE}_slowvote.slowvote_transaction
+ SET oldValue = '"closed"' WHERE
+ transactionType = 'vote:status' AND oldValue IN ('1', '"1"', 'true');
+
+UPDATE {$NAMESPACE}_slowvote.slowvote_transaction
+ SET newValue = '"closed"' WHERE
+ transactionType = 'vote:status' AND newValue IN ('1', '"1"', 'true');
diff --git a/resources/sql/patches/20130611.migrateoauth.php b/resources/sql/patches/20130611.migrateoauth.php
index 92fe854cfd..9d1490c5c1 100644
--- a/resources/sql/patches/20130611.migrateoauth.php
+++ b/resources/sql/patches/20130611.migrateoauth.php
@@ -1,14 +1,14 @@
<?php
$table = new PhabricatorUser();
$conn = $table->establishConnection('w');
$table_name = 'user_oauthinfo';
foreach (new LiskRawMigrationIterator($conn, $table_name) as $row) {
throw new Exception(
pht(
- 'Your Phabricator install has ancient OAuth account data and is '.
- 'too old to upgrade directly to a modern version of Phabricator. '.
- 'Upgrade to a version released between June 2013 and February 2019 '.
- 'first, then upgrade to a modern version.'));
+ 'This database has ancient OAuth account data and is too old to '.
+ 'upgrade directly to a modern software version. Upgrade to a version '.
+ 'released between June 2013 and February 2019 first, then upgrade to '.
+ 'a modern version.'));
}
diff --git a/resources/sql/patches/20130611.nukeldap.php b/resources/sql/patches/20130611.nukeldap.php
index 0f0b976a58..70a3a3fec4 100644
--- a/resources/sql/patches/20130611.nukeldap.php
+++ b/resources/sql/patches/20130611.nukeldap.php
@@ -1,14 +1,14 @@
<?php
$table = new PhabricatorUser();
$conn = $table->establishConnection('w');
$table_name = 'user_ldapinfo';
foreach (new LiskRawMigrationIterator($conn, $table_name) as $row) {
throw new Exception(
pht(
- 'Your Phabricator install has ancient LDAP account data and is '.
- 'too old to upgrade directly to a modern version of Phabricator. '.
- 'Upgrade to a version released between June 2013 and February 2019 '.
- 'first, then upgrade to a modern version.'));
+ 'This database has ancient LDAP account data and is too old to upgrade '.
+ 'directly to a modern version of the software. Upgrade to a version '.
+ 'released between June 2013 and February 2019 first, then upgrade to a '.
+ 'modern version.'));
}
diff --git a/resources/sql/patches/20130820.file-mailkey-populate.php b/resources/sql/patches/20130820.file-mailkey-populate.php
index 0db10bef58..2cf6371c4d 100644
--- a/resources/sql/patches/20130820.file-mailkey-populate.php
+++ b/resources/sql/patches/20130820.file-mailkey-populate.php
@@ -1,38 +1,38 @@
<?php
-echo pht('Populating Phabricator files with mail keys xactions...')."\n";
+echo pht('Populating files with mail keys...')."\n";
$table = new PhabricatorFile();
$table_name = $table->getTableName();
$conn_w = $table->establishConnection('w');
$conn_w->openTransaction();
$sql = array();
foreach (new LiskRawMigrationIterator($conn_w, 'file') as $row) {
// NOTE: MySQL requires that the INSERT specify all columns which don't
// have default values when configured in strict mode. This query will
// never actually insert rows, but we need to hand it values anyway.
$sql[] = qsprintf(
$conn_w,
'(%d, %s, 0, 0, 0, 0, 0, 0, 0, 0)',
$row['id'],
Filesystem::readRandomCharacters(20));
}
if ($sql) {
foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) {
queryfx(
$conn_w,
'INSERT INTO %T
(id, mailKey, phid, byteSize, storageEngine, storageFormat,
storageHandle, dateCreated, dateModified, metadata) VALUES %LQ '.
'ON DUPLICATE KEY UPDATE mailKey = VALUES(mailKey)',
$table_name,
$chunk);
}
}
$table->saveTransaction();
echo pht('Done.')."\n";
diff --git a/resources/sql/quickstart.sql b/resources/sql/quickstart.sql
index 2aa25e75ba..b2796d31e6 100644
--- a/resources/sql/quickstart.sql
+++ b/resources/sql/quickstart.sql
@@ -1,10942 +1,10651 @@
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_almanac` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `almanac_binding` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`servicePHID` varbinary(64) NOT NULL,
`devicePHID` varbinary(64) NOT NULL,
`interfacePHID` varbinary(64) NOT NULL,
`mailKey` binary(20) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`isDisabled` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_service` (`servicePHID`,`interfacePHID`),
KEY `key_device` (`devicePHID`),
KEY `key_interface` (`interfacePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `almanac_bindingtransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `almanac_device` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`nameIndex` binary(12) NOT NULL,
`mailKey` binary(20) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`isBoundToClusterService` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_name` (`nameIndex`),
KEY `key_nametext` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `almanac_devicename_ngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_object` (`objectID`),
KEY `key_ngram` (`ngram`,`objectID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `almanac_devicetransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `almanac_interface` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`devicePHID` varbinary(64) NOT NULL,
`networkPHID` varbinary(64) NOT NULL,
`address` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`port` int(10) unsigned NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_unique` (`devicePHID`,`networkPHID`,`address`,`port`),
KEY `key_location` (`networkPHID`,`address`,`port`),
KEY `key_device` (`devicePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `almanac_interfacetransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `almanac_namespace` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`nameIndex` binary(12) NOT NULL,
`mailKey` binary(20) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_nameindex` (`nameIndex`),
KEY `key_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `almanac_namespacename_ngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_object` (`objectID`),
KEY `key_ngram` (`ngram`,`objectID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `almanac_namespacetransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `almanac_network` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(128) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`mailKey` binary(20) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `almanac_networkname_ngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_object` (`objectID`),
KEY `key_ngram` (`ngram`,`objectID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `almanac_networktransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `almanac_property` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`fieldIndex` binary(12) NOT NULL,
`fieldName` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`fieldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `objectPHID` (`objectPHID`,`fieldIndex`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `almanac_service` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`nameIndex` binary(12) NOT NULL,
`mailKey` binary(20) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`serviceType` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_name` (`nameIndex`),
KEY `key_nametext` (`name`),
KEY `key_servicetype` (`serviceType`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `almanac_servicename_ngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_object` (`objectID`),
KEY `key_ngram` (`ngram`,`objectID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `almanac_servicetransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_almanac`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_application` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_application`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `application_application` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_application`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `application_applicationtransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_application`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_application`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_audit` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_audit`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `audit_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_audit`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `audit_transaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`commitPHID` varbinary(64) DEFAULT NULL,
`pathID` int(10) unsigned DEFAULT NULL,
`isNewFile` tinyint(1) NOT NULL,
`lineNumber` int(10) unsigned NOT NULL,
`lineLength` int(10) unsigned NOT NULL,
`fixedState` varchar(12) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`hasReplies` tinyint(1) NOT NULL,
`replyToCommentPHID` varbinary(64) DEFAULT NULL,
`legacyCommentID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`),
KEY `key_path` (`pathID`),
KEY `key_draft` (`authorPHID`,`transactionPHID`),
KEY `key_commit` (`commitPHID`),
KEY `key_legacy` (`legacyCommentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_auth` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_auth`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `auth_challenge` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`userPHID` varbinary(64) NOT NULL,
`factorPHID` varbinary(64) NOT NULL,
`sessionPHID` varbinary(64) NOT NULL,
`challengeKey` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`challengeTTL` int(10) unsigned NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`workflowKey` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`responseDigest` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`responseTTL` int(10) unsigned DEFAULT NULL,
`isCompleted` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_issued` (`userPHID`,`challengeTTL`),
KEY `key_collection` (`challengeTTL`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_auth`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `auth_contactnumber` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`contactNumber` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`uniqueKey` binary(12) DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`isPrimary` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_unique` (`uniqueKey`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_auth`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `auth_contactnumbertransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_auth`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `auth_factorconfig` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`userPHID` varbinary(64) NOT NULL,
`factorName` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`factorSecret` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`factorProviderPHID` varbinary(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_user` (`userPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_auth`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `auth_factorprovider` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`providerFactorKey` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_auth`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `auth_factorprovidertransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_auth`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `auth_hmackey` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`keyName` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`keyValue` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_name` (`keyName`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_auth`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `auth_message` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`messageKey` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`messageText` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_type` (`messageKey`),
UNIQUE KEY `key_phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_auth`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `auth_messagetransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_auth`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `auth_password` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`passwordType` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`passwordHash` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isRevoked` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`passwordSalt` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`legacyDigestFormat` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_role` (`objectPHID`,`passwordType`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_auth`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `auth_passwordtransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_auth`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `auth_providerconfig` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`providerClass` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`providerType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`providerDomain` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isEnabled` tinyint(1) NOT NULL,
`shouldAllowLogin` tinyint(1) NOT NULL,
`shouldAllowRegistration` tinyint(1) NOT NULL,
`shouldAllowLink` tinyint(1) NOT NULL,
`shouldAllowUnlink` tinyint(1) NOT NULL,
`shouldTrustEmails` tinyint(1) NOT NULL DEFAULT '0',
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`shouldAutoLogin` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_provider` (`providerType`,`providerDomain`),
KEY `key_class` (`providerClass`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_auth`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `auth_providerconfigtransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_auth`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `auth_sshkey` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`keyType` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`keyBody` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`keyComment` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`keyIndex` binary(12) NOT NULL,
`isTrusted` tinyint(1) NOT NULL,
`isActive` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_activeunique` (`keyIndex`,`isActive`),
KEY `key_object` (`objectPHID`),
KEY `key_active` (`isActive`,`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_auth`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `auth_sshkeytransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_auth`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `auth_temporarytoken` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`tokenResource` varbinary(64) NOT NULL,
`tokenType` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`tokenExpires` int(10) unsigned NOT NULL,
`tokenCode` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`userPHID` varbinary(64) DEFAULT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_token` (`tokenResource`,`tokenType`,`tokenCode`),
KEY `key_expires` (`tokenExpires`),
KEY `key_user` (`userPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_badges` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_badges`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `badges_award` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`badgePHID` varbinary(64) NOT NULL,
`recipientPHID` varbinary(64) NOT NULL,
`awarderPHID` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_badge` (`badgePHID`,`recipientPHID`),
KEY `key_recipient` (`recipientPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_badges`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `badges_badge` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`flavor` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`description` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`icon` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL,
`quality` int(10) unsigned NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`creatorPHID` varbinary(64) NOT NULL,
`mailKey` binary(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_creator` (`creatorPHID`,`dateModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_badges`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `badges_badgename_ngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_object` (`objectID`),
KEY `key_ngram` (`ngram`,`objectID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_badges`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `badges_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_badges`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `badges_transaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_badges`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_badges`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_cache` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_cache`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `cache_general` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`cacheKeyHash` binary(12) NOT NULL,
`cacheKey` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`cacheFormat` varchar(16) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`cacheData` longblob NOT NULL,
`cacheCreated` int(10) unsigned NOT NULL,
`cacheExpires` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_cacheKeyHash` (`cacheKeyHash`),
KEY `key_cacheCreated` (`cacheCreated`),
KEY `key_ttl` (`cacheExpires`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_cache`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `cache_markupcache` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`cacheKey` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`cacheData` longblob NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `cacheKey` (`cacheKey`),
KEY `dateCreated` (`dateCreated`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_calendar` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_calendar`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `calendar_event` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`hostPHID` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`description` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isCancelled` tinyint(1) NOT NULL,
`name` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`mailKey` binary(20) NOT NULL,
`isAllDay` tinyint(1) NOT NULL,
`icon` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isRecurring` tinyint(1) NOT NULL,
`instanceOfEventPHID` varbinary(64) DEFAULT NULL,
`sequenceIndex` int(10) unsigned DEFAULT NULL,
`spacePHID` varbinary(64) DEFAULT NULL,
`isStub` tinyint(1) NOT NULL,
`utcInitialEpoch` int(10) unsigned NOT NULL,
`utcUntilEpoch` int(10) unsigned DEFAULT NULL,
`utcInstanceEpoch` int(10) unsigned DEFAULT NULL,
`parameters` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`importAuthorPHID` varbinary(64) DEFAULT NULL,
`importSourcePHID` varbinary(64) DEFAULT NULL,
`importUIDIndex` binary(12) DEFAULT NULL,
`importUID` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT},
`seriesParentPHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_instance` (`instanceOfEventPHID`,`sequenceIndex`),
UNIQUE KEY `key_rdate` (`instanceOfEventPHID`,`utcInstanceEpoch`),
KEY `key_epoch` (`utcInitialEpoch`,`utcUntilEpoch`),
KEY `key_series` (`seriesParentPHID`,`utcInitialEpoch`),
KEY `key_space` (`spacePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_calendar`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `calendar_event_fdocument` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`epochCreated` int(10) unsigned NOT NULL,
`epochModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_owner` (`ownerPHID`),
KEY `key_created` (`epochCreated`),
KEY `key_modified` (`epochModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_calendar`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `calendar_event_ffield` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`fieldKey` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_calendar`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `calendar_event_fngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_ngram` (`ngram`,`documentID`),
KEY `key_object` (`documentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_calendar`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `calendar_event_fngrams_common` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`needsCollection` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ngram` (`ngram`),
KEY `key_collect` (`needsCollection`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_calendar`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `calendar_eventinvitee` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`eventPHID` varbinary(64) NOT NULL,
`inviteePHID` varbinary(64) NOT NULL,
`inviterPHID` varbinary(64) NOT NULL,
`status` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`availability` varchar(64) COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_event` (`eventPHID`,`inviteePHID`),
KEY `key_invitee` (`inviteePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_calendar`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `calendar_eventtransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_calendar`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `calendar_eventtransaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_calendar`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `calendar_export` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`policyMode` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`queryKey` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`secretKey` binary(20) NOT NULL,
`isDisabled` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_secret` (`secretKey`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_author` (`authorPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_calendar`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `calendar_exporttransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_calendar`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `calendar_externalinvitee` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`nameIndex` binary(12) NOT NULL,
`uri` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`parameters` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`sourcePHID` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_name` (`nameIndex`),
UNIQUE KEY `key_phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_calendar`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `calendar_import` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`engineType` varchar(64) COLLATE {$COLLATE_TEXT} NOT NULL,
`parameters` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDisabled` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`triggerPHID` varbinary(64) DEFAULT NULL,
`triggerFrequency` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_author` (`authorPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_calendar`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `calendar_importlog` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`importPHID` varbinary(64) NOT NULL,
`parameters` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `key_import` (`importPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_calendar`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `calendar_importtransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_calendar`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `calendar_notification` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`eventPHID` varbinary(64) NOT NULL,
`utcInitialEpoch` int(10) unsigned NOT NULL,
`targetPHID` varbinary(64) NOT NULL,
`didNotifyEpoch` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_notify` (`eventPHID`,`utcInitialEpoch`,`targetPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_calendar`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_calendar`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_chatlog` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_chatlog`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `chatlog_channel` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`serviceName` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`serviceType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`channelName` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_channel` (`channelName`,`serviceType`,`serviceName`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_chatlog`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `chatlog_event` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`epoch` int(10) unsigned NOT NULL,
`author` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`type` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`message` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`loggedByPHID` varbinary(64) NOT NULL,
`channelID` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `channel` (`epoch`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_conduit` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_conduit`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `conduit_certificatetoken` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`userPHID` varbinary(64) NOT NULL,
`token` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `userPHID` (`userPHID`),
UNIQUE KEY `token` (`token`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_conduit`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `conduit_methodcalllog` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`connectionID` bigint(20) unsigned DEFAULT NULL,
`method` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`error` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`duration` bigint(20) unsigned NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`callerPHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `key_method` (`method`),
KEY `key_callermethod` (`callerPHID`,`method`),
KEY `key_date` (`dateCreated`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_conduit`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `conduit_token` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`tokenType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`token` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`expires` int(10) unsigned DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_token` (`token`),
KEY `key_object` (`objectPHID`,`tokenType`),
KEY `key_expires` (`expires`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_config` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_config`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `config_entry` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`namespace` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`configKey` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`value` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_name` (`namespace`,`configKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_config`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `config_manualactivity` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`activityType` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`parameters` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_type` (`activityType`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_config`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `config_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_conpherence` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_conpherence`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `conpherence_index` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`threadPHID` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) NOT NULL,
`previousTransactionPHID` varbinary(64) DEFAULT NULL,
`corpus` longtext CHARACTER SET {$CHARSET_FULLTEXT} COLLATE {$COLLATE_FULLTEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_transaction` (`transactionPHID`),
UNIQUE KEY `key_previous` (`previousTransactionPHID`),
KEY `key_thread` (`threadPHID`),
FULLTEXT KEY `key_corpus` (`corpus`)
) ENGINE=MyISAM DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_conpherence`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `conpherence_participant` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`participantPHID` varbinary(64) NOT NULL,
`conpherencePHID` varbinary(64) NOT NULL,
`seenMessageCount` bigint(20) unsigned NOT NULL,
`settings` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `conpherencePHID` (`conpherencePHID`,`participantPHID`),
KEY `key_thread` (`participantPHID`,`conpherencePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_conpherence`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `conpherence_thread` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`title` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`messageCount` bigint(20) unsigned NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`joinPolicy` varbinary(64) NOT NULL,
`mailKey` varchar(20) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`topic` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`profileImagePHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_conpherence`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `conpherence_threadtitle_ngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_object` (`objectID`),
KEY `key_ngram` (`ngram`,`objectID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_conpherence`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `conpherence_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_conpherence`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `conpherence_transaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`conpherencePHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`),
UNIQUE KEY `key_draft` (`authorPHID`,`conpherencePHID`,`transactionPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_conpherence`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_conpherence`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_countdown` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_countdown`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `countdown` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`title` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`epoch` int(10) unsigned NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`spacePHID` varbinary(64) DEFAULT NULL,
`editPolicy` varbinary(64) NOT NULL,
`description` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`mailKey` binary(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_epoch` (`epoch`),
KEY `key_author` (`authorPHID`,`epoch`),
KEY `key_space` (`spacePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_countdown`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `countdown_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_countdown`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `countdown_transaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_countdown`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_countdown`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_daemon` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_daemon`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `daemon_locklog` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`lockName` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`lockReleased` int(10) unsigned DEFAULT NULL,
`lockParameters` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`lockContext` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `key_lock` (`lockName`),
KEY `key_created` (`dateCreated`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_daemon`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `daemon_log` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`daemon` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`host` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`pid` int(10) unsigned NOT NULL,
`argv` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`explicitArgv` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`status` varchar(8) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`runningAsUser` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`daemonID` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_daemonID` (`daemonID`),
KEY `status` (`status`),
KEY `key_modified` (`dateModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_daemon`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `daemon_logevent` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`logID` int(10) unsigned NOT NULL,
`logType` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`message` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`epoch` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `logID` (`logID`,`epoch`),
KEY `key_epoch` (`epoch`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_dashboard` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `dashboard` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`layoutConfig` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`icon` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `dashboard_dashboard_fdocument` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`epochCreated` int(10) unsigned NOT NULL,
`epochModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_owner` (`ownerPHID`),
KEY `key_created` (`epochCreated`),
KEY `key_modified` (`epochModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `dashboard_dashboard_ffield` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`fieldKey` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `dashboard_dashboard_fngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_ngram` (`ngram`,`documentID`),
KEY `key_object` (`documentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `dashboard_dashboard_fngrams_common` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`needsCollection` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ngram` (`ngram`),
KEY `key_collect` (`needsCollection`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `dashboard_panel` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`panelType` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`isArchived` tinyint(1) NOT NULL DEFAULT '0',
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `dashboard_panel_fdocument` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`epochCreated` int(10) unsigned NOT NULL,
`epochModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_owner` (`ownerPHID`),
KEY `key_created` (`epochCreated`),
KEY `key_modified` (`epochModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `dashboard_panel_ffield` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`fieldKey` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `dashboard_panel_fngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_ngram` (`ngram`,`documentID`),
KEY `key_object` (`documentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `dashboard_panel_fngrams_common` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`needsCollection` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ngram` (`ngram`),
KEY `key_collect` (`needsCollection`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `dashboard_paneltransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `dashboard_portal` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `dashboard_portal_fdocument` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`epochCreated` int(10) unsigned NOT NULL,
`epochModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_owner` (`ownerPHID`),
KEY `key_created` (`epochCreated`),
KEY `key_modified` (`epochModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `dashboard_portal_ffield` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`fieldKey` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `dashboard_portal_fngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_ngram` (`ngram`,`documentID`),
KEY `key_object` (`documentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `dashboard_portal_fngrams_common` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`needsCollection` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ngram` (`ngram`),
KEY `key_collect` (`needsCollection`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `dashboard_portaltransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `dashboard_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_dashboard`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_differential` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_affectedpath` (
`repositoryID` int(10) unsigned NOT NULL,
`pathID` int(10) unsigned NOT NULL,
`epoch` int(10) unsigned NOT NULL,
`revisionID` int(10) unsigned NOT NULL,
KEY `repositoryID` (`repositoryID`,`pathID`,`epoch`),
KEY `revisionID` (`revisionID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_changeset` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`diffID` int(10) unsigned NOT NULL,
`oldFile` longblob,
`filename` longblob NOT NULL,
`awayPaths` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT},
`changeType` int(10) unsigned NOT NULL,
`fileType` int(10) unsigned NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT},
`oldProperties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT},
`newProperties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT},
`addLines` int(10) unsigned NOT NULL,
`delLines` int(10) unsigned NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `diffID` (`diffID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_changeset_parse_cache` (
`id` int(10) unsigned NOT NULL,
`cache` longblob NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `dateCreated` (`dateCreated`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_commit` (
`revisionID` int(10) unsigned NOT NULL,
`commitPHID` varbinary(64) NOT NULL,
PRIMARY KEY (`revisionID`,`commitPHID`),
UNIQUE KEY `commitPHID` (`commitPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_customfieldnumericindex` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`indexKey` binary(12) NOT NULL,
`indexValue` bigint(20) NOT NULL,
PRIMARY KEY (`id`),
KEY `key_join` (`objectPHID`,`indexKey`,`indexValue`),
KEY `key_find` (`indexKey`,`indexValue`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_customfieldstorage` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`fieldIndex` binary(12) NOT NULL,
`fieldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `objectPHID` (`objectPHID`,`fieldIndex`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_customfieldstringindex` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`indexKey` binary(12) NOT NULL,
`indexValue` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_join` (`objectPHID`,`indexKey`,`indexValue`(64)),
KEY `key_find` (`indexKey`,`indexValue`(64))
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_diff` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`revisionID` int(10) unsigned DEFAULT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`repositoryPHID` varbinary(64) DEFAULT NULL,
`sourceMachine` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`sourcePath` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`sourceControlSystem` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`sourceControlBaseRevision` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`sourceControlPath` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`lintStatus` int(10) unsigned NOT NULL,
`unitStatus` int(10) unsigned NOT NULL,
`lineCount` int(10) unsigned NOT NULL,
`branch` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`bookmark` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`creationMethod` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`description` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`repositoryUUID` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`commitPHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `revisionID` (`revisionID`),
KEY `key_commit` (`commitPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_diffproperty` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`diffID` int(10) unsigned NOT NULL,
`name` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `diffID` (`diffID`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_difftransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_hiddencomment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`userPHID` varbinary(64) NOT NULL,
`commentID` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_user` (`userPHID`,`commentID`),
KEY `key_comment` (`commentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_hunk` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`changesetID` int(10) unsigned NOT NULL,
`oldOffset` int(10) unsigned NOT NULL,
`oldLen` int(10) unsigned NOT NULL,
`newOffset` int(10) unsigned NOT NULL,
`newLen` int(10) unsigned NOT NULL,
`dataType` binary(4) NOT NULL,
`dataEncoding` varchar(16) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`dataFormat` binary(4) NOT NULL,
`data` longblob NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `key_changeset` (`changesetID`),
KEY `key_created` (`dateCreated`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_reviewer` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`revisionPHID` varbinary(64) NOT NULL,
`reviewerPHID` varbinary(64) NOT NULL,
`reviewerStatus` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`lastActionDiffPHID` varbinary(64) DEFAULT NULL,
`lastCommentDiffPHID` varbinary(64) DEFAULT NULL,
`lastActorPHID` varbinary(64) DEFAULT NULL,
`voidedPHID` varbinary(64) DEFAULT NULL,
`options` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_revision` (`revisionPHID`,`reviewerPHID`),
KEY `key_reviewer` (`reviewerPHID`,`revisionPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_revision` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`phid` varbinary(64) NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`summary` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`testPlan` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`lastReviewerPHID` varbinary(64) DEFAULT NULL,
`lineCount` int(10) unsigned DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`attached` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`mailKey` binary(40) NOT NULL,
`branchName` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`repositoryPHID` varbinary(64) DEFAULT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`activeDiffPHID` varbinary(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `phid` (`phid`),
KEY `authorPHID` (`authorPHID`,`status`),
KEY `repositoryPHID` (`repositoryPHID`),
KEY `key_status` (`status`,`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_revision_fdocument` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`epochCreated` int(10) unsigned NOT NULL,
`epochModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_owner` (`ownerPHID`),
KEY `key_created` (`epochCreated`),
KEY `key_modified` (`epochModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_revision_ffield` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`fieldKey` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_revision_fngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_ngram` (`ngram`,`documentID`),
KEY `key_object` (`documentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_revision_fngrams_common` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`needsCollection` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ngram` (`ngram`),
KEY `key_collect` (`needsCollection`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_revisionhash` (
`revisionID` int(10) unsigned NOT NULL,
`type` binary(4) NOT NULL,
`hash` binary(40) NOT NULL,
KEY `type` (`type`,`hash`),
KEY `revisionID` (`revisionID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `differential_transaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`revisionPHID` varbinary(64) DEFAULT NULL,
`changesetID` int(10) unsigned DEFAULT NULL,
`isNewFile` tinyint(1) NOT NULL,
`lineNumber` int(10) unsigned NOT NULL,
`lineLength` int(10) unsigned NOT NULL,
`fixedState` varchar(12) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`hasReplies` tinyint(1) NOT NULL,
`replyToCommentPHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`),
KEY `key_changeset` (`changesetID`),
KEY `key_draft` (`authorPHID`,`transactionPHID`),
KEY `key_revision` (`revisionPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_differential`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_diviner` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_diviner`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `diviner_liveatom` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`symbolPHID` varbinary(64) NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`atomData` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `symbolPHID` (`symbolPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_diviner`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `diviner_livebook` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`repositoryPHID` varbinary(64) DEFAULT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`configurationData` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`),
UNIQUE KEY `phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_diviner`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `diviner_livebooktransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_diviner`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `diviner_livesymbol` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`bookPHID` varbinary(64) NOT NULL,
`repositoryPHID` varbinary(64) DEFAULT NULL,
`context` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`type` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`atomIndex` int(10) unsigned NOT NULL,
`identityHash` binary(12) NOT NULL,
`graphHash` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`title` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT},
`titleSlugHash` binary(12) DEFAULT NULL,
`groupName` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`summary` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT},
`isDocumentable` tinyint(1) NOT NULL,
`nodeHash` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `identityHash` (`identityHash`),
UNIQUE KEY `phid` (`phid`),
UNIQUE KEY `graphHash` (`graphHash`),
UNIQUE KEY `nodeHash` (`nodeHash`),
KEY `key_slug` (`titleSlugHash`),
KEY `bookPHID` (`bookPHID`,`type`,`name`(64),`context`(64),`atomIndex`),
KEY `name` (`name`(64))
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_diviner`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_diviner`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_doorkeeper` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_doorkeeper`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `doorkeeper_externalobject` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`objectKey` binary(12) NOT NULL,
`applicationType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`applicationDomain` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`objectType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`objectID` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`objectURI` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`importerPHID` varbinary(64) DEFAULT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_object` (`objectKey`),
KEY `key_full` (`applicationType`,`applicationDomain`,`objectType`,`objectID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_doorkeeper`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_doorkeeper`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_draft` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_draft`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `draft` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`authorPHID` varbinary(64) NOT NULL,
`draftKey` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`draft` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `authorPHID` (`authorPHID`,`draftKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_draft`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `draft_versioneddraft` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`version` int(10) unsigned NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`,`authorPHID`,`version`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_drydock` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_drydock`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `drydock_authorization` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`blueprintPHID` varbinary(64) NOT NULL,
`blueprintAuthorizationState` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`objectAuthorizationState` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_unique` (`objectPHID`,`blueprintPHID`),
KEY `key_blueprint` (`blueprintPHID`,`blueprintAuthorizationState`),
KEY `key_object` (`objectPHID`,`objectAuthorizationState`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_drydock`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `drydock_blueprint` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`className` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`blueprintName` varchar(255) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`details` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`isDisabled` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_drydock`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `drydock_blueprintname_ngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_object` (`objectID`),
KEY `key_ngram` (`ngram`,`objectID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_drydock`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `drydock_blueprinttransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_drydock`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `drydock_command` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`authorPHID` varbinary(64) NOT NULL,
`targetPHID` varbinary(64) NOT NULL,
`command` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isConsumed` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_target` (`targetPHID`,`isConsumed`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_drydock`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `drydock_lease` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`until` int(10) unsigned DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`attributes` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`resourceType` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`resourcePHID` varbinary(64) DEFAULT NULL,
`authorizingPHID` varbinary(64) NOT NULL,
`acquiredEpoch` int(10) unsigned DEFAULT NULL,
`activatedEpoch` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_resource` (`resourcePHID`,`status`),
KEY `key_status` (`status`),
KEY `key_owner` (`ownerPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_drydock`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `drydock_log` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`epoch` int(10) unsigned NOT NULL,
`blueprintPHID` varbinary(64) DEFAULT NULL,
`resourcePHID` varbinary(64) DEFAULT NULL,
`leasePHID` varbinary(64) DEFAULT NULL,
`type` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`operationPHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `epoch` (`epoch`),
KEY `key_blueprint` (`blueprintPHID`,`type`),
KEY `key_resource` (`resourcePHID`,`type`),
KEY `key_lease` (`leasePHID`,`type`),
KEY `key_operation` (`operationPHID`,`type`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_drydock`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `drydock_repositoryoperation` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`repositoryPHID` varbinary(64) NOT NULL,
`repositoryTarget` longblob NOT NULL,
`operationType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`operationState` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`isDismissed` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`),
KEY `key_repository` (`repositoryPHID`,`operationState`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_drydock`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `drydock_resource` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`type` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`attributes` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`capabilities` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`blueprintPHID` varbinary(64) NOT NULL,
`until` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_type` (`type`,`status`),
KEY `key_blueprint` (`blueprintPHID`,`status`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_drydock`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `drydock_slotlock` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ownerPHID` varbinary(64) NOT NULL,
`lockIndex` binary(12) NOT NULL,
`lockKey` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_lock` (`lockIndex`),
KEY `key_owner` (`ownerPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_drydock`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_drydock`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_fact` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_fact`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `fact_aggregate` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`factType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`valueX` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `factType` (`factType`,`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_fact`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `fact_chart` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`chartKey` binary(12) NOT NULL,
`chartParameters` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_chart` (`chartKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_fact`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `fact_cursor` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`position` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_fact`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `fact_intdatapoint` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`keyID` int(10) unsigned NOT NULL,
`objectID` int(10) unsigned NOT NULL,
`dimensionID` int(10) unsigned DEFAULT NULL,
`value` bigint(20) NOT NULL,
`epoch` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `key_dimension` (`keyID`,`dimensionID`),
KEY `key_object` (`objectID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_fact`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `fact_keydimension` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`factKey` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_factkey` (`factKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_fact`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `fact_objectdimension` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_fact`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `fact_raw` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`factType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`objectA` varbinary(64) NOT NULL,
`valueX` bigint(20) NOT NULL,
`valueY` bigint(20) NOT NULL,
`epoch` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `objectPHID` (`objectPHID`),
KEY `factType` (`factType`,`epoch`),
KEY `factType_2` (`factType`,`objectA`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_feed` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_feed`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `feed_storydata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`chronologicalKey` bigint(20) unsigned NOT NULL,
`storyType` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`storyData` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `chronologicalKey` (`chronologicalKey`),
UNIQUE KEY `phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_feed`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `feed_storynotification` (
`userPHID` varbinary(64) NOT NULL,
`primaryObjectPHID` varbinary(64) NOT NULL,
`chronologicalKey` bigint(20) unsigned NOT NULL,
`hasViewed` tinyint(1) NOT NULL,
UNIQUE KEY `userPHID` (`userPHID`,`chronologicalKey`),
KEY `userPHID_2` (`userPHID`,`hasViewed`,`primaryObjectPHID`),
KEY `key_object` (`primaryObjectPHID`),
KEY `key_chronological` (`chronologicalKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_feed`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `feed_storyreference` (
`objectPHID` varbinary(64) NOT NULL,
`chronologicalKey` bigint(20) unsigned NOT NULL,
UNIQUE KEY `objectPHID` (`objectPHID`,`chronologicalKey`),
KEY `chronologicalKey` (`chronologicalKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_file` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_file`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_file`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_file`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `file` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} DEFAULT NULL,
`mimeType` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`byteSize` bigint(20) unsigned NOT NULL,
`storageEngine` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`storageFormat` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`storageHandle` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`secretKey` binary(20) DEFAULT NULL,
`contentHash` binary(64) DEFAULT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`ttl` int(10) unsigned DEFAULT NULL,
`isExplicitUpload` tinyint(1) DEFAULT '1',
`mailKey` binary(20) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`isPartial` tinyint(1) NOT NULL DEFAULT '0',
`builtinKey` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`isDeleted` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `phid` (`phid`),
UNIQUE KEY `key_builtin` (`builtinKey`),
KEY `authorPHID` (`authorPHID`),
KEY `contentHash` (`contentHash`),
KEY `key_ttl` (`ttl`),
KEY `key_dateCreated` (`dateCreated`),
KEY `key_partial` (`authorPHID`,`isPartial`),
KEY `key_engine` (`storageEngine`,`storageHandle`(64))
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_file`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `file_chunk` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`chunkHandle` binary(12) NOT NULL,
`byteStart` bigint(20) unsigned NOT NULL,
`byteEnd` bigint(20) unsigned NOT NULL,
`dataFilePHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `key_file` (`chunkHandle`,`byteStart`,`byteEnd`),
KEY `key_data` (`dataFilePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_file`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `file_externalrequest` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`filePHID` varbinary(64) DEFAULT NULL,
`ttl` int(10) unsigned NOT NULL,
`uri` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`uriIndex` binary(12) NOT NULL,
`isSuccessful` tinyint(1) NOT NULL,
`responseMessage` longtext COLLATE {$COLLATE_TEXT},
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_uriindex` (`uriIndex`),
KEY `key_ttl` (`ttl`),
KEY `key_file` (`filePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_file`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `file_filename_ngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_object` (`objectID`),
KEY `key_ngram` (`ngram`,`objectID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_file`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `file_imagemacro` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`filePHID` varbinary(64) NOT NULL,
`name` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`isDisabled` tinyint(1) NOT NULL,
`audioPHID` varbinary(64) DEFAULT NULL,
`audioBehavior` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`mailKey` binary(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `name` (`name`),
KEY `key_disabled` (`isDisabled`),
KEY `key_dateCreated` (`dateCreated`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_file`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `file_storageblob` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longblob NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_file`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `file_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_file`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `file_transaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_file`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `file_transformedfile` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`originalPHID` varbinary(64) NOT NULL,
`transform` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`transformedPHID` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `originalPHID` (`originalPHID`,`transform`),
KEY `transformedPHID` (`transformedPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_file`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `macro_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_file`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `macro_transaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_flag` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_flag`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `flag` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ownerPHID` varbinary(64) NOT NULL,
`type` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`reasonPHID` varbinary(64) NOT NULL,
`color` int(10) unsigned NOT NULL,
`note` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ownerPHID` (`ownerPHID`,`type`,`objectPHID`),
KEY `objectPHID` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_fund` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_fund`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_fund`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_fund`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `fund_backer` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`initiativePHID` varbinary(64) NOT NULL,
`backerPHID` varbinary(64) NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`amountAsCurrency` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_initiative` (`initiativePHID`),
KEY `key_backer` (`backerPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_fund`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `fund_backertransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_fund`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `fund_initiative` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`ownerPHID` varbinary(64) NOT NULL,
`description` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`merchantPHID` varbinary(64) DEFAULT NULL,
`risks` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`totalAsCurrency` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`mailKey` binary(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_status` (`status`),
KEY `key_owner` (`ownerPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_fund`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `fund_initiative_fdocument` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`epochCreated` int(10) unsigned NOT NULL,
`epochModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_owner` (`ownerPHID`),
KEY `key_created` (`epochCreated`),
KEY `key_modified` (`epochModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_fund`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `fund_initiative_ffield` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`fieldKey` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_fund`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `fund_initiative_fngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_ngram` (`ngram`,`documentID`),
KEY `key_object` (`documentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_fund`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `fund_initiative_fngrams_common` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`needsCollection` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ngram` (`ngram`),
KEY `key_collect` (`needsCollection`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_fund`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `fund_initiativetransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_fund`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `fund_initiativetransaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_harbormaster` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_build` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`buildablePHID` varbinary(64) NOT NULL,
`buildPlanPHID` varbinary(64) NOT NULL,
`buildStatus` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`buildGeneration` int(10) unsigned NOT NULL DEFAULT '0',
`planAutoKey` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`buildParameters` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`initiatorPHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_planautokey` (`buildablePHID`,`planAutoKey`),
KEY `key_buildable` (`buildablePHID`),
KEY `key_plan` (`buildPlanPHID`),
KEY `key_status` (`buildStatus`),
KEY `key_initiator` (`initiatorPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_buildable` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`buildablePHID` varbinary(64) NOT NULL,
`containerPHID` varbinary(64) DEFAULT NULL,
`buildableStatus` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`isManualBuildable` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_buildable` (`buildablePHID`),
KEY `key_container` (`containerPHID`),
KEY `key_manual` (`isManualBuildable`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_buildabletransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_buildartifact` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`artifactType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`artifactIndex` binary(12) NOT NULL,
`artifactKey` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`artifactData` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`buildTargetPHID` varbinary(64) NOT NULL,
`isReleased` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_artifact` (`artifactType`,`artifactIndex`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_garbagecollect` (`artifactType`,`dateCreated`),
KEY `key_target` (`buildTargetPHID`,`artifactType`),
KEY `key_index` (`artifactIndex`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_buildcommand` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`authorPHID` varbinary(64) NOT NULL,
`targetPHID` varbinary(64) NOT NULL,
`command` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `key_target` (`targetPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_buildlintmessage` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`buildTargetPHID` varbinary(64) NOT NULL,
`path` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`line` int(10) unsigned DEFAULT NULL,
`characterOffset` int(10) unsigned DEFAULT NULL,
`code` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`severity` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `key_target` (`buildTargetPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_buildlog` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`logSource` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`logType` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`duration` int(10) unsigned DEFAULT NULL,
`live` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`buildTargetPHID` varbinary(64) NOT NULL,
`filePHID` varbinary(64) DEFAULT NULL,
`byteLength` bigint(20) unsigned NOT NULL,
`chunkFormat` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`lineMap` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_buildtarget` (`buildTargetPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_buildlogchunk` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`logID` int(10) unsigned NOT NULL,
`encoding` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`size` int(10) unsigned DEFAULT NULL,
`chunk` longblob NOT NULL,
`headOffset` bigint(20) unsigned NOT NULL,
`tailOffset` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `key_offset` (`logID`,`headOffset`,`tailOffset`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_buildmessage` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`authorPHID` varbinary(64) NOT NULL,
`receiverPHID` varbinary(64) NOT NULL,
`type` varchar(16) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isConsumed` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `key_receiver` (`receiverPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_buildplan` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(128) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`planStatus` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`planAutoKey` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_planautokey` (`planAutoKey`),
KEY `key_status` (`planStatus`),
KEY `key_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_buildplanname_ngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_object` (`objectID`),
KEY `key_ngram` (`ngram`,`objectID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_buildplantransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_buildstep` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`buildPlanPHID` varbinary(64) NOT NULL,
`className` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`details` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`sequence` int(10) unsigned NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`description` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`stepAutoKey` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_stepautokey` (`buildPlanPHID`,`stepAutoKey`),
KEY `key_plan` (`buildPlanPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_buildsteptransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_buildtarget` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`buildPHID` varbinary(64) NOT NULL,
`buildStepPHID` varbinary(64) NOT NULL,
`className` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`details` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`variables` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`targetStatus` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`dateStarted` int(10) unsigned DEFAULT NULL,
`dateCompleted` int(10) unsigned DEFAULT NULL,
`buildGeneration` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_build` (`buildPHID`,`buildStepPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_buildtransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_buildunitmessage` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`buildTargetPHID` varbinary(64) NOT NULL,
`engine` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`namespace` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`result` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`duration` double DEFAULT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`nameIndex` binary(12) NOT NULL,
PRIMARY KEY (`id`),
KEY `key_target` (`buildTargetPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_object` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_scratchtable` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`bigData` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT},
`nonmutableData` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `data` (`data`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `harbormaster_string` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`stringIndex` binary(12) NOT NULL,
`stringValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_string` (`stringIndex`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_harbormaster`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `lisk_counter` (
`counterName` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`counterValue` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`counterName`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_herald` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_herald`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_herald`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_herald`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `herald_action` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ruleID` int(10) unsigned NOT NULL,
`action` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`target` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `ruleID` (`ruleID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_herald`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `herald_condition` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ruleID` int(10) unsigned NOT NULL,
`fieldName` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`fieldCondition` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`value` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `ruleID` (`ruleID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_herald`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `herald_rule` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`contentType` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`mustMatchAll` tinyint(1) NOT NULL,
`configVersion` int(10) unsigned NOT NULL DEFAULT '1',
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`repetitionPolicy` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`ruleType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`phid` varbinary(64) NOT NULL,
`isDisabled` int(10) unsigned NOT NULL DEFAULT '0',
`triggerObjectPHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_trigger` (`triggerObjectPHID`),
KEY `key_name` (`name`(128)),
KEY `key_author` (`authorPHID`),
KEY `key_ruletype` (`ruleType`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_herald`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `herald_ruleapplied` (
`ruleID` int(10) unsigned NOT NULL,
`phid` varbinary(64) NOT NULL,
PRIMARY KEY (`ruleID`,`phid`),
KEY `phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_herald`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `herald_ruletransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_herald`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `herald_savedheader` (
`phid` varbinary(64) NOT NULL,
`header` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_herald`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `herald_transcript` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`time` int(10) unsigned NOT NULL,
`host` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`duration` double NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`dryRun` tinyint(1) NOT NULL,
`objectTranscript` longblob NOT NULL,
`ruleTranscripts` longblob NOT NULL,
`conditionTranscripts` longblob NOT NULL,
`applyTranscripts` longblob NOT NULL,
`garbageCollected` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `phid` (`phid`),
KEY `objectPHID` (`objectPHID`),
KEY `garbageCollected` (`garbageCollected`,`time`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_herald`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `herald_webhook` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`webhookURI` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`hmacKey` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_herald`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `herald_webhookrequest` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`webhookPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`lastRequestResult` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`lastRequestEpoch` int(10) unsigned NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_ratelimit` (`webhookPHID`,`lastRequestResult`,`lastRequestEpoch`),
KEY `key_collect` (`dateCreated`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_herald`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `herald_webhooktransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_legalpad` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_legalpad`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_legalpad`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_legalpad`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `legalpad_document` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`title` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contributorCount` int(10) unsigned NOT NULL DEFAULT '0',
`recentContributorPHIDs` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`creatorPHID` varbinary(64) NOT NULL,
`versions` int(10) unsigned NOT NULL DEFAULT '0',
`documentBodyPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`mailKey` binary(20) NOT NULL,
`signatureType` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`preamble` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`requireSignature` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_creator` (`creatorPHID`,`dateModified`),
KEY `key_required` (`requireSignature`,`dateModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_legalpad`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `legalpad_documentbody` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`creatorPHID` varbinary(64) NOT NULL,
`documentPHID` varbinary(64) NOT NULL,
`version` int(10) unsigned NOT NULL DEFAULT '0',
`title` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`text` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT},
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_document` (`documentPHID`,`version`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_legalpad`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `legalpad_documentsignature` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentPHID` varbinary(64) NOT NULL,
`documentVersion` int(10) unsigned NOT NULL DEFAULT '0',
`signatureType` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`signerPHID` varbinary(64) DEFAULT NULL,
`signerName` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`signerEmail` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`signatureData` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`secretKey` binary(20) NOT NULL,
`verified` tinyint(1) DEFAULT '0',
`isExemption` tinyint(1) NOT NULL DEFAULT '0',
`exemptionPHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `key_signer` (`signerPHID`,`dateModified`),
KEY `secretKey` (`secretKey`),
KEY `key_document` (`documentPHID`,`signerPHID`,`documentVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_legalpad`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `legalpad_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_legalpad`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `legalpad_transaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`documentID` int(10) unsigned DEFAULT NULL,
`lineNumber` int(10) unsigned NOT NULL,
`lineLength` int(10) unsigned NOT NULL,
`fixedState` varchar(12) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`hasReplies` tinyint(1) NOT NULL,
`replyToCommentPHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`),
UNIQUE KEY `key_draft` (`authorPHID`,`documentID`,`transactionPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_maniphest` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_maniphest`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_maniphest`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_maniphest`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `maniphest_customfieldnumericindex` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`indexKey` binary(12) NOT NULL,
`indexValue` bigint(20) NOT NULL,
PRIMARY KEY (`id`),
KEY `key_join` (`objectPHID`,`indexKey`,`indexValue`),
KEY `key_find` (`indexKey`,`indexValue`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_maniphest`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `maniphest_customfieldstorage` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`fieldIndex` binary(12) NOT NULL,
`fieldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `objectPHID` (`objectPHID`,`fieldIndex`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_maniphest`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `maniphest_customfieldstringindex` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`indexKey` binary(12) NOT NULL,
`indexValue` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_join` (`objectPHID`,`indexKey`,`indexValue`(64)),
KEY `key_find` (`indexKey`,`indexValue`(64))
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_maniphest`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `maniphest_nameindex` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`indexedObjectPHID` varbinary(64) NOT NULL,
`indexedObjectName` varchar(128) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`indexedObjectPHID`),
KEY `key_name` (`indexedObjectName`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_maniphest`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `maniphest_task` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`status` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`priority` int(10) unsigned NOT NULL,
`title` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`description` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`mailKey` binary(20) NOT NULL,
`ownerOrdering` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`originalEmailSource` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`subpriority` double NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`spacePHID` varbinary(64) DEFAULT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`points` double DEFAULT NULL,
`bridgedObjectPHID` varbinary(64) DEFAULT NULL,
`subtype` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`closedEpoch` int(10) unsigned DEFAULT NULL,
`closerPHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `phid` (`phid`),
UNIQUE KEY `key_bridgedobject` (`bridgedObjectPHID`),
KEY `priority` (`priority`,`status`),
KEY `status` (`status`),
KEY `ownerPHID` (`ownerPHID`,`status`),
KEY `authorPHID` (`authorPHID`,`status`),
KEY `ownerOrdering` (`ownerOrdering`),
KEY `priority_2` (`priority`,`subpriority`),
KEY `key_dateCreated` (`dateCreated`),
KEY `key_dateModified` (`dateModified`),
KEY `key_title` (`title`(64)),
KEY `key_subtype` (`subtype`),
KEY `key_closed` (`closedEpoch`),
KEY `key_closer` (`closerPHID`,`closedEpoch`),
KEY `key_space` (`spacePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_maniphest`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `maniphest_task_fdocument` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`epochCreated` int(10) unsigned NOT NULL,
`epochModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_owner` (`ownerPHID`),
KEY `key_created` (`epochCreated`),
KEY `key_modified` (`epochModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_maniphest`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `maniphest_task_ffield` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`fieldKey` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_maniphest`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `maniphest_task_fngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_ngram` (`ngram`,`documentID`),
KEY `key_object` (`documentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_maniphest`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `maniphest_task_fngrams_common` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`needsCollection` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ngram` (`ngram`),
KEY `key_collect` (`needsCollection`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_maniphest`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `maniphest_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_maniphest`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `maniphest_transaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_meta_data` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_meta_data`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `hoststate` (
`stateKey` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`stateValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`stateKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_meta_data`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `patch_status` (
`patch` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`applied` int(10) unsigned NOT NULL,
`duration` bigint(20) unsigned DEFAULT NULL,
PRIMARY KEY (`patch`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
INSERT INTO `patch_status` VALUES ('phabricator:000.project.sql',1556231684,NULL),('phabricator:0000.legacy.sql',1556231684,NULL),('phabricator:001.maniphest_projects.sql',1556231684,NULL),('phabricator:002.oauth.sql',1556231684,NULL),('phabricator:003.more_oauth.sql',1556231684,NULL),('phabricator:004.daemonrepos.sql',1556231684,NULL),('phabricator:005.workers.sql',1556231684,NULL),('phabricator:006.repository.sql',1556231684,NULL),('phabricator:007.daemonlog.sql',1556231684,NULL),('phabricator:008.repoopt.sql',1556231684,NULL),('phabricator:009.repo_summary.sql',1556231684,NULL),('phabricator:010.herald.sql',1556231684,NULL),('phabricator:011.badcommit.sql',1556231684,NULL),('phabricator:012.dropphidtype.sql',1556231684,NULL),('phabricator:013.commitdetail.sql',1556231684,NULL),('phabricator:014.shortcuts.sql',1556231684,NULL),('phabricator:015.preferences.sql',1556231684,NULL),('phabricator:016.userrealnameindex.sql',1556231684,NULL),('phabricator:017.sessionkeys.sql',1556231684,NULL),('phabricator:018.owners.sql',1556231684,NULL),('phabricator:019.arcprojects.sql',1556231684,NULL),('phabricator:020.pathcapital.sql',1556231684,NULL),('phabricator:021.xhpastview.sql',1556231684,NULL),('phabricator:022.differentialcommit.sql',1556231684,NULL),('phabricator:023.dxkeys.sql',1556231685,NULL),('phabricator:024.mlistkeys.sql',1556231685,NULL),('phabricator:025.commentopt.sql',1556231685,NULL),('phabricator:026.diffpropkey.sql',1556231685,NULL),('phabricator:027.metamtakeys.sql',1556231685,NULL),('phabricator:028.systemagent.sql',1556231685,NULL),('phabricator:029.cursors.sql',1556231685,NULL),('phabricator:030.imagemacro.sql',1556231685,NULL),('phabricator:031.workerrace.sql',1556231685,NULL),('phabricator:032.viewtime.sql',1556231685,NULL),('phabricator:033.privtest.sql',1556231685,NULL),('phabricator:034.savedheader.sql',1556231685,NULL),('phabricator:035.proxyimage.sql',1556231685,NULL),('phabricator:036.mailkey.sql',1556231685,NULL),('phabricator:037.setuptest.sql',1556231685,NULL),('phabricator:038.admin.sql',1556231685,NULL),('phabricator:039.userlog.sql',1556231685,NULL),('phabricator:040.transform.sql',1556231685,NULL),('phabricator:041.heraldrepetition.sql',1556231685,NULL),('phabricator:042.commentmetadata.sql',1556231685,NULL),('phabricator:043.pastebin.sql',1556231685,NULL),('phabricator:044.countdown.sql',1556231685,NULL),('phabricator:045.timezone.sql',1556231685,NULL),('phabricator:046.conduittoken.sql',1556231685,NULL),('phabricator:047.projectstatus.sql',1556231685,NULL),('phabricator:048.relationshipkeys.sql',1556231685,NULL),('phabricator:049.projectowner.sql',1556231685,NULL),('phabricator:050.taskdenormal.sql',1556231685,NULL),('phabricator:051.projectfilter.sql',1556231685,NULL),('phabricator:052.pastelanguage.sql',1556231685,NULL),('phabricator:053.feed.sql',1556231685,NULL),('phabricator:054.subscribers.sql',1556231685,NULL),('phabricator:055.add_author_to_files.sql',1556231685,NULL),('phabricator:056.slowvote.sql',1556231685,NULL),('phabricator:057.parsecache.sql',1556231685,NULL),('phabricator:058.missingkeys.sql',1556231685,NULL),('phabricator:059.engines.php',1556231685,NULL),('phabricator:060.phriction.sql',1556231685,NULL),('phabricator:061.phrictioncontent.sql',1556231685,NULL),('phabricator:062.phrictionmenu.sql',1556231685,NULL),('phabricator:063.pasteforks.sql',1556231685,NULL),('phabricator:064.subprojects.sql',1556231685,NULL),('phabricator:065.sshkeys.sql',1556231685,NULL),('phabricator:066.phrictioncontent.sql',1556231685,NULL),('phabricator:067.preferences.sql',1556231685,NULL),('phabricator:068.maniphestauxiliarystorage.sql',1556231685,NULL),('phabricator:069.heraldxscript.sql',1556231685,NULL),('phabricator:070.differentialaux.sql',1556231685,NULL),('phabricator:071.contentsource.sql',1556231685,NULL),('phabricator:072.blamerevert.sql',1556231685,NULL),('phabricator:073.reposymbols.sql',1556231685,NULL),('phabricator:074.affectedpath.sql',1556231685,NULL),('phabricator:075.revisionhash.sql',1556231685,NULL),('phabricator:076.indexedlanguages.sql',1556231685,NULL),('phabricator:077.originalemail.sql',1556231685,NULL),('phabricator:078.nametoken.sql',1556231685,NULL),('phabricator:079.nametokenindex.php',1556231685,NULL),('phabricator:080.filekeys.sql',1556231685,NULL),('phabricator:081.filekeys.php',1556231685,NULL),('phabricator:082.xactionkey.sql',1556231685,NULL),('phabricator:083.dxviewtime.sql',1556231685,NULL),('phabricator:084.pasteauthorkey.sql',1556231685,NULL),('phabricator:085.packagecommitrelationship.sql',1556231685,NULL),('phabricator:086.formeraffil.sql',1556231685,NULL),('phabricator:087.phrictiondelete.sql',1556231685,NULL),('phabricator:088.audit.sql',1556231685,NULL),('phabricator:089.projectwiki.sql',1556231685,NULL),('phabricator:090.forceuniqueprojectnames.php',1556231685,NULL),('phabricator:091.uniqueslugkey.sql',1556231686,NULL),('phabricator:092.dropgithubnotification.sql',1556231686,NULL),('phabricator:093.gitremotes.php',1556231686,NULL),('phabricator:094.phrictioncolumn.sql',1556231686,NULL),('phabricator:095.directory.sql',1556231686,NULL),('phabricator:096.filename.sql',1556231686,NULL),('phabricator:097.heraldruletypes.sql',1556231686,NULL),('phabricator:098.heraldruletypemigration.php',1556231686,NULL),('phabricator:099.drydock.sql',1556231686,NULL),('phabricator:100.projectxaction.sql',1556231686,NULL),('phabricator:101.heraldruleapplied.sql',1556231686,NULL),('phabricator:102.heraldcleanup.php',1556231686,NULL),('phabricator:103.heraldedithistory.sql',1556231686,NULL),('phabricator:104.searchkey.sql',1556231686,NULL),('phabricator:105.mimetype.sql',1556231686,NULL),('phabricator:106.chatlog.sql',1556231686,NULL),('phabricator:107.oauthserver.sql',1556231686,NULL),('phabricator:108.oauthscope.sql',1556231686,NULL),('phabricator:109.oauthclientphidkey.sql',1556231686,NULL),('phabricator:110.commitaudit.sql',1556231686,NULL),('phabricator:111.commitauditmigration.php',1556231686,NULL),('phabricator:112.oauthaccesscoderedirecturi.sql',1556231686,NULL),('phabricator:113.lastreviewer.sql',1556231686,NULL),('phabricator:114.auditrequest.sql',1556231686,NULL),('phabricator:115.prepareutf8.sql',1556231686,NULL),('phabricator:116.utf8-backup-first-expect-wait.sql',1556231688,NULL),('phabricator:117.repositorydescription.php',1556231688,NULL),('phabricator:118.auditinline.sql',1556231688,NULL),('phabricator:119.filehash.sql',1556231688,NULL),('phabricator:120.noop.sql',1556231688,NULL),('phabricator:121.drydocklog.sql',1556231688,NULL),('phabricator:122.flag.sql',1556231688,NULL),('phabricator:123.heraldrulelog.sql',1556231688,NULL),('phabricator:124.subpriority.sql',1556231688,NULL),('phabricator:125.ipv6.sql',1556231688,NULL),('phabricator:126.edges.sql',1556231688,NULL),('phabricator:127.userkeybody.sql',1556231688,NULL),('phabricator:128.phabricatorcom.sql',1556231688,NULL),('phabricator:129.savedquery.sql',1556231688,NULL),('phabricator:130.denormalrevisionquery.sql',1556231688,NULL),('phabricator:131.migraterevisionquery.php',1556231688,NULL),('phabricator:132.phame.sql',1556231688,NULL),('phabricator:133.imagemacro.sql',1556231688,NULL),('phabricator:134.emptysearch.sql',1556231688,NULL),('phabricator:135.datecommitted.sql',1556231688,NULL),('phabricator:136.sex.sql',1556231688,NULL),('phabricator:137.auditmetadata.sql',1556231688,NULL),('phabricator:138.notification.sql',1556231688,NULL),('phabricator:20121209.pholioxactions.sql',1556231689,NULL),('phabricator:20121209.xmacroadd.sql',1556231689,NULL),('phabricator:20121209.xmacromigrate.php',1556231689,NULL),('phabricator:20121209.xmacromigratekey.sql',1556231689,NULL),('phabricator:20121220.generalcache.sql',1556231689,NULL),('phabricator:20121226.config.sql',1556231689,NULL),('phabricator:20130101.confxaction.sql',1556231689,NULL),('phabricator:20130102.metamtareceivedmailmessageidhash.sql',1556231689,NULL),('phabricator:20130103.filemetadata.sql',1556231689,NULL),('phabricator:20130111.conpherence.sql',1556231689,NULL),('phabricator:20130127.altheraldtranscript.sql',1556231689,NULL),('phabricator:20130131.conpherencepics.sql',1556231689,NULL),('phabricator:20130201.revisionunsubscribed.php',1556231689,NULL),('phabricator:20130201.revisionunsubscribed.sql',1556231689,NULL),('phabricator:20130214.chatlogchannel.sql',1556231689,NULL),('phabricator:20130214.chatlogchannelid.sql',1556231689,NULL),('phabricator:20130214.token.sql',1556231689,NULL),('phabricator:20130215.phabricatorfileaddttl.sql',1556231689,NULL),('phabricator:20130217.cachettl.sql',1556231689,NULL),('phabricator:20130218.longdaemon.sql',1556231689,NULL),('phabricator:20130218.updatechannelid.php',1556231689,NULL),('phabricator:20130219.commitsummary.sql',1556231689,NULL),('phabricator:20130219.commitsummarymig.php',1556231689,NULL),('phabricator:20130222.dropchannel.sql',1556231689,NULL),('phabricator:20130226.commitkey.sql',1556231689,NULL),('phabricator:20130304.lintauthor.sql',1556231689,NULL),('phabricator:20130310.xactionmeta.sql',1556231689,NULL),('phabricator:20130317.phrictionedge.sql',1556231689,NULL),('phabricator:20130319.conpherence.sql',1556231689,NULL),('phabricator:20130319.phabricatorfileexplicitupload.sql',1556231689,NULL),('phabricator:20130320.phlux.sql',1556231689,NULL),('phabricator:20130321.token.sql',1556231689,NULL),('phabricator:20130322.phortune.sql',1556231689,NULL),('phabricator:20130323.phortunepayment.sql',1556231689,NULL),('phabricator:20130324.phortuneproduct.sql',1556231689,NULL),('phabricator:20130330.phrequent.sql',1556231689,NULL),('phabricator:20130403.conpherencecache.sql',1556231689,NULL),('phabricator:20130403.conpherencecachemig.php',1556231689,NULL),('phabricator:20130409.commitdrev.php',1556231689,NULL),('phabricator:20130417.externalaccount.sql',1556231689,NULL),('phabricator:20130423.conpherenceindices.sql',1556231690,NULL),('phabricator:20130423.phortunepaymentrevised.sql',1556231690,NULL),('phabricator:20130423.updateexternalaccount.sql',1556231689,NULL),('phabricator:20130426.search_savedquery.sql',1556231690,NULL),('phabricator:20130502.countdownrevamp1.sql',1556231690,NULL),('phabricator:20130502.countdownrevamp2.php',1556231690,NULL),('phabricator:20130502.countdownrevamp3.sql',1556231690,NULL),('phabricator:20130507.releephrqmailkey.sql',1556231690,NULL),('phabricator:20130507.releephrqmailkeypop.php',1556231690,NULL),('phabricator:20130507.releephrqsimplifycols.sql',1556231690,NULL),('phabricator:20130508.releephtransactions.sql',1556231690,NULL),('phabricator:20130508.releephtransactionsmig.php',1556231690,NULL),('phabricator:20130508.search_namedquery.sql',1556231690,NULL),('phabricator:20130513.receviedmailstatus.sql',1556231690,NULL),('phabricator:20130519.diviner.sql',1556231690,NULL),('phabricator:20130521.dropconphimages.sql',1556231690,NULL),('phabricator:20130523.maniphest_owners.sql',1556231690,NULL),('phabricator:20130524.repoxactions.sql',1556231690,NULL),('phabricator:20130529.macroauthor.sql',1556231690,NULL),('phabricator:20130529.macroauthormig.php',1556231690,NULL),('phabricator:20130530.macrodatekey.sql',1556231690,NULL),('phabricator:20130530.pastekeys.sql',1556231690,NULL),('phabricator:20130530.sessionhash.php',1556231690,NULL),('phabricator:20130531.filekeys.sql',1556231690,NULL),('phabricator:20130602.morediviner.sql',1556231690,NULL),('phabricator:20130602.namedqueries.sql',1556231690,NULL),('phabricator:20130606.userxactions.sql',1556231690,NULL),('phabricator:20130607.xaccount.sql',1556231690,NULL),('phabricator:20130611.migrateoauth.php',1556231690,NULL),('phabricator:20130611.nukeldap.php',1556231690,NULL),('phabricator:20130613.authdb.sql',1556231690,NULL),('phabricator:20130619.authconf.php',1556231690,NULL),('phabricator:20130620.diffxactions.sql',1556231690,NULL),('phabricator:20130621.diffcommentphid.sql',1556231690,NULL),('phabricator:20130621.diffcommentphidmig.php',1556231690,NULL),('phabricator:20130621.diffcommentunphid.sql',1556231690,NULL),('phabricator:20130622.doorkeeper.sql',1556231690,NULL),('phabricator:20130628.legalpadv0.sql',1556231690,NULL),('phabricator:20130701.conduitlog.sql',1556231690,NULL),('phabricator:20130703.legalpaddocdenorm.php',1556231690,NULL),('phabricator:20130703.legalpaddocdenorm.sql',1556231690,NULL),('phabricator:20130709.droptimeline.sql',1556231690,NULL),('phabricator:20130709.legalpadsignature.sql',1556231690,NULL),('phabricator:20130711.pholioimageobsolete.php',1556231690,NULL),('phabricator:20130711.pholioimageobsolete.sql',1556231690,NULL),('phabricator:20130711.pholioimageobsolete2.sql',1556231690,NULL),('phabricator:20130711.trimrealnames.php',1556231690,NULL),('phabricator:20130714.votexactions.sql',1556231690,NULL),('phabricator:20130715.votecomments.php',1556231690,NULL),('phabricator:20130715.voteedges.sql',1556231690,NULL),('phabricator:20130716.archivememberlessprojects.php',1556231690,NULL),('phabricator:20130722.pholioreplace.sql',1556231690,NULL),('phabricator:20130723.taskstarttime.sql',1556231690,NULL),('phabricator:20130726.ponderxactions.sql',1556231690,NULL),('phabricator:20130727.ponderquestionstatus.sql',1556231690,NULL),('phabricator:20130728.ponderunique.php',1556231690,NULL),('phabricator:20130728.ponderuniquekey.sql',1556231690,NULL),('phabricator:20130728.ponderxcomment.php',1556231690,NULL),('phabricator:20130731.releephcutpointidentifier.sql',1556231690,NULL),('phabricator:20130731.releephproject.sql',1556231690,NULL),('phabricator:20130731.releephrepoid.sql',1556231690,NULL),('phabricator:20130801.pastexactions.php',1556231690,NULL),('phabricator:20130801.pastexactions.sql',1556231690,NULL),('phabricator:20130802.heraldphid.sql',1556231690,NULL),('phabricator:20130802.heraldphids.php',1556231690,NULL),('phabricator:20130802.heraldphidukey.sql',1556231690,NULL),('phabricator:20130802.heraldxactions.sql',1556231690,NULL),('phabricator:20130805.pasteedges.sql',1556231690,NULL),('phabricator:20130805.pastemailkey.sql',1556231690,NULL),('phabricator:20130805.pastemailkeypop.php',1556231690,NULL),('phabricator:20130814.usercustom.sql',1556231690,NULL),('phabricator:20130820.file-mailkey-populate.php',1556231691,NULL),('phabricator:20130820.filemailkey.sql',1556231691,NULL),('phabricator:20130820.filexactions.sql',1556231691,NULL),('phabricator:20130820.releephxactions.sql',1556231690,NULL),('phabricator:20130826.divinernode.sql',1556231691,NULL),('phabricator:20130912.maniphest.1.touch.sql',1556231691,NULL),('phabricator:20130912.maniphest.2.created.sql',1556231691,NULL),('phabricator:20130912.maniphest.3.nameindex.sql',1556231691,NULL),('phabricator:20130912.maniphest.4.fillindex.php',1556231691,NULL),('phabricator:20130913.maniphest.1.migratesearch.php',1556231691,NULL),('phabricator:20130914.usercustom.sql',1556231691,NULL),('phabricator:20130915.maniphestcustom.sql',1556231691,NULL),('phabricator:20130915.maniphestmigrate.php',1556231691,NULL),('phabricator:20130915.maniphestqdrop.sql',1556231691,NULL),('phabricator:20130919.mfieldconf.php',1556231691,NULL),('phabricator:20130920.repokeyspolicy.sql',1556231691,NULL),('phabricator:20130921.mtransactions.sql',1556231691,NULL),('phabricator:20130921.xmigratemaniphest.php',1556231691,NULL),('phabricator:20130923.mrename.sql',1556231691,NULL),('phabricator:20130924.mdraftkey.sql',1556231691,NULL),('phabricator:20130925.mpolicy.sql',1556231691,NULL),('phabricator:20130925.xpolicy.sql',1556231691,NULL),('phabricator:20130926.dcustom.sql',1556231691,NULL),('phabricator:20130926.dinkeys.sql',1556231691,NULL),('phabricator:20130926.dinline.php',1556231691,NULL),('phabricator:20130927.audiomacro.sql',1556231691,NULL),('phabricator:20130929.filepolicy.sql',1556231691,NULL),('phabricator:20131004.dxedgekey.sql',1556231691,NULL),('phabricator:20131004.dxreviewers.php',1556231691,NULL),('phabricator:20131006.hdisable.sql',1556231691,NULL),('phabricator:20131010.pstorage.sql',1556231691,NULL),('phabricator:20131015.cpolicy.sql',1556231691,NULL),('phabricator:20131020.col1.sql',1556231691,NULL),('phabricator:20131020.harbormaster.sql',1556231691,NULL),('phabricator:20131020.pcustom.sql',1556231691,NULL),('phabricator:20131020.pxaction.sql',1556231691,NULL),('phabricator:20131020.pxactionmig.php',1556231691,NULL),('phabricator:20131025.repopush.sql',1556231691,NULL),('phabricator:20131026.commitstatus.sql',1556231691,NULL),('phabricator:20131030.repostatusmessage.sql',1556231691,NULL),('phabricator:20131031.vcspassword.sql',1556231691,NULL),('phabricator:20131105.buildstep.sql',1556231691,NULL),('phabricator:20131106.diffphid.1.col.sql',1556231691,NULL),('phabricator:20131106.diffphid.2.mig.php',1556231691,NULL),('phabricator:20131106.diffphid.3.key.sql',1556231691,NULL),('phabricator:20131106.nuance-v0.sql',1556231691,NULL),('phabricator:20131107.buildlog.sql',1556231691,NULL),('phabricator:20131112.userverified.1.col.sql',1556231691,NULL),('phabricator:20131112.userverified.2.mig.php',1556231691,NULL),('phabricator:20131118.ownerorder.php',1556231691,NULL),('phabricator:20131119.passphrase.sql',1556231691,NULL),('phabricator:20131120.nuancesourcetype.sql',1556231691,NULL),('phabricator:20131121.passphraseedge.sql',1556231691,NULL),('phabricator:20131121.repocredentials.1.col.sql',1556231691,NULL),('phabricator:20131121.repocredentials.2.mig.php',1556231691,NULL),('phabricator:20131122.repomirror.sql',1556231691,NULL),('phabricator:20131123.drydockblueprintpolicy.sql',1556231691,NULL),('phabricator:20131129.drydockresourceblueprint.sql',1556231691,NULL),('phabricator:20131204.pushlog.sql',1556231691,NULL),('phabricator:20131205.buildsteporder.sql',1556231691,NULL),('phabricator:20131205.buildstepordermig.php',1556231691,NULL),('phabricator:20131205.buildtargets.sql',1556231691,NULL),('phabricator:20131206.phragment.sql',1556231691,NULL),('phabricator:20131206.phragmentnull.sql',1556231691,NULL),('phabricator:20131208.phragmentsnapshot.sql',1556231691,NULL),('phabricator:20131211.phragmentedges.sql',1556231691,NULL),('phabricator:20131217.pushlogphid.1.col.sql',1556231691,NULL),('phabricator:20131217.pushlogphid.2.mig.php',1556231691,NULL),('phabricator:20131217.pushlogphid.3.key.sql',1556231692,NULL),('phabricator:20131219.pxdrop.sql',1556231692,NULL),('phabricator:20131224.harbormanual.sql',1556231692,NULL),('phabricator:20131227.heraldobject.sql',1556231692,NULL),('phabricator:20131231.dropshortcut.sql',1556231692,NULL),('phabricator:20131302.maniphestvalue.sql',1556231689,NULL),('phabricator:20140104.harbormastercmd.sql',1556231692,NULL),('phabricator:20140106.macromailkey.1.sql',1556231692,NULL),('phabricator:20140106.macromailkey.2.php',1556231692,NULL),('phabricator:20140108.ddbpname.1.sql',1556231692,NULL),('phabricator:20140108.ddbpname.2.php',1556231692,NULL),('phabricator:20140109.ddxactions.sql',1556231692,NULL),('phabricator:20140109.projectcolumnsdates.sql',1556231692,NULL),('phabricator:20140113.legalpadsig.1.sql',1556231692,NULL),('phabricator:20140113.legalpadsig.2.php',1556231692,NULL),('phabricator:20140115.auth.1.id.sql',1556231692,NULL),('phabricator:20140115.auth.2.expires.sql',1556231692,NULL),('phabricator:20140115.auth.3.unlimit.php',1556231692,NULL),('phabricator:20140115.legalpadsigkey.sql',1556231692,NULL),('phabricator:20140116.reporefcursor.sql',1556231692,NULL),('phabricator:20140126.diff.1.parentrevisionid.sql',1556231692,NULL),('phabricator:20140126.diff.2.repositoryphid.sql',1556231692,NULL),('phabricator:20140130.dash.1.board.sql',1556231692,NULL),('phabricator:20140130.dash.2.panel.sql',1556231692,NULL),('phabricator:20140130.dash.3.boardxaction.sql',1556231692,NULL),('phabricator:20140130.dash.4.panelxaction.sql',1556231692,NULL),('phabricator:20140130.mail.1.retry.sql',1556231692,NULL),('phabricator:20140130.mail.2.next.sql',1556231692,NULL),('phabricator:20140201.gc.1.mailsent.sql',1556231692,NULL),('phabricator:20140201.gc.2.mailreceived.sql',1556231692,NULL),('phabricator:20140205.cal.1.rename.sql',1556231692,NULL),('phabricator:20140205.cal.2.phid-col.sql',1556231692,NULL),('phabricator:20140205.cal.3.phid-mig.php',1556231692,NULL),('phabricator:20140205.cal.4.phid-key.sql',1556231692,NULL),('phabricator:20140210.herald.rule-condition-mig.php',1556231692,NULL),('phabricator:20140210.projcfield.1.blurb.php',1556231692,NULL),('phabricator:20140210.projcfield.2.piccol.sql',1556231692,NULL),('phabricator:20140210.projcfield.3.picmig.sql',1556231692,NULL),('phabricator:20140210.projcfield.4.memmig.sql',1556231692,NULL),('phabricator:20140210.projcfield.5.dropprofile.sql',1556231692,NULL),('phabricator:20140211.dx.1.nullablechangesetid.sql',1556231692,NULL),('phabricator:20140211.dx.2.migcommenttext.php',1556231692,NULL),('phabricator:20140211.dx.3.migsubscriptions.sql',1556231692,NULL),('phabricator:20140211.dx.999.drop.relationships.sql',1556231692,NULL),('phabricator:20140212.dx.1.armageddon.php',1556231692,NULL),('phabricator:20140214.clean.1.legacycommentid.sql',1556231692,NULL),('phabricator:20140214.clean.2.dropcomment.sql',1556231692,NULL),('phabricator:20140214.clean.3.dropinline.sql',1556231692,NULL),('phabricator:20140218.differentialdraft.sql',1556231692,NULL),('phabricator:20140218.passwords.1.extend.sql',1556231692,NULL),('phabricator:20140218.passwords.2.prefix.sql',1556231692,NULL),('phabricator:20140218.passwords.3.vcsextend.sql',1556231692,NULL),('phabricator:20140218.passwords.4.vcs.php',1556231692,NULL),('phabricator:20140223.bigutf8scratch.sql',1556231692,NULL),('phabricator:20140224.dxclean.1.datecommitted.sql',1556231692,NULL),('phabricator:20140226.dxcustom.1.fielddata.php',1556231692,NULL),('phabricator:20140226.dxcustom.99.drop.sql',1556231692,NULL),('phabricator:20140228.dxcomment.1.sql',1556231692,NULL),('phabricator:20140305.diviner.1.slugcol.sql',1556231692,NULL),('phabricator:20140305.diviner.2.slugkey.sql',1556231692,NULL),('phabricator:20140311.mdroplegacy.sql',1556231692,NULL),('phabricator:20140314.projectcolumn.1.statuscol.sql',1556231692,NULL),('phabricator:20140314.projectcolumn.2.statuskey.sql',1556231692,NULL),('phabricator:20140317.mupdatedkey.sql',1556231692,NULL),('phabricator:20140321.harbor.1.bxaction.sql',1556231692,NULL),('phabricator:20140321.mstatus.1.col.sql',1556231692,NULL),('phabricator:20140321.mstatus.2.mig.php',1556231692,NULL),('phabricator:20140323.harbor.1.renames.php',1556231692,NULL),('phabricator:20140323.harbor.2.message.sql',1556231692,NULL),('phabricator:20140325.push.1.event.sql',1556231692,NULL),('phabricator:20140325.push.2.eventphid.sql',1556231692,NULL),('phabricator:20140325.push.3.groups.php',1556231692,NULL),('phabricator:20140325.push.4.prune.sql',1556231692,NULL),('phabricator:20140326.project.1.colxaction.sql',1556231692,NULL),('phabricator:20140328.releeph.1.productxaction.sql',1556231692,NULL),('phabricator:20140330.flagtext.sql',1556231692,NULL),('phabricator:20140402.actionlog.sql',1556231692,NULL),('phabricator:20140410.accountsecret.1.sql',1556231692,NULL),('phabricator:20140410.accountsecret.2.php',1556231692,NULL),('phabricator:20140416.harbor.1.sql',1556231692,NULL),('phabricator:20140420.rel.1.objectphid.sql',1556231692,NULL),('phabricator:20140420.rel.2.objectmig.php',1556231692,NULL),('phabricator:20140421.slowvotecolumnsisclosed.sql',1556231692,NULL),('phabricator:20140423.session.1.hisec.sql',1556231692,NULL),('phabricator:20140427.mfactor.1.sql',1556231692,NULL),('phabricator:20140430.auth.1.partial.sql',1556231692,NULL),('phabricator:20140430.dash.1.paneltype.sql',1556231692,NULL),('phabricator:20140430.dash.2.edge.sql',1556231692,NULL),('phabricator:20140501.passphraselockcredential.sql',1556231692,NULL),('phabricator:20140501.remove.1.dlog.sql',1556231692,NULL),('phabricator:20140507.smstable.sql',1556231692,NULL),('phabricator:20140509.coverage.1.sql',1556231692,NULL),('phabricator:20140509.dashboardlayoutconfig.sql',1556231692,NULL),('phabricator:20140512.dparents.1.sql',1556231692,NULL),('phabricator:20140514.harbormasterbuildabletransaction.sql',1556231692,NULL),('phabricator:20140514.pholiomockclose.sql',1556231692,NULL),('phabricator:20140515.trust-emails.sql',1556231692,NULL),('phabricator:20140517.dxbinarycache.sql',1556231692,NULL),('phabricator:20140518.dxmorebinarycache.sql',1556231693,NULL),('phabricator:20140519.dashboardinstall.sql',1556231693,NULL),('phabricator:20140520.authtemptoken.sql',1556231693,NULL),('phabricator:20140521.projectslug.1.create.sql',1556231693,NULL),('phabricator:20140521.projectslug.2.mig.php',1556231693,NULL),('phabricator:20140522.projecticon.sql',1556231693,NULL),('phabricator:20140524.auth.mfa.cache.sql',1556231693,NULL),('phabricator:20140525.hunkmodern.sql',1556231693,NULL),('phabricator:20140615.pholioedit.1.sql',1556231693,NULL),('phabricator:20140615.pholioedit.2.sql',1556231693,NULL),('phabricator:20140617.daemon.explicit-argv.sql',1556231693,NULL),('phabricator:20140617.daemonlog.sql',1556231693,NULL),('phabricator:20140624.projcolor.1.sql',1556231693,NULL),('phabricator:20140624.projcolor.2.sql',1556231693,NULL),('phabricator:20140629.dasharchive.1.sql',1556231693,NULL),('phabricator:20140629.legalsig.1.sql',1556231693,NULL),('phabricator:20140629.legalsig.2.php',1556231693,NULL),('phabricator:20140701.legalexemption.1.sql',1556231693,NULL),('phabricator:20140701.legalexemption.2.sql',1556231693,NULL),('phabricator:20140703.legalcorp.1.sql',1556231693,NULL),('phabricator:20140703.legalcorp.2.sql',1556231693,NULL),('phabricator:20140703.legalcorp.3.sql',1556231693,NULL),('phabricator:20140703.legalcorp.4.sql',1556231693,NULL),('phabricator:20140703.legalcorp.5.sql',1556231693,NULL),('phabricator:20140704.harbormasterstep.1.sql',1556231693,NULL),('phabricator:20140704.harbormasterstep.2.sql',1556231693,NULL),('phabricator:20140704.legalpreamble.1.sql',1556231693,NULL),('phabricator:20140706.harbormasterdepend.1.php',1556231693,NULL),('phabricator:20140706.pedge.1.sql',1556231693,NULL),('phabricator:20140711.pnames.1.sql',1556231693,NULL),('phabricator:20140711.pnames.2.php',1556231693,NULL),('phabricator:20140711.workerpriority.sql',1556231693,NULL),('phabricator:20140712.projcoluniq.sql',1556231693,NULL),('phabricator:20140721.phortune.1.cart.sql',1556231693,NULL),('phabricator:20140721.phortune.2.purchase.sql',1556231693,NULL),('phabricator:20140721.phortune.3.charge.sql',1556231693,NULL),('phabricator:20140721.phortune.4.cartstatus.sql',1556231693,NULL),('phabricator:20140721.phortune.5.cstatusdefault.sql',1556231693,NULL),('phabricator:20140721.phortune.6.onetimecharge.sql',1556231693,NULL),('phabricator:20140721.phortune.7.nullmethod.sql',1556231693,NULL),('phabricator:20140722.appname.php',1556231693,NULL),('phabricator:20140722.audit.1.xactions.sql',1556231693,NULL),('phabricator:20140722.audit.2.comments.sql',1556231693,NULL),('phabricator:20140722.audit.3.miginlines.php',1556231693,NULL),('phabricator:20140722.audit.4.migtext.php',1556231693,NULL),('phabricator:20140722.renameauth.php',1556231693,NULL),('phabricator:20140723.apprenamexaction.sql',1556231693,NULL),('phabricator:20140725.audit.1.migxactions.php',1556231693,NULL),('phabricator:20140731.audit.1.subscribers.php',1556231693,NULL),('phabricator:20140731.cancdn.php',1556231693,NULL),('phabricator:20140731.harbormasterstepdesc.sql',1556231693,NULL),('phabricator:20140805.boardcol.1.sql',1556231693,NULL),('phabricator:20140805.boardcol.2.php',1556231693,NULL),('phabricator:20140807.harbormastertargettime.sql',1556231693,NULL),('phabricator:20140808.boardprop.1.sql',1556231693,NULL),('phabricator:20140808.boardprop.2.sql',1556231693,NULL),('phabricator:20140808.boardprop.3.php',1556231693,NULL),('phabricator:20140811.blob.1.sql',1556231693,NULL),('phabricator:20140811.blob.2.sql',1556231693,NULL),('phabricator:20140812.projkey.1.sql',1556231693,NULL),('phabricator:20140812.projkey.2.sql',1556231693,NULL),('phabricator:20140814.passphrasecredentialconduit.sql',1556231693,NULL),('phabricator:20140815.cancdncase.php',1556231693,NULL),('phabricator:20140818.harbormasterindex.1.sql',1556231693,NULL),('phabricator:20140821.harbormasterbuildgen.1.sql',1556231693,NULL),('phabricator:20140822.daemonenvhash.sql',1556231693,NULL),('phabricator:20140902.almanacdevice.1.sql',1556231693,NULL),('phabricator:20140904.macroattach.php',1556231693,NULL),('phabricator:20140911.fund.1.initiative.sql',1556231693,NULL),('phabricator:20140911.fund.2.xaction.sql',1556231693,NULL),('phabricator:20140911.fund.3.edge.sql',1556231693,NULL),('phabricator:20140911.fund.4.backer.sql',1556231693,NULL),('phabricator:20140911.fund.5.backxaction.sql',1556231693,NULL),('phabricator:20140914.betaproto.php',1556231693,NULL),('phabricator:20140917.project.canlock.sql',1556231693,NULL),('phabricator:20140918.schema.1.dropaudit.sql',1556231693,NULL),('phabricator:20140918.schema.2.dropauditinline.sql',1556231693,NULL),('phabricator:20140918.schema.3.wipecache.sql',1556231693,NULL),('phabricator:20140918.schema.4.cachetype.sql',1556231693,NULL),('phabricator:20140918.schema.5.slowvote.sql',1556231693,NULL),('phabricator:20140919.schema.01.calstatus.sql',1556231693,NULL),('phabricator:20140919.schema.02.calname.sql',1556231693,NULL),('phabricator:20140919.schema.03.dropaux.sql',1556231693,NULL),('phabricator:20140919.schema.04.droptaskproj.sql',1556231693,NULL),('phabricator:20140926.schema.01.droprelev.sql',1556231693,NULL),('phabricator:20140926.schema.02.droprelreqev.sql',1556231693,NULL),('phabricator:20140926.schema.03.dropldapinfo.sql',1556231693,NULL),('phabricator:20140926.schema.04.dropoauthinfo.sql',1556231693,NULL),('phabricator:20140926.schema.05.dropprojaffil.sql',1556231693,NULL),('phabricator:20140926.schema.06.dropsubproject.sql',1556231693,NULL),('phabricator:20140926.schema.07.droppondcom.sql',1556231693,NULL),('phabricator:20140927.schema.01.dropsearchq.sql',1556231693,NULL),('phabricator:20140927.schema.02.pholio1.sql',1556231693,NULL),('phabricator:20140927.schema.03.pholio2.sql',1556231693,NULL),('phabricator:20140927.schema.04.pholio3.sql',1556231693,NULL),('phabricator:20140927.schema.05.phragment1.sql',1556231693,NULL),('phabricator:20140927.schema.06.releeph1.sql',1556231693,NULL),('phabricator:20141001.schema.01.version.sql',1556231693,NULL),('phabricator:20141001.schema.02.taskmail.sql',1556231693,NULL),('phabricator:20141002.schema.01.liskcounter.sql',1556231693,NULL),('phabricator:20141002.schema.02.draftnull.sql',1556231693,NULL),('phabricator:20141004.currency.01.sql',1556231693,NULL),('phabricator:20141004.currency.02.sql',1556231693,NULL),('phabricator:20141004.currency.03.sql',1556231693,NULL),('phabricator:20141004.currency.04.sql',1556231693,NULL),('phabricator:20141004.currency.05.sql',1556231693,NULL),('phabricator:20141004.currency.06.sql',1556231693,NULL),('phabricator:20141004.harborliskcounter.sql',1556231693,NULL),('phabricator:20141005.phortuneproduct.sql',1556231694,NULL),('phabricator:20141006.phortunecart.sql',1556231694,NULL),('phabricator:20141006.phortunemerchant.sql',1556231694,NULL),('phabricator:20141006.phortunemerchantx.sql',1556231694,NULL),('phabricator:20141007.fundmerchant.sql',1556231694,NULL),('phabricator:20141007.fundrisks.sql',1556231694,NULL),('phabricator:20141007.fundtotal.sql',1556231694,NULL),('phabricator:20141007.phortunecartmerchant.sql',1556231694,NULL),('phabricator:20141007.phortunecharge.sql',1556231694,NULL),('phabricator:20141007.phortunepayment.sql',1556231694,NULL),('phabricator:20141007.phortuneprovider.sql',1556231694,NULL),('phabricator:20141007.phortuneproviderx.sql',1556231694,NULL),('phabricator:20141008.phortunemerchdesc.sql',1556231694,NULL),('phabricator:20141008.phortuneprovdis.sql',1556231694,NULL),('phabricator:20141008.phortunerefund.sql',1556231694,NULL),('phabricator:20141010.fundmailkey.sql',1556231694,NULL),('phabricator:20141011.phortunemerchedit.sql',1556231694,NULL),('phabricator:20141012.phortunecartxaction.sql',1556231694,NULL),('phabricator:20141013.phortunecartkey.sql',1556231694,NULL),('phabricator:20141016.almanac.device.sql',1556231694,NULL),('phabricator:20141016.almanac.dxaction.sql',1556231694,NULL),('phabricator:20141016.almanac.interface.sql',1556231694,NULL),('phabricator:20141016.almanac.network.sql',1556231694,NULL),('phabricator:20141016.almanac.nxaction.sql',1556231694,NULL),('phabricator:20141016.almanac.service.sql',1556231694,NULL),('phabricator:20141016.almanac.sxaction.sql',1556231694,NULL),('phabricator:20141017.almanac.binding.sql',1556231694,NULL),('phabricator:20141017.almanac.bxaction.sql',1556231694,NULL),('phabricator:20141025.phriction.1.xaction.sql',1556231694,NULL),('phabricator:20141025.phriction.2.xaction.sql',1556231694,NULL),('phabricator:20141025.phriction.mailkey.sql',1556231694,NULL),('phabricator:20141103.almanac.1.delprop.sql',1556231694,NULL),('phabricator:20141103.almanac.2.addprop.sql',1556231694,NULL),('phabricator:20141104.almanac.3.edge.sql',1556231694,NULL),('phabricator:20141105.ssh.1.rename.sql',1556231694,NULL),('phabricator:20141106.dropold.sql',1556231694,NULL),('phabricator:20141106.uniqdrafts.php',1556231694,NULL),('phabricator:20141107.phriction.policy.1.sql',1556231694,NULL),('phabricator:20141107.phriction.policy.2.php',1556231694,NULL),('phabricator:20141107.phriction.popkeys.php',1556231694,NULL),('phabricator:20141107.ssh.1.colname.sql',1556231694,NULL),('phabricator:20141107.ssh.2.keyhash.sql',1556231694,NULL),('phabricator:20141107.ssh.3.keyindex.sql',1556231694,NULL),('phabricator:20141107.ssh.4.keymig.php',1556231694,NULL),('phabricator:20141107.ssh.5.indexnull.sql',1556231694,NULL),('phabricator:20141107.ssh.6.indexkey.sql',1556231694,NULL),('phabricator:20141107.ssh.7.colnull.sql',1556231694,NULL),('phabricator:20141113.auditdupes.php',1556231694,NULL),('phabricator:20141118.diffxaction.sql',1556231694,NULL),('phabricator:20141119.commitpedge.sql',1556231694,NULL),('phabricator:20141119.differential.diff.policy.sql',1556231694,NULL),('phabricator:20141119.sshtrust.sql',1556231694,NULL),('phabricator:20141123.taskpriority.1.sql',1556231694,NULL),('phabricator:20141123.taskpriority.2.sql',1556231694,NULL),('phabricator:20141210.maniphestsubscribersmig.1.sql',1556231694,NULL),('phabricator:20141210.maniphestsubscribersmig.2.sql',1556231694,NULL),('phabricator:20141210.reposervice.sql',1556231694,NULL),('phabricator:20141212.conduittoken.sql',1556231694,NULL),('phabricator:20141215.almanacservicetype.sql',1556231694,NULL),('phabricator:20141217.almanacdevicelock.sql',1556231694,NULL),('phabricator:20141217.almanaclock.sql',1556231694,NULL),('phabricator:20141218.maniphestcctxn.php',1556231694,NULL),('phabricator:20141222.maniphestprojtxn.php',1556231694,NULL),('phabricator:20141223.daemonloguser.sql',1556231694,NULL),('phabricator:20141223.daemonobjectphid.sql',1556231694,NULL),('phabricator:20141230.pasteeditpolicycolumn.sql',1556231694,NULL),('phabricator:20141230.pasteeditpolicyexisting.sql',1556231694,NULL),('phabricator:20150102.policyname.php',1556231694,NULL),('phabricator:20150102.tasksubscriber.sql',1556231694,NULL),('phabricator:20150105.conpsearch.sql',1556231694,NULL),('phabricator:20150114.oauthserver.client.policy.sql',1556231694,NULL),('phabricator:20150115.applicationemails.sql',1556231694,NULL),('phabricator:20150115.trigger.1.sql',1556231694,NULL),('phabricator:20150115.trigger.2.sql',1556231694,NULL),('phabricator:20150116.maniphestapplicationemails.php',1556231694,NULL),('phabricator:20150120.maniphestdefaultauthor.php',1556231694,NULL),('phabricator:20150124.subs.1.sql',1556231694,NULL),('phabricator:20150129.pastefileapplicationemails.php',1556231694,NULL),('phabricator:20150130.phortune.1.subphid.sql',1556231694,NULL),('phabricator:20150130.phortune.2.subkey.sql',1556231695,NULL),('phabricator:20150131.phortune.1.defaultpayment.sql',1556231695,NULL),('phabricator:20150205.authprovider.autologin.sql',1556231695,NULL),('phabricator:20150205.daemonenv.sql',1556231695,NULL),('phabricator:20150209.invite.sql',1556231695,NULL),('phabricator:20150209.oauthclient.trust.sql',1556231695,NULL),('phabricator:20150210.invitephid.sql',1556231695,NULL),('phabricator:20150212.legalpad.session.1.sql',1556231695,NULL),('phabricator:20150212.legalpad.session.2.sql',1556231695,NULL),('phabricator:20150219.scratch.nonmutable.sql',1556231695,NULL),('phabricator:20150223.daemon.1.id.sql',1556231695,NULL),('phabricator:20150223.daemon.2.idlegacy.sql',1556231695,NULL),('phabricator:20150223.daemon.3.idkey.sql',1556231695,NULL),('phabricator:20150312.filechunk.1.sql',1556231695,NULL),('phabricator:20150312.filechunk.2.sql',1556231695,NULL),('phabricator:20150312.filechunk.3.sql',1556231695,NULL),('phabricator:20150317.conpherence.isroom.1.sql',1556231695,NULL),('phabricator:20150317.conpherence.isroom.2.sql',1556231695,NULL),('phabricator:20150317.conpherence.policy.sql',1556231695,NULL),('phabricator:20150410.nukeruleedit.sql',1556231695,NULL),('phabricator:20150420.invoice.1.sql',1556231695,NULL),('phabricator:20150420.invoice.2.sql',1556231695,NULL),('phabricator:20150425.isclosed.sql',1556231695,NULL),('phabricator:20150427.calendar.1.edge.sql',1556231695,NULL),('phabricator:20150427.calendar.1.xaction.sql',1556231695,NULL),('phabricator:20150427.calendar.2.xaction.sql',1556231695,NULL),('phabricator:20150428.calendar.1.iscancelled.sql',1556231695,NULL),('phabricator:20150428.calendar.1.name.sql',1556231695,NULL),('phabricator:20150429.calendar.1.invitee.sql',1556231695,NULL),('phabricator:20150430.calendar.1.policies.sql',1556231695,NULL),('phabricator:20150430.multimeter.1.sql',1556231695,NULL),('phabricator:20150430.multimeter.2.host.sql',1556231695,NULL),('phabricator:20150430.multimeter.3.viewer.sql',1556231695,NULL),('phabricator:20150430.multimeter.4.context.sql',1556231695,NULL),('phabricator:20150430.multimeter.5.label.sql',1556231695,NULL),('phabricator:20150501.calendar.1.reply.sql',1556231695,NULL),('phabricator:20150501.calendar.2.reply.php',1556231695,NULL),('phabricator:20150501.conpherencepics.sql',1556231695,NULL),('phabricator:20150503.repositorysymbols.1.sql',1556231695,NULL),('phabricator:20150503.repositorysymbols.2.php',1556231695,NULL),('phabricator:20150503.repositorysymbols.3.sql',1556231695,NULL),('phabricator:20150504.symbolsproject.1.php',1556231695,NULL),('phabricator:20150504.symbolsproject.2.sql',1556231695,NULL),('phabricator:20150506.calendarunnamedevents.1.php',1556231695,NULL),('phabricator:20150507.calendar.1.isallday.sql',1556231695,NULL),('phabricator:20150513.user.cache.1.sql',1556231695,NULL),('phabricator:20150514.calendar.status.sql',1556231695,NULL),('phabricator:20150514.phame.blog.xaction.sql',1556231695,NULL),('phabricator:20150514.user.cache.2.sql',1556231695,NULL),('phabricator:20150515.phame.post.xaction.sql',1556231695,NULL),('phabricator:20150515.project.mailkey.1.sql',1556231695,NULL),('phabricator:20150515.project.mailkey.2.php',1556231695,NULL),('phabricator:20150519.calendar.calendaricon.sql',1556231695,NULL),('phabricator:20150521.releephrepository.sql',1556231695,NULL),('phabricator:20150525.diff.hidden.1.sql',1556231695,NULL),('phabricator:20150526.owners.mailkey.1.sql',1556231695,NULL),('phabricator:20150526.owners.mailkey.2.php',1556231695,NULL),('phabricator:20150526.owners.xaction.sql',1556231695,NULL),('phabricator:20150527.calendar.recurringevents.sql',1556231695,NULL),('phabricator:20150601.spaces.1.namespace.sql',1556231695,NULL),('phabricator:20150601.spaces.2.xaction.sql',1556231695,NULL),('phabricator:20150602.mlist.1.sql',1556231695,NULL),('phabricator:20150602.mlist.2.php',1556231695,NULL),('phabricator:20150604.spaces.1.sql',1556231695,NULL),('phabricator:20150605.diviner.edges.sql',1556231695,NULL),('phabricator:20150605.diviner.editPolicy.sql',1556231695,NULL),('phabricator:20150605.diviner.xaction.sql',1556231695,NULL),('phabricator:20150606.mlist.1.php',1556231695,NULL),('phabricator:20150609.inline.sql',1556231695,NULL),('phabricator:20150609.spaces.1.pholio.sql',1556231695,NULL),('phabricator:20150609.spaces.2.maniphest.sql',1556231695,NULL),('phabricator:20150610.spaces.1.desc.sql',1556231695,NULL),('phabricator:20150610.spaces.2.edge.sql',1556231695,NULL),('phabricator:20150610.spaces.3.archive.sql',1556231695,NULL),('phabricator:20150611.spaces.1.mailxaction.sql',1556231695,NULL),('phabricator:20150611.spaces.2.appmail.sql',1556231695,NULL),('phabricator:20150616.divinerrepository.sql',1556231695,NULL),('phabricator:20150617.harbor.1.lint.sql',1556231695,NULL),('phabricator:20150617.harbor.2.unit.sql',1556231695,NULL),('phabricator:20150618.harbor.1.planauto.sql',1556231695,NULL),('phabricator:20150618.harbor.2.stepauto.sql',1556231695,NULL),('phabricator:20150618.harbor.3.buildauto.sql',1556231695,NULL),('phabricator:20150619.conpherencerooms.1.sql',1556231695,NULL),('phabricator:20150619.conpherencerooms.2.sql',1556231695,NULL),('phabricator:20150619.conpherencerooms.3.sql',1556231695,NULL),('phabricator:20150621.phrase.1.sql',1556231695,NULL),('phabricator:20150621.phrase.2.sql',1556231695,NULL),('phabricator:20150622.bulk.1.job.sql',1556231695,NULL),('phabricator:20150622.bulk.2.task.sql',1556231695,NULL),('phabricator:20150622.bulk.3.xaction.sql',1556231695,NULL),('phabricator:20150622.bulk.4.edge.sql',1556231696,NULL),('phabricator:20150622.metamta.1.phid-col.sql',1556231696,NULL),('phabricator:20150622.metamta.2.phid-mig.php',1556231696,NULL),('phabricator:20150622.metamta.3.phid-key.sql',1556231696,NULL),('phabricator:20150622.metamta.4.actor-phid-col.sql',1556231696,NULL),('phabricator:20150622.metamta.5.actor-phid-mig.php',1556231696,NULL),('phabricator:20150622.metamta.6.actor-phid-key.sql',1556231696,NULL),('phabricator:20150624.spaces.1.repo.sql',1556231696,NULL),('phabricator:20150626.spaces.1.calendar.sql',1556231696,NULL),('phabricator:20150630.herald.1.sql',1556231696,NULL),('phabricator:20150630.herald.2.sql',1556231696,NULL),('phabricator:20150701.herald.1.sql',1556231696,NULL),('phabricator:20150701.herald.2.sql',1556231696,NULL),('phabricator:20150702.spaces.1.slowvote.sql',1556231696,NULL),('phabricator:20150706.herald.1.sql',1556231696,NULL),('phabricator:20150707.herald.1.sql',1556231696,NULL),('phabricator:20150708.arcanistproject.sql',1556231696,NULL),('phabricator:20150708.herald.1.sql',1556231696,NULL),('phabricator:20150708.herald.2.sql',1556231696,NULL),('phabricator:20150708.herald.3.sql',1556231696,NULL),('phabricator:20150712.badges.1.sql',1556231696,NULL),('phabricator:20150714.spaces.countdown.1.sql',1556231696,NULL),('phabricator:20150717.herald.1.sql',1556231696,NULL),('phabricator:20150719.countdown.1.sql',1556231696,NULL),('phabricator:20150719.countdown.2.sql',1556231696,NULL),('phabricator:20150719.countdown.3.sql',1556231696,NULL),('phabricator:20150721.phurl.1.url.sql',1556231696,NULL),('phabricator:20150721.phurl.2.xaction.sql',1556231696,NULL),('phabricator:20150721.phurl.3.xactioncomment.sql',1556231696,NULL),('phabricator:20150721.phurl.4.url.sql',1556231696,NULL),('phabricator:20150721.phurl.5.edge.sql',1556231696,NULL),('phabricator:20150721.phurl.6.alias.sql',1556231696,NULL),('phabricator:20150721.phurl.7.authorphid.sql',1556231696,NULL),('phabricator:20150722.dashboard.1.sql',1556231696,NULL),('phabricator:20150722.dashboard.2.sql',1556231696,NULL),('phabricator:20150723.countdown.1.sql',1556231696,NULL),('phabricator:20150724.badges.comments.1.sql',1556231696,NULL),('phabricator:20150724.countdown.comments.1.sql',1556231696,NULL),('phabricator:20150725.badges.mailkey.1.sql',1556231696,NULL),('phabricator:20150725.badges.mailkey.2.php',1556231696,NULL),('phabricator:20150725.badges.viewpolicy.3.sql',1556231696,NULL),('phabricator:20150725.countdown.mailkey.1.sql',1556231696,NULL),('phabricator:20150725.countdown.mailkey.2.php',1556231696,NULL),('phabricator:20150725.slowvote.mailkey.1.sql',1556231696,NULL),('phabricator:20150725.slowvote.mailkey.2.php',1556231696,NULL),('phabricator:20150727.heraldaction.1.sql',1556231696,NULL),('phabricator:20150730.herald.1.sql',1556231696,NULL),('phabricator:20150730.herald.2.sql',1556231696,NULL),('phabricator:20150730.herald.3.sql',1556231696,NULL),('phabricator:20150730.herald.4.sql',1556231696,NULL),('phabricator:20150730.herald.5.sql',1556231696,NULL),('phabricator:20150730.herald.6.sql',1556231696,NULL),('phabricator:20150730.herald.7.sql',1556231696,NULL),('phabricator:20150803.herald.1.sql',1556231696,NULL),('phabricator:20150803.herald.2.sql',1556231696,NULL),('phabricator:20150804.ponder.answer.mailkey.1.sql',1556231696,NULL),('phabricator:20150804.ponder.answer.mailkey.2.php',1556231696,NULL),('phabricator:20150804.ponder.question.1.sql',1556231696,NULL),('phabricator:20150804.ponder.question.2.sql',1556231696,NULL),('phabricator:20150804.ponder.question.3.sql',1556231696,NULL),('phabricator:20150804.ponder.spaces.4.sql',1556231696,NULL),('phabricator:20150805.paste.status.1.sql',1556231696,NULL),('phabricator:20150805.paste.status.2.sql',1556231696,NULL),('phabricator:20150806.ponder.answer.1.sql',1556231696,NULL),('phabricator:20150806.ponder.editpolicy.2.sql',1556231696,NULL),('phabricator:20150806.ponder.status.1.sql',1556231696,NULL),('phabricator:20150806.ponder.status.2.sql',1556231696,NULL),('phabricator:20150806.ponder.status.3.sql',1556231696,NULL),('phabricator:20150808.ponder.vote.1.sql',1556231696,NULL),('phabricator:20150808.ponder.vote.2.sql',1556231696,NULL),('phabricator:20150812.ponder.answer.1.sql',1556231696,NULL),('phabricator:20150812.ponder.answer.2.sql',1556231696,NULL),('phabricator:20150814.harbormater.artifact.phid.sql',1556231696,NULL),('phabricator:20150815.owners.status.1.sql',1556231696,NULL),('phabricator:20150815.owners.status.2.sql',1556231696,NULL),('phabricator:20150823.nuance.queue.1.sql',1556231696,NULL),('phabricator:20150823.nuance.queue.2.sql',1556231696,NULL),('phabricator:20150823.nuance.queue.3.sql',1556231696,NULL),('phabricator:20150823.nuance.queue.4.sql',1556231696,NULL),('phabricator:20150828.ponder.wiki.1.sql',1556231696,NULL),('phabricator:20150829.ponder.dupe.1.sql',1556231696,NULL),('phabricator:20150904.herald.1.sql',1556231696,NULL),('phabricator:20150906.mailinglist.sql',1556231696,NULL),('phabricator:20150910.owners.custom.1.sql',1556231696,NULL),('phabricator:20150916.drydock.slotlocks.1.sql',1556231696,NULL),('phabricator:20150922.drydock.commands.1.sql',1556231696,NULL),('phabricator:20150923.drydock.resourceid.1.sql',1556231696,NULL),('phabricator:20150923.drydock.resourceid.2.sql',1556231696,NULL),('phabricator:20150923.drydock.resourceid.3.sql',1556231696,NULL),('phabricator:20150923.drydock.taskid.1.sql',1556231696,NULL),('phabricator:20150924.drydock.disable.1.sql',1556231696,NULL),('phabricator:20150924.drydock.status.1.sql',1556231696,NULL),('phabricator:20150928.drydock.rexpire.1.sql',1556231696,NULL),('phabricator:20150930.drydock.log.1.sql',1556231696,NULL),('phabricator:20151001.drydock.rname.1.sql',1556231696,NULL),('phabricator:20151002.dashboard.status.1.sql',1556231696,NULL),('phabricator:20151002.harbormaster.bparam.1.sql',1556231696,NULL),('phabricator:20151009.drydock.auth.1.sql',1556231696,NULL),('phabricator:20151010.drydock.auth.2.sql',1556231696,NULL),('phabricator:20151013.drydock.op.1.sql',1556231696,NULL),('phabricator:20151023.harborpolicy.1.sql',1556231696,NULL),('phabricator:20151023.harborpolicy.2.php',1556231696,NULL),('phabricator:20151023.patchduration.sql',1556231697,141072),('phabricator:20151030.harbormaster.initiator.sql',1556231697,14355),('phabricator:20151106.editengine.1.table.sql',1556231697,7000),('phabricator:20151106.editengine.2.xactions.sql',1556231697,6327),('phabricator:20151106.phame.post.mailkey.1.sql',1556231697,13453),('phabricator:20151106.phame.post.mailkey.2.php',1556231697,1570),('phabricator:20151107.phame.blog.mailkey.1.sql',1556231697,11087),('phabricator:20151107.phame.blog.mailkey.2.php',1556231697,970),('phabricator:20151108.phame.blog.joinpolicy.sql',1556231697,11189),('phabricator:20151108.xhpast.stderr.sql',1556231697,18926),('phabricator:20151109.phame.post.comments.1.sql',1556231697,7158),('phabricator:20151109.repository.coverage.1.sql',1556231697,1260),('phabricator:20151109.xhpast.db.1.sql',1556231697,3950),('phabricator:20151109.xhpast.db.2.sql',1556231697,1156),('phabricator:20151110.daemonenvhash.sql',1556231697,24270),('phabricator:20151111.phame.blog.archive.1.sql',1556231697,11808),('phabricator:20151111.phame.blog.archive.2.sql',1556231697,570),('phabricator:20151112.herald.edge.sql',1556231697,10237),('phabricator:20151116.owners.edge.sql',1556231697,10178),('phabricator:20151128.phame.blog.picture.1.sql',1556231697,12092),('phabricator:20151130.phurl.mailkey.1.sql',1556231697,9727),('phabricator:20151130.phurl.mailkey.2.php',1556231697,1287),('phabricator:20151202.versioneddraft.1.sql',1556231697,5191),('phabricator:20151207.editengine.1.sql',1556231697,48281),('phabricator:20151210.land.1.refphid.sql',1556231697,9677),('phabricator:20151210.land.2.refphid.php',1556231697,629),('phabricator:20151215.phame.1.autotitle.sql',1556231697,20604),('phabricator:20151218.key.1.keyphid.sql',1556231697,13167),('phabricator:20151218.key.2.keyphid.php',1556231697,423),('phabricator:20151219.proj.01.prislug.sql',1556231697,13742),('phabricator:20151219.proj.02.prislugkey.sql',1556231697,8362),('phabricator:20151219.proj.03.copyslug.sql',1556231697,517),('phabricator:20151219.proj.04.dropslugkey.sql',1556231697,6993),('phabricator:20151219.proj.05.dropslug.sql',1556231697,14034),('phabricator:20151219.proj.06.defaultpolicy.php',1556231697,1187),('phabricator:20151219.proj.07.viewnull.sql',1556231697,17899),('phabricator:20151219.proj.08.editnull.sql',1556231697,17020),('phabricator:20151219.proj.09.joinnull.sql',1556231697,17501),('phabricator:20151219.proj.10.subcolumns.sql',1556231697,77351),('phabricator:20151219.proj.11.subprojectphids.sql',1556231697,14306),('phabricator:20151221.search.1.version.sql',1556231697,5434),('phabricator:20151221.search.2.ownersngrams.sql',1556231697,5494),('phabricator:20151221.search.3.reindex.php',1556231697,84),('phabricator:20151223.proj.01.paths.sql',1556231697,15641),('phabricator:20151223.proj.02.depths.sql',1556231697,15239),('phabricator:20151223.proj.03.pathkey.sql',1556231697,9414),('phabricator:20151223.proj.04.keycol.sql',1556231697,16788),('phabricator:20151223.proj.05.updatekeys.php',1556231697,483),('phabricator:20151223.proj.06.uniq.sql',1556231697,10101),('phabricator:20151226.reop.1.sql',1556231697,12605),('phabricator:20151227.proj.01.materialize.sql',1556231697,586),('phabricator:20151231.proj.01.icon.php',1556231697,2511),('phabricator:20160102.badges.award.sql',1556231697,6329),('phabricator:20160110.repo.01.slug.sql',1556231697,20421),('phabricator:20160110.repo.02.slug.php',1556231697,678),('phabricator:20160111.repo.01.slugx.sql',1556231697,1338),('phabricator:20160112.repo.01.uri.sql',1556231697,5759),('phabricator:20160112.repo.02.uri.index.php',1556231697,105),('phabricator:20160113.propanel.1.storage.sql',1556231697,6417),('phabricator:20160113.propanel.2.xaction.sql',1556231697,7222),('phabricator:20160119.project.1.silence.sql',1556231697,547),('phabricator:20160122.project.1.boarddefault.php',1556231697,759),('phabricator:20160124.people.1.icon.sql',1556231697,10389),('phabricator:20160124.people.2.icondefault.sql',1556231697,597),('phabricator:20160128.repo.1.pull.sql',1556231697,6188),('phabricator:20160201.revision.properties.1.sql',1556231697,13671),('phabricator:20160201.revision.properties.2.sql',1556231697,582),('phabricator:20160202.board.1.proxy.sql',1556231697,11473),('phabricator:20160202.ipv6.1.sql',1556231697,29431),('phabricator:20160202.ipv6.2.php',1556231697,1039),('phabricator:20160206.cover.1.sql',1556231697,16645),('phabricator:20160208.task.1.sql',1556231697,17354),('phabricator:20160208.task.2.sql',1556231697,28687),('phabricator:20160208.task.3.sql',1556231697,21722),('phabricator:20160212.proj.1.sql',1556231697,18838),('phabricator:20160212.proj.2.sql',1556231697,682),('phabricator:20160215.owners.policy.1.sql',1556231698,13159),('phabricator:20160215.owners.policy.2.sql',1556231698,13532),('phabricator:20160215.owners.policy.3.sql',1556231698,781),('phabricator:20160215.owners.policy.4.sql',1556231698,575),('phabricator:20160218.callsigns.1.sql',1556231698,18754),('phabricator:20160221.almanac.1.devicen.sql',1556231698,5402),('phabricator:20160221.almanac.2.devicei.php',1556231698,86),('phabricator:20160221.almanac.3.servicen.sql',1556231698,5514),('phabricator:20160221.almanac.4.servicei.php',1556231698,88),('phabricator:20160221.almanac.5.networkn.sql',1556231698,5797),('phabricator:20160221.almanac.6.networki.php',1556231698,98),('phabricator:20160221.almanac.7.namespacen.sql',1556231698,6222),('phabricator:20160221.almanac.8.namespace.sql',1556231698,6967),('phabricator:20160221.almanac.9.namespacex.sql',1556231698,6258),('phabricator:20160222.almanac.1.properties.php',1556231698,1785),('phabricator:20160223.almanac.1.bound.sql',1556231698,12636),('phabricator:20160223.almanac.2.lockbind.sql',1556231698,555),('phabricator:20160223.almanac.3.devicelock.sql',1556231698,12370),('phabricator:20160223.almanac.4.servicelock.sql',1556231698,12403),('phabricator:20160223.paste.fileedges.php',1556231698,563),('phabricator:20160225.almanac.1.disablebinding.sql',1556231698,13239),('phabricator:20160225.almanac.2.stype.sql',1556231698,5306),('phabricator:20160225.almanac.3.stype.php',1556231698,854),('phabricator:20160227.harbormaster.1.plann.sql',1556231698,5517),('phabricator:20160227.harbormaster.2.plani.php',1556231698,85),('phabricator:20160303.drydock.1.bluen.sql',1556231698,5916),('phabricator:20160303.drydock.2.bluei.php',1556231698,84),('phabricator:20160303.drydock.3.edge.sql',1556231698,9299),('phabricator:20160308.nuance.01.disabled.sql',1556231698,11938),('phabricator:20160308.nuance.02.cursordata.sql',1556231698,5933),('phabricator:20160308.nuance.03.sourcen.sql',1556231698,5421),('phabricator:20160308.nuance.04.sourcei.php',1556231698,84),('phabricator:20160308.nuance.05.sourcename.sql',1556231698,14051),('phabricator:20160308.nuance.06.label.sql',1556231698,14773),('phabricator:20160308.nuance.07.itemtype.sql',1556231698,12745),('phabricator:20160308.nuance.08.itemkey.sql',1556231698,12490),('phabricator:20160308.nuance.09.itemcontainer.sql',1556231698,14099),('phabricator:20160308.nuance.10.itemkeyu.sql',1556231698,525),('phabricator:20160308.nuance.11.requestor.sql',1556231698,16661),('phabricator:20160308.nuance.12.queue.sql',1556231698,12884),('phabricator:20160316.lfs.01.token.resource.sql',1556231698,14175),('phabricator:20160316.lfs.02.token.user.sql',1556231698,10063),('phabricator:20160316.lfs.03.token.properties.sql',1556231698,10329),('phabricator:20160316.lfs.04.token.default.sql',1556231698,508),('phabricator:20160317.lfs.01.ref.sql',1556231698,5342),('phabricator:20160321.nuance.01.taskbridge.sql',1556231698,17760),('phabricator:20160322.nuance.01.itemcommand.sql',1556231698,4975),('phabricator:20160323.badgemigrate.sql',1556231698,1649),('phabricator:20160329.nuance.01.requestor.sql',1556231698,3782),('phabricator:20160329.nuance.02.requestorsource.sql',1556231698,4055),('phabricator:20160329.nuance.03.requestorxaction.sql',1556231698,4785),('phabricator:20160329.nuance.04.requestorcomment.sql',1556231698,4420),('phabricator:20160330.badges.migratequality.sql',1556231698,15822),('phabricator:20160330.badges.qualityxaction.mig.sql',1556231698,2799),('phabricator:20160331.fund.comments.1.sql',1556231698,6871),('phabricator:20160404.oauth.1.xaction.sql',1556231698,6721),('phabricator:20160405.oauth.2.disable.sql',1556231698,10930),('phabricator:20160406.badges.ngrams.php',1556231698,97),('phabricator:20160406.badges.ngrams.sql',1556231698,6525),('phabricator:20160406.columns.1.php',1556231698,500),('phabricator:20160411.repo.1.version.sql',1556231698,5519),('phabricator:20160418.repouri.1.sql',1556231698,6074),('phabricator:20160418.repouri.2.sql',1556231698,11525),('phabricator:20160418.repoversion.1.sql',1556231698,10689),('phabricator:20160419.pushlog.1.sql',1556231698,14353),('phabricator:20160424.locks.1.sql',1556231698,10204),('phabricator:20160426.searchedge.sql',1556231698,9947),('phabricator:20160428.repo.1.urixaction.sql',1556231698,7021),('phabricator:20160503.repo.01.lpath.sql',1556231698,13307),('phabricator:20160503.repo.02.lpathkey.sql',1556231698,8490),('phabricator:20160503.repo.03.lpathmigrate.php',1556231698,504),('phabricator:20160503.repo.04.mirrormigrate.php',1556231698,850),('phabricator:20160503.repo.05.urimigrate.php',1556231698,424),('phabricator:20160510.repo.01.uriindex.php',1556231698,4595),('phabricator:20160513.owners.01.autoreview.sql',1556231698,10941),('phabricator:20160513.owners.02.autoreviewnone.sql',1556231698,564),('phabricator:20160516.owners.01.dominion.sql',1556231698,10783),('phabricator:20160516.owners.02.dominionstrong.sql',1556231698,572),('phabricator:20160517.oauth.01.edge.sql',1556231698,10687),('phabricator:20160518.ssh.01.activecol.sql',1556231698,10789),('phabricator:20160518.ssh.02.activeval.sql',1556231698,581),('phabricator:20160518.ssh.03.activekey.sql',1556231698,6783),('phabricator:20160519.ssh.01.xaction.sql',1556231698,6533),('phabricator:20160531.pref.01.xaction.sql',1556231698,7117),('phabricator:20160531.pref.02.datecreatecol.sql',1556231698,9742),('phabricator:20160531.pref.03.datemodcol.sql',1556231698,9161),('phabricator:20160531.pref.04.datecreateval.sql',1556231698,581),('phabricator:20160531.pref.05.datemodval.sql',1556231698,500),('phabricator:20160531.pref.06.phidcol.sql',1556231698,9802),('phabricator:20160531.pref.07.phidval.php',1556231698,812),('phabricator:20160601.user.01.cache.sql',1556231698,7441),('phabricator:20160601.user.02.copyprefs.php',1556231698,1761),('phabricator:20160601.user.03.removetime.sql',1556231698,15650),('phabricator:20160601.user.04.removetranslation.sql',1556231698,15109),('phabricator:20160601.user.05.removesex.sql',1556231698,14245),('phabricator:20160603.user.01.removedcenabled.sql',1556231698,13434),('phabricator:20160603.user.02.removedctab.sql',1556231698,14889),('phabricator:20160603.user.03.removedcvisible.sql',1556231698,14290),('phabricator:20160604.user.01.stringmailprefs.php',1556231698,605),('phabricator:20160604.user.02.removeimagecache.sql',1556231698,13598),('phabricator:20160605.user.01.prefnulluser.sql',1556231698,13603),('phabricator:20160605.user.02.prefbuiltin.sql',1556231698,9726),('phabricator:20160605.user.03.builtinunique.sql',1556231698,5808),('phabricator:20160616.phame.blog.header.1.sql',1556231698,10765),('phabricator:20160616.repo.01.oldref.sql',1556231698,5571),('phabricator:20160617.harbormaster.01.arelease.sql',1556231698,11995),('phabricator:20160618.phame.blog.subtitle.sql',1556231698,11187),('phabricator:20160620.phame.blog.parentdomain.2.sql',1556231698,11609),('phabricator:20160620.phame.blog.parentsite.1.sql',1556231698,13040),('phabricator:20160623.phame.blog.fulldomain.1.sql',1556231698,12008),('phabricator:20160623.phame.blog.fulldomain.2.sql',1556231698,564),('phabricator:20160623.phame.blog.fulldomain.3.sql',1556231698,1211),('phabricator:20160706.phame.blog.parentdomain.2.sql',1556231698,12418),('phabricator:20160706.phame.blog.parentsite.1.sql',1556231699,13036),('phabricator:20160707.calendar.01.stub.sql',1556231699,12754),('phabricator:20160711.files.01.builtin.sql',1556231699,15129),('phabricator:20160711.files.02.builtinkey.sql',1556231699,8836),('phabricator:20160713.event.01.host.sql',1556231699,15511),('phabricator:20160715.event.01.alldayfrom.sql',1556231699,14131),('phabricator:20160715.event.02.alldayto.sql',1556231699,12699),('phabricator:20160715.event.03.allday.php',1556231699,86),('phabricator:20160720.calendar.invitetxn.php',1556231699,1319),('phabricator:20160721.pack.01.pub.sql',1556231699,5812),('phabricator:20160721.pack.02.pubxaction.sql',1556231699,6139),('phabricator:20160721.pack.03.edge.sql',1556231699,10806),('phabricator:20160721.pack.04.pkg.sql',1556231699,6295),('phabricator:20160721.pack.05.pkgxaction.sql',1556231699,6625),('phabricator:20160721.pack.06.version.sql',1556231699,5517),('phabricator:20160721.pack.07.versionxaction.sql',1556231699,6541),('phabricator:20160722.pack.01.pubngrams.sql',1556231699,5991),('phabricator:20160722.pack.02.pkgngrams.sql',1556231699,6165),('phabricator:20160722.pack.03.versionngrams.sql',1556231699,5845),('phabricator:20160810.commit.01.summarylength.sql',1556231699,16647),('phabricator:20160824.connectionlog.sql',1556231699,3624),('phabricator:20160824.repohint.01.hint.sql',1556231699,5880),('phabricator:20160824.repohint.02.movebad.php',1556231699,610),('phabricator:20160824.repohint.03.nukebad.sql',1556231699,3549),('phabricator:20160825.ponder.sql',1556231699,635),('phabricator:20160829.pastebin.01.language.sql',1556231699,16331),('phabricator:20160829.pastebin.02.language.sql',1556231699,560),('phabricator:20160913.conpherence.topic.1.sql',1556231699,11148),('phabricator:20160919.repo.messagecount.sql',1556231699,10883),('phabricator:20160919.repo.messagedefault.sql',1556231699,3870),('phabricator:20160921.fileexternalrequest.sql',1556231699,6461),('phabricator:20160927.phurl.ngrams.php',1556231699,88),('phabricator:20160927.phurl.ngrams.sql',1556231699,5450),('phabricator:20160928.repo.messagecount.sql',1556231699,513),('phabricator:20160928.tokentoken.sql',1556231699,7220),('phabricator:20161003.cal.01.utcepoch.sql',1556231699,39888),('phabricator:20161003.cal.02.parameters.sql',1556231699,13545),('phabricator:20161004.cal.01.noepoch.php',1556231699,1824),('phabricator:20161005.cal.01.rrules.php',1556231699,313),('phabricator:20161005.cal.02.export.sql',1556231699,6093),('phabricator:20161005.cal.03.exportxaction.sql',1556231699,6495),('phabricator:20161005.conpherence.image.1.sql',1556231699,12059),('phabricator:20161005.conpherence.image.2.php',1556231699,85),('phabricator:20161011.conpherence.ngrams.php',1556231699,65),('phabricator:20161011.conpherence.ngrams.sql',1556231699,5332),('phabricator:20161012.cal.01.import.sql',1556231699,6181),('phabricator:20161012.cal.02.importxaction.sql',1556231699,6973),('phabricator:20161012.cal.03.eventimport.sql',1556231699,52644),('phabricator:20161013.cal.01.importlog.sql',1556231699,5063),('phabricator:20161016.conpherence.imagephids.sql',1556231699,10859),('phabricator:20161025.phortune.contact.1.sql',1556231699,9620),('phabricator:20161025.phortune.merchant.image.1.sql',1556231699,11081),('phabricator:20161026.calendar.01.importtriggers.sql',1556231699,23297),('phabricator:20161027.calendar.01.externalinvitee.sql',1556231699,5726),('phabricator:20161029.phortune.invoice.1.sql',1556231699,20493),('phabricator:20161031.calendar.01.seriesparent.sql',1556231699,13938),('phabricator:20161031.calendar.02.notifylog.sql',1556231699,5033),('phabricator:20161101.calendar.01.noholiday.sql',1556231699,3280),('phabricator:20161101.calendar.02.removecolumns.sql',1556231699,78114),('phabricator:20161104.calendar.01.availability.sql',1556231699,11128),('phabricator:20161104.calendar.02.availdefault.sql',1556231699,550),('phabricator:20161115.phamepost.01.subtitle.sql',1556231699,11909),('phabricator:20161115.phamepost.02.header.sql',1556231699,13239),('phabricator:20161121.cluster.01.hoststate.sql',1556231699,4824),('phabricator:20161124.search.01.stopwords.sql',1556231699,4622),('phabricator:20161125.search.01.stemmed.sql',1556231699,7243),('phabricator:20161130.search.01.manual.sql',1556231699,5811),('phabricator:20161130.search.02.rebuild.php',1556231699,1398),('phabricator:20161210.dashboards.01.author.sql',1556231699,11083),('phabricator:20161210.dashboards.02.author.php',1556231699,1611),('phabricator:20161211.menu.01.itemkey.sql',1556231699,4255),('phabricator:20161211.menu.02.itemprops.sql',1556231699,3566),('phabricator:20161211.menu.03.order.sql',1556231699,3844),('phabricator:20161212.dashboardpanel.01.author.sql',1556231699,10207),('phabricator:20161212.dashboardpanel.02.author.php',1556231699,836),('phabricator:20161212.dashboards.01.icon.sql',1556231699,11227),('phabricator:20161213.diff.01.hunks.php',1556231699,747),('phabricator:20161216.dashboard.ngram.01.sql',1556231699,10936),('phabricator:20161216.dashboard.ngram.02.php',1556231699,114),('phabricator:20170106.menu.01.customphd.sql',1556231699,10393),('phabricator:20170109.diff.01.commit.sql',1556231699,12971),('phabricator:20170119.menuitem.motivator.01.php',1556231699,407),('phabricator:20170131.dashboard.personal.01.php',1556231699,1445),('phabricator:20170301.subtype.01.col.sql',1556231699,12251),('phabricator:20170301.subtype.02.default.sql',1556231699,687),('phabricator:20170301.subtype.03.taskcol.sql',1556231699,17435),('phabricator:20170301.subtype.04.taskdefault.sql',1556231699,554),('phabricator:20170303.people.01.avatar.sql',1556231699,27615),('phabricator:20170313.reviewers.01.sql',1556231699,5349),('phabricator:20170316.rawfiles.01.php',1556231699,1720),('phabricator:20170320.reviewers.01.lastaction.sql',1556231699,11553),('phabricator:20170320.reviewers.02.lastcomment.sql',1556231699,10266),('phabricator:20170320.reviewers.03.migrate.php',1556231699,1127),('phabricator:20170322.reviewers.04.actor.sql',1556231699,10051),('phabricator:20170328.reviewers.01.void.sql',1556231699,11143),('phabricator:20170404.files.retroactive-content-hash.sql',1556231699,19835),('phabricator:20170406.hmac.01.keystore.sql',1556231699,5350),('phabricator:20170410.calendar.01.repair.php',1556231699,576),('phabricator:20170412.conpherence.01.picturecrop.sql',1556231699,499),('phabricator:20170413.conpherence.01.recentparty.sql',1556231700,11833),('phabricator:20170417.files.ngrams.sql',1556231700,5606),('phabricator:20170418.1.application.01.xaction.sql',1556231700,6959),('phabricator:20170418.1.application.02.edge.sql',1556231700,9907),('phabricator:20170418.files.isDeleted.sql',1556231700,16731),('phabricator:20170419.app.01.table.sql',1556231700,5395),('phabricator:20170419.thread.01.behind.sql',1556231700,11820),('phabricator:20170419.thread.02.status.sql',1556231700,11320),('phabricator:20170419.thread.03.touched.sql',1556231700,12098),('phabricator:20170424.user.01.verify.php',1556231700,450),('phabricator:20170427.owners.01.long.sql',1556231700,10936),('phabricator:20170504.1.slowvote.shuffle.sql',1556231700,13917),('phabricator:20170522.nuance.01.itemkey.sql',1556231700,13967),('phabricator:20170524.nuance.01.command.sql',1556231700,29175),('phabricator:20170524.nuance.02.commandstatus.sql',1556231700,10499),('phabricator:20170526.dropdifferentialdrafts.sql',1556231700,4528),('phabricator:20170526.milestones.php',1556231700,82),('phabricator:20170528.maniphestdupes.php',1556231700,407),('phabricator:20170612.repository.image.01.sql',1556231700,14513),('phabricator:20170614.taskstatus.sql',1556231700,22946),('phabricator:20170725.legalpad.date.01.sql',1556231700,1121),('phabricator:20170811.differential.01.status.php',1556231700,436),('phabricator:20170811.differential.02.modernstatus.sql',1556231700,1112),('phabricator:20170811.differential.03.modernxaction.php',1556231700,946),('phabricator:20170814.search.01.qconfig.sql',1556231700,5484),('phabricator:20170820.phame.01.post.views.sql',1556231700,12773),('phabricator:20170820.phame.02.post.views.sql',1556231700,492),('phabricator:20170824.search.01.saved.php',1556231700,1093),('phabricator:20170825.phame.01.post.views.sql',1556231700,14383),('phabricator:20170828.ferret.01.taskdoc.sql',1556231700,4720),('phabricator:20170828.ferret.02.taskfield.sql',1556231700,4430),('phabricator:20170828.ferret.03.taskngrams.sql',1556231700,4459),('phabricator:20170830.ferret.01.unique.sql',1556231700,11567),('phabricator:20170830.ferret.02.term.sql',1556231700,10544),('phabricator:20170905.ferret.01.diff.doc.sql',1556231700,4638),('phabricator:20170905.ferret.02.diff.field.sql',1556231700,4380),('phabricator:20170905.ferret.03.diff.ngrams.sql',1556231700,4849),('phabricator:20170907.ferret.01.user.doc.sql',1556231700,4719),('phabricator:20170907.ferret.02.user.field.sql',1556231700,4741),('phabricator:20170907.ferret.03.user.ngrams.sql',1556231700,4472),('phabricator:20170907.ferret.04.fund.doc.sql',1556231700,6308),('phabricator:20170907.ferret.05.fund.field.sql',1556231700,4669),('phabricator:20170907.ferret.06.fund.ngrams.sql',1556231700,4853),('phabricator:20170907.ferret.07.passphrase.doc.sql',1556231700,4554),('phabricator:20170907.ferret.08.passphrase.field.sql',1556231700,5115),('phabricator:20170907.ferret.09.passphrase.ngrams.sql',1556231700,4083),('phabricator:20170907.ferret.10.owners.doc.sql',1556231700,4723),('phabricator:20170907.ferret.11.owners.field.sql',1556231700,5061),('phabricator:20170907.ferret.12.owners.ngrams.sql',1556231700,4786),('phabricator:20170907.ferret.13.blog.doc.sql',1556231700,4252),('phabricator:20170907.ferret.14.blog.field.sql',1556231700,4923),('phabricator:20170907.ferret.15.blog.ngrams.sql',1556231700,4231),('phabricator:20170907.ferret.16.post.doc.sql',1556231700,4741),('phabricator:20170907.ferret.17.post.field.sql',1556231700,4888),('phabricator:20170907.ferret.18.post.ngrams.sql',1556231700,5208),('phabricator:20170907.ferret.19.project.doc.sql',1556231700,4908),('phabricator:20170907.ferret.20.project.field.sql',1556231700,5022),('phabricator:20170907.ferret.21.project.ngrams.sql',1556231700,4594),('phabricator:20170907.ferret.22.phriction.doc.sql',1556231700,4889),('phabricator:20170907.ferret.23.phriction.field.sql',1556231700,4387),('phabricator:20170907.ferret.24.phriction.ngrams.sql',1556231700,4437),('phabricator:20170907.ferret.25.event.doc.sql',1556231700,5550),('phabricator:20170907.ferret.26.event.field.sql',1556231700,4987),('phabricator:20170907.ferret.27.event.ngrams.sql',1556231700,4139),('phabricator:20170907.ferret.28.mock.doc.sql',1556231700,5204),('phabricator:20170907.ferret.29.mock.field.sql',1556231700,4481),('phabricator:20170907.ferret.30.mock.ngrams.sql',1556231700,4263),('phabricator:20170907.ferret.31.repo.doc.sql',1556231700,4498),('phabricator:20170907.ferret.32.repo.field.sql',1556231700,5632),('phabricator:20170907.ferret.33.repo.ngrams.sql',1556231700,4712),('phabricator:20170907.ferret.34.commit.doc.sql',1556231700,5410),('phabricator:20170907.ferret.35.commit.field.sql',1556231700,4832),('phabricator:20170907.ferret.36.commit.ngrams.sql',1556231700,4997),('phabricator:20170912.ferret.01.activity.php',1556231700,358),('phabricator:20170914.ref.01.position.sql',1556231700,4702),('phabricator:20170915.ref.01.migrate.php',1556231700,735),('phabricator:20170915.ref.02.drop.id.sql',1556231700,11053),('phabricator:20170915.ref.03.drop.closed.sql',1556231700,10493),('phabricator:20170915.ref.04.uniq.sql',1556231700,6301),('phabricator:20170918.ref.01.position.php',1556231700,6814),('phabricator:20171002.cngram.01.maniphest.sql',1556231700,6208),('phabricator:20171002.cngram.02.event.sql',1556231700,6453),('phabricator:20171002.cngram.03.revision.sql',1556231700,5686),('phabricator:20171002.cngram.04.fund.sql',1556231700,5388),('phabricator:20171002.cngram.05.owners.sql',1556231700,5625),('phabricator:20171002.cngram.06.passphrase.sql',1556231700,6665),('phabricator:20171002.cngram.07.blog.sql',1556231700,5687),('phabricator:20171002.cngram.08.post.sql',1556231700,5555),('phabricator:20171002.cngram.09.pholio.sql',1556231700,5669),('phabricator:20171002.cngram.10.phriction.sql',1556231700,5644),('phabricator:20171002.cngram.11.project.sql',1556231700,5239),('phabricator:20171002.cngram.12.user.sql',1556231700,6475),('phabricator:20171002.cngram.13.repository.sql',1556231700,5340),('phabricator:20171002.cngram.14.commit.sql',1556231700,5519),('phabricator:20171026.ferret.01.ponder.doc.sql',1556231700,4734),('phabricator:20171026.ferret.02.ponder.field.sql',1556231700,4312),('phabricator:20171026.ferret.03.ponder.ngrams.sql',1556231700,4486),('phabricator:20171026.ferret.04.ponder.cngrams.sql',1556231700,6303),('phabricator:20171026.ferret.05.ponder.index.php',1556231700,111),('phabricator:20171101.diff.01.active.sql',1556231700,13157),('phabricator:20171101.diff.02.populate.php',1556231700,466),('phabricator:20180119.bulk.01.silent.sql',1556231700,12179),('phabricator:20180120.auth.01.password.sql',1556231700,5465),('phabricator:20180120.auth.02.passwordxaction.sql',1556231700,7117),('phabricator:20180120.auth.03.vcsdata.sql',1556231700,1196),('phabricator:20180120.auth.04.vcsphid.php',1556231700,751),('phabricator:20180121.auth.01.vcsnuke.sql',1556231700,3980),('phabricator:20180121.auth.02.passsalt.sql',1556231700,9066),('phabricator:20180121.auth.03.accountdata.sql',1556231700,607),('phabricator:20180121.auth.04.accountphid.php',1556231700,431),('phabricator:20180121.auth.05.accountnuke.sql',1556231700,29120),('phabricator:20180121.auth.06.legacydigest.sql',1556231700,9699),('phabricator:20180121.auth.07.marklegacy.sql',1556231700,567),('phabricator:20180124.herald.01.repetition.sql',1556231700,17664),('phabricator:20180207.mail.01.task.sql',1556231700,18090),('phabricator:20180207.mail.02.revision.sql',1556231700,13051),('phabricator:20180207.mail.03.mock.sql',1556231700,11482),('phabricator:20180208.maniphest.01.close.sql',1556231700,34873),('phabricator:20180208.maniphest.02.populate.php',1556231700,494),('phabricator:20180209.hook.01.hook.sql',1556231700,5084),('phabricator:20180209.hook.02.hookxaction.sql',1556231700,6844),('phabricator:20180209.hook.03.hookrequest.sql',1556231700,4822),('phabricator:20180210.hunk.01.droplegacy.sql',1556231700,4058),('phabricator:20180210.hunk.02.renamemodern.sql',1556231700,4436),('phabricator:20180212.harbor.01.receiver.sql',1556231700,14179),('phabricator:20180214.harbor.01.aborted.php',1556231700,902),('phabricator:20180215.phriction.01.phidcol.sql',1556231701,12098),('phabricator:20180215.phriction.02.phidvalues.php',1556231701,643),('phabricator:20180215.phriction.03.descempty.sql',1556231701,535),('phabricator:20180215.phriction.04.descnull.sql',1556231701,14930),('phabricator:20180215.phriction.05.statustext.sql',1556231701,15691),('phabricator:20180215.phriction.06.statusvalue.sql',1556231701,904),('phabricator:20180218.fact.01.dim.key.sql',1556231701,5182),('phabricator:20180218.fact.02.dim.obj.sql',1556231701,4795),('phabricator:20180218.fact.03.data.int.sql',1556231701,4788),('phabricator:20180222.log.01.filephid.sql',1556231701,12884),('phabricator:20180223.log.01.bytelength.sql',1556231701,11783),('phabricator:20180223.log.02.chunkformat.sql',1556231701,10735),('phabricator:20180223.log.03.chunkdefault.sql',1556231701,508),('phabricator:20180223.log.04.linemap.sql',1556231701,12025),('phabricator:20180223.log.05.linemapdefault.sql',1556231701,597),('phabricator:20180228.log.01.offset.sql',1556231701,20084),('phabricator:20180305.lock.01.locklog.sql',1556231701,4480),('phabricator:20180306.opath.01.digest.sql',1556231701,11285),('phabricator:20180306.opath.02.digestpopulate.php',1556231701,636),('phabricator:20180306.opath.03.purge.php',1556231701,422),('phabricator:20180306.opath.04.unique.sql',1556231701,6155),('phabricator:20180306.opath.05.longpath.sql',1556231701,12989),('phabricator:20180306.opath.06.pathdisplay.sql',1556231701,10854),('phabricator:20180306.opath.07.copypaths.sql',1556231701,667),('phabricator:20180309.owners.01.primaryowner.sql',1556231701,12220),('phabricator:20180312.reviewers.01.options.sql',1556231701,10529),('phabricator:20180312.reviewers.02.optionsdefault.sql',1556231701,514),('phabricator:20180322.lock.01.identifier.sql',1556231701,18925),('phabricator:20180322.lock.02.wait.sql',1556231701,35843),('phabricator:20180326.lock.03.nonunique.sql',1556231701,5998),('phabricator:20180403.draft.01.broadcast.php',1556231701,689),('phabricator:20180410.almanac.01.iface.xaction.sql',1556231701,6457),('phabricator:20180418.alamanc.interface.unique.php',1556231701,8603),('phabricator:20180418.almanac.network.unique.php',1556231701,6736),('phabricator:20180419.phlux.edges.sql',1556231701,10016),('phabricator:20180423.mail.01.properties.sql',1556231701,5352),('phabricator:20180430.repo_identity.sql',1556231701,6953),('phabricator:20180504.owners.01.mailkey.php',1556231701,613),('phabricator:20180504.owners.02.rmkey.sql',1556231701,10972),('phabricator:20180504.owners.03.properties.sql',1556231701,11898),('phabricator:20180504.owners.04.default.sql',1556231701,626),('phabricator:20180504.repo_identity.author.sql',1556231701,11039),('phabricator:20180504.repo_identity.xaction.sql',1556231701,7323),('phabricator:20180509.repo_identity.commits.sql',1556231701,13684),('phabricator:20180730.phriction.01.spaces.sql',1556231701,12193),('phabricator:20180730.project.01.spaces.sql',1556231701,16504),('phabricator:20180809.repo_identities.activity.php',1556231701,412),('phabricator:20180827.drydock.01.acquired.sql',1556231701,11934),('phabricator:20180827.drydock.02.activated.sql',1556231701,10516),('phabricator:20180828.phriction.01.contentphid.sql',1556231701,12373),('phabricator:20180828.phriction.02.documentphid.sql',1556231701,12991),('phabricator:20180828.phriction.03.editedepoch.sql',1556231701,12601),('phabricator:20180828.phriction.04.migrate.php',1556231701,612),('phabricator:20180828.phriction.05.contentid.sql',1556231701,11315),('phabricator:20180828.phriction.06.c.documentid.php',1556231701,4931),('phabricator:20180828.phriction.06.documentid.sql',1556231701,12630),('phabricator:20180828.phriction.07.c.documentuniq.sql',1556231701,539),('phabricator:20180828.phriction.07.documentkey.sql',1556231701,6810),('phabricator:20180829.phriction.01.mailkey.php',1556231701,454),('phabricator:20180829.phriction.02.rmkey.sql',1556231701,12074),('phabricator:20180830.phriction.01.maxversion.sql',1556231701,12437),('phabricator:20180830.phriction.02.maxes.php',1556231701,429),('phabricator:20180910.audit.01.searches.php',1556231701,358),('phabricator:20180910.audit.02.string.sql',1556231701,27220),('phabricator:20180910.audit.03.status.php',1556231701,1070),('phabricator:20180910.audit.04.xactions.php',1556231701,1835),('phabricator:20180914.audit.01.mailkey.php',1556231701,501),('phabricator:20180914.audit.02.rmkey.sql',1556231701,15907),('phabricator:20180914.drydock.01.operationphid.sql',1556231701,11675),('phabricator:20181024.drydock.01.commandprops.sql',1556231701,10537),('phabricator:20181024.drydock.02.commanddefaults.sql',1556231701,562),('phabricator:20181031.board.01.queryreset.php',1556231701,2252),('phabricator:20181106.repo.01.sync.sql',1556231701,5414),('phabricator:20181106.repo.02.hook.sql',1556231701,10859),('phabricator:20181213.auth.01.sessionphid.sql',1556231701,11625),('phabricator:20181213.auth.02.populatephid.php',1556231701,418),('phabricator:20181213.auth.03.phidkey.sql',1556231701,8125),('phabricator:20181213.auth.04.longerhashes.sql',1556231701,15735),('phabricator:20181213.auth.05.longerloghashes.sql',1556231701,17050),('phabricator:20181213.auth.06.challenge.sql',1556231701,5795),('phabricator:20181214.auth.01.workflowkey.sql',1556231701,9956),('phabricator:20181217.auth.01.digest.sql',1556231701,10408),('phabricator:20181217.auth.02.ttl.sql',1556231701,10154),('phabricator:20181217.auth.03.completed.sql',1556231701,10782),('phabricator:20181218.pholio.01.imageauthor.sql',1556231701,11427),('phabricator:20181219.pholio.01.imagephid.sql',1556231701,11379),('phabricator:20181219.pholio.02.imagemigrate.php',1556231701,707),('phabricator:20181219.pholio.03.imageid.sql',1556231701,11959),('phabricator:20181220.pholio.01.mailkey.php',1556231701,444),('phabricator:20181220.pholio.02.dropmailkey.sql',1556231701,11687),('phabricator:20181228.auth.01.provider.sql',1556231701,4634),('phabricator:20181228.auth.02.xaction.sql',1556231701,6313),('phabricator:20181228.auth.03.name.sql',1556231701,10666),('phabricator:20190101.sms.01.drop.sql',1556231701,4467),('phabricator:20190115.mfa.01.provider.sql',1556231701,11158),('phabricator:20190115.mfa.02.migrate.php',1556231701,974),('phabricator:20190115.mfa.03.factorkey.sql',1556231701,10875),('phabricator:20190116.contact.01.number.sql',1556231701,5635),('phabricator:20190116.contact.02.xaction.sql',1556231701,6947),('phabricator:20190116.phortune.01.billing.sql',1556231701,9904),('phabricator:20190117.authmessage.01.message.sql',1556231701,4988),('phabricator:20190117.authmessage.02.xaction.sql',1556231701,6637),('phabricator:20190121.contact.01.primary.sql',1556231701,11548),('phabricator:20190127.project.01.subtype.sql',1556231701,17416),('phabricator:20190127.project.02.default.sql',1556231701,555),('phabricator:20190129.project.01.spaces.php',1556231701,406),('phabricator:20190206.external.01.legalpad.sql',1556231701,485),('phabricator:20190206.external.02.email.sql',1556231701,524),('phabricator:20190206.external.03.providerphid.sql',1556231701,13239),('phabricator:20190206.external.04.providerlink.php',1556231701,698),('phabricator:20190207.packages.01.state.sql',1556231702,10942),('phabricator:20190207.packages.02.migrate.sql',1556231702,565),('phabricator:20190207.packages.03.drop.sql',1556231702,11126),('phabricator:20190207.packages.04.xactions.php',1556231702,1229),('phabricator:20190215.daemons.01.dropdataid.php',1556231702,5352),('phabricator:20190215.daemons.02.nulldataid.sql',1556231702,13353),('phabricator:20190215.harbor.01.stringindex.sql',1556231702,5418),('phabricator:20190215.harbor.02.stringcol.sql',1556231702,10942),('phabricator:20190220.daemon_worker.completed.01.sql',1556231702,12323),('phabricator:20190220.daemon_worker.completed.02.sql',1556231702,12844),('phabricator:20190226.harbor.01.planprops.sql',1556231702,10963),('phabricator:20190226.harbor.02.planvalue.sql',1556231702,644),('phabricator:20190307.herald.01.comments.sql',1556231702,4292),('phabricator:20190312.triggers.01.trigger.sql',1556231702,5749),('phabricator:20190312.triggers.02.xaction.sql',1556231702,7189),('phabricator:20190312.triggers.03.triggerphid.sql',1556231702,12671),('phabricator:20190322.triggers.01.usage.sql',1556231702,5436),('phabricator:20190329.portals.01.create.sql',1556231702,4633),('phabricator:20190329.portals.02.xaction.sql',1556231702,8280),('phabricator:20190410.portals.01.ferret.doc.sql',1556231702,5198),('phabricator:20190410.portals.02.ferret.field.sql',1556231702,4797),('phabricator:20190410.portals.03.ferret.ngrams.sql',1556231702,4275),('phabricator:20190410.portals.04.ferret.cngrams.sql',1556231702,5990),('phabricator:20190412.dashboard.01.panels.php',1556231702,453),('phabricator:20190412.dashboard.02.install.sql',1556231702,3768),('phabricator:20190412.dashboard.03.dashngrams.sql',1556231702,4706),('phabricator:20190412.dashboard.04.panelngrams.sql',1556231702,4492),('phabricator:20190412.dashboard.05.dferret.doc.sql',1556231702,4528),('phabricator:20190412.dashboard.06.dferret.field.sql',1556231702,5105),('phabricator:20190412.dashboard.07.dferret.ngrams.sql',1556231702,4069),('phabricator:20190412.dashboard.08.dferret.cngrams.sql',1556231702,5099),('phabricator:20190412.dashboard.09.pferret.doc.sql',1556231702,4827),('phabricator:20190412.dashboard.10.pferret.field.sql',1556231702,5374),('phabricator:20190412.dashboard.11.pferret.ngrams.sql',1556231702,5073),('phabricator:20190412.dashboard.12.pferret.cngrams.sql',1556231702,5528),('phabricator:20190412.dashboard.13.rebuild.php',1556231702,7019),('phabricator:20190412.herald.01.rebuild.php',1556231702,1878),('phabricator:20190416.chart.01.storage.sql',1556231702,4935),('phabricator:daemonstatus.sql',1556231688,NULL),('phabricator:daemonstatuskey.sql',1556231689,NULL),('phabricator:daemontaskarchive.sql',1556231689,NULL),('phabricator:db.almanac',1556231684,NULL),('phabricator:db.application',1556231684,NULL),('phabricator:db.audit',1556231684,NULL),('phabricator:db.auth',1556231684,NULL),('phabricator:db.badges',1556231684,NULL),('phabricator:db.cache',1556231684,NULL),('phabricator:db.calendar',1556231684,NULL),('phabricator:db.chatlog',1556231684,NULL),('phabricator:db.conduit',1556231684,NULL),('phabricator:db.config',1556231684,NULL),('phabricator:db.conpherence',1556231684,NULL),('phabricator:db.countdown',1556231684,NULL),('phabricator:db.daemon',1556231684,NULL),('phabricator:db.dashboard',1556231684,NULL),('phabricator:db.differential',1556231684,NULL),('phabricator:db.diviner',1556231684,NULL),('phabricator:db.doorkeeper',1556231684,NULL),('phabricator:db.draft',1556231684,NULL),('phabricator:db.drydock',1556231684,NULL),('phabricator:db.fact',1556231684,NULL),('phabricator:db.feed',1556231684,NULL),('phabricator:db.file',1556231684,NULL),('phabricator:db.flag',1556231684,NULL),('phabricator:db.fund',1556231684,NULL),('phabricator:db.harbormaster',1556231684,NULL),('phabricator:db.herald',1556231684,NULL),('phabricator:db.legalpad',1556231684,NULL),('phabricator:db.maniphest',1556231684,NULL),('phabricator:db.meta_data',1556231684,NULL),('phabricator:db.metamta',1556231684,NULL),('phabricator:db.multimeter',1556231684,NULL),('phabricator:db.nuance',1556231684,NULL),('phabricator:db.oauth_server',1556231684,NULL),('phabricator:db.owners',1556231684,NULL),('phabricator:db.packages',1556231684,NULL),('phabricator:db.passphrase',1556231684,NULL),('phabricator:db.pastebin',1556231684,NULL),('phabricator:db.phame',1556231684,NULL),('phabricator:db.phlux',1556231684,NULL),('phabricator:db.pholio',1556231684,NULL),('phabricator:db.phortune',1556231684,NULL),('phabricator:db.phragment',1556231684,NULL),('phabricator:db.phrequent',1556231684,NULL),('phabricator:db.phriction',1556231684,NULL),('phabricator:db.phurl',1556231684,NULL),('phabricator:db.policy',1556231684,NULL),('phabricator:db.ponder',1556231684,NULL),('phabricator:db.project',1556231684,NULL),('phabricator:db.releeph',1556231684,NULL),('phabricator:db.repository',1556231684,NULL),('phabricator:db.search',1556231684,NULL),('phabricator:db.slowvote',1556231684,NULL),('phabricator:db.spaces',1556231684,NULL),('phabricator:db.system',1556231684,NULL),('phabricator:db.timeline',1556231684,NULL),('phabricator:db.token',1556231684,NULL),('phabricator:db.user',1556231684,NULL),('phabricator:db.worker',1556231684,NULL),('phabricator:db.xhpast',1556231684,NULL),('phabricator:db.xhpastview',1556231684,NULL),('phabricator:db.xhprof',1556231684,NULL),('phabricator:differentialbookmarks.sql',1556231688,NULL),('phabricator:draft-metadata.sql',1556231689,NULL),('phabricator:dropfileproxyimage.sql',1556231689,NULL),('phabricator:drydockresoucetype.sql',1556231689,NULL),('phabricator:drydocktaskid.sql',1556231689,NULL),('phabricator:edgetype.sql',1556231689,NULL),('phabricator:emailtable.sql',1556231688,NULL),('phabricator:emailtableport.sql',1556231688,NULL),('phabricator:emailtableremove.sql',1556231688,NULL),('phabricator:fact-raw.sql',1556231688,NULL),('phabricator:harbormasterobject.sql',1556231688,NULL),('phabricator:holidays.sql',1556231688,NULL),('phabricator:ldapinfo.sql',1556231688,NULL),('phabricator:legalpad-mailkey-populate.php',1556231690,NULL),('phabricator:legalpad-mailkey.sql',1556231690,NULL),('phabricator:liskcounters-task.sql',1556231689,NULL),('phabricator:liskcounters.php',1556231689,NULL),('phabricator:liskcounters.sql',1556231689,NULL),('phabricator:maniphestxcache.sql',1556231688,NULL),('phabricator:markupcache.sql',1556231688,NULL),('phabricator:migrate-differential-dependencies.php',1556231688,NULL),('phabricator:migrate-maniphest-dependencies.php',1556231688,NULL),('phabricator:migrate-maniphest-revisions.php',1556231688,NULL),('phabricator:migrate-project-edges.php',1556231688,NULL),('phabricator:owners-exclude.sql',1556231689,NULL),('phabricator:pastepolicy.sql',1556231689,NULL),('phabricator:phameblog.sql',1556231688,NULL),('phabricator:phamedomain.sql',1556231689,NULL),('phabricator:phameoneblog.sql',1556231689,NULL),('phabricator:phamepolicy.sql',1556231689,NULL),('phabricator:phiddrop.sql',1556231688,NULL),('phabricator:pholio.sql',1556231689,NULL),('phabricator:policy-project.sql',1556231689,NULL),('phabricator:ponder-comments.sql',1556231689,NULL),('phabricator:ponder-mailkey-populate.php',1556231689,NULL),('phabricator:ponder-mailkey.sql',1556231689,NULL),('phabricator:ponder.sql',1556231688,NULL),('phabricator:releeph.sql',1556231689,NULL),('phabricator:repository-lint.sql',1556231689,NULL),('phabricator:statustxt.sql',1556231689,NULL),('phabricator:symbolcontexts.sql',1556231688,NULL),('phabricator:testdatabase.sql',1556231688,NULL),('phabricator:threadtopic.sql',1556231688,NULL),('phabricator:userstatus.sql',1556231688,NULL),('phabricator:usertranslation.sql',1556231688,NULL),('phabricator:xhprof.sql',1556231689,NULL);
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_metamta` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_metamta`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_metamta`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_metamta`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `metamta_applicationemail` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`applicationPHID` varbinary(64) NOT NULL,
`address` varchar(128) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`configData` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`spacePHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_address` (`address`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_application` (`applicationPHID`),
KEY `key_space` (`spacePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_metamta`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `metamta_applicationemailtransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_metamta`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `metamta_mail` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`actorPHID` varbinary(64) DEFAULT NULL,
`parameters` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`message` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT},
`relatedPHID` varbinary(64) DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `relatedPHID` (`relatedPHID`),
KEY `key_created` (`dateCreated`),
KEY `key_actorPHID` (`actorPHID`),
KEY `status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_metamta`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `metamta_mailproperties` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`mailProperties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_metamta`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `metamta_receivedmail` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`headers` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`bodies` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`attachments` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`relatedPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`message` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT},
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`messageIDHash` binary(12) NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `relatedPHID` (`relatedPHID`),
KEY `authorPHID` (`authorPHID`),
KEY `key_messageIDHash` (`messageIDHash`),
KEY `key_created` (`dateCreated`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_multimeter` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_multimeter`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `multimeter_context` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`nameHash` binary(12) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_hash` (`nameHash`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_multimeter`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `multimeter_event` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`eventType` int(10) unsigned NOT NULL,
`eventLabelID` int(10) unsigned NOT NULL,
`resourceCost` bigint(20) NOT NULL,
`sampleRate` int(10) unsigned NOT NULL,
`eventContextID` int(10) unsigned NOT NULL,
`eventHostID` int(10) unsigned NOT NULL,
`eventViewerID` int(10) unsigned NOT NULL,
`epoch` int(10) unsigned NOT NULL,
`requestKey` binary(12) NOT NULL,
PRIMARY KEY (`id`),
KEY `key_request` (`requestKey`),
KEY `key_type` (`eventType`,`epoch`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_multimeter`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `multimeter_host` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`nameHash` binary(12) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_hash` (`nameHash`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_multimeter`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `multimeter_label` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`nameHash` binary(12) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_hash` (`nameHash`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_multimeter`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `multimeter_viewer` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`nameHash` binary(12) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_hash` (`nameHash`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_nuance` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_nuance`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_nuance`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_nuance`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `nuance_importcursordata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`sourcePHID` varbinary(64) NOT NULL,
`cursorKey` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`cursorType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_source` (`sourcePHID`,`cursorKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_nuance`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `nuance_item` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`requestorPHID` varbinary(64) DEFAULT NULL,
`sourcePHID` varbinary(64) NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`mailKey` binary(20) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`queuePHID` varbinary(64) DEFAULT NULL,
`itemType` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`itemKey` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`itemContainerKey` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_item` (`sourcePHID`,`itemKey`),
KEY `key_source` (`sourcePHID`,`status`),
KEY `key_owner` (`ownerPHID`,`status`),
KEY `key_requestor` (`requestorPHID`,`status`),
KEY `key_queue` (`queuePHID`,`status`),
KEY `key_container` (`sourcePHID`,`itemContainerKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_nuance`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `nuance_itemcommand` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`itemPHID` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`command` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`parameters` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`queuePHID` varbinary(64) DEFAULT NULL,
`status` varchar(64) COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_pending` (`itemPHID`,`status`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_nuance`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `nuance_itemtransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_nuance`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `nuance_itemtransaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_nuance`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `nuance_queue` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`mailKey` binary(20) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_nuance`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `nuance_queuetransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_nuance`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `nuance_queuetransaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_nuance`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `nuance_source` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`type` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`mailKey` binary(20) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`defaultQueuePHID` varbinary(64) NOT NULL,
`isDisabled` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_type` (`type`,`dateModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_nuance`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `nuance_sourcename_ngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_object` (`objectID`),
KEY `key_ngram` (`ngram`,`objectID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_nuance`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `nuance_sourcetransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_nuance`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `nuance_sourcetransaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_oauth_server` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_oauth_server`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_oauth_server`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_oauth_server`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `oauth_server_oauthclientauthorization` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`userPHID` varbinary(64) NOT NULL,
`clientPHID` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`scope` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `phid` (`phid`),
UNIQUE KEY `userPHID` (`userPHID`,`clientPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_oauth_server`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `oauth_server_oauthserveraccesstoken` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`token` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`userPHID` varbinary(64) NOT NULL,
`clientPHID` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `token` (`token`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_oauth_server`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `oauth_server_oauthserverauthorizationcode` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`code` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`clientPHID` varbinary(64) NOT NULL,
`clientSecret` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`userPHID` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`redirectURI` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_oauth_server`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `oauth_server_oauthserverclient` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`secret` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`redirectURI` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`creatorPHID` varbinary(64) NOT NULL,
`isTrusted` tinyint(1) NOT NULL DEFAULT '0',
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`isDisabled` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `creatorPHID` (`creatorPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_oauth_server`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `oauth_server_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_owners` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_owners`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_owners`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_owners`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `owners_customfieldnumericindex` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`indexKey` binary(12) NOT NULL,
`indexValue` bigint(20) NOT NULL,
PRIMARY KEY (`id`),
KEY `key_join` (`objectPHID`,`indexKey`,`indexValue`),
KEY `key_find` (`indexKey`,`indexValue`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_owners`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `owners_customfieldstorage` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`fieldIndex` binary(12) NOT NULL,
`fieldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `objectPHID` (`objectPHID`,`fieldIndex`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_owners`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `owners_customfieldstringindex` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`indexKey` binary(12) NOT NULL,
`indexValue` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_join` (`objectPHID`,`indexKey`,`indexValue`(64)),
KEY `key_find` (`indexKey`,`indexValue`(64))
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_owners`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `owners_name_ngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_object` (`objectID`),
KEY `key_ngram` (`ngram`,`objectID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_owners`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `owners_owner` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`packageID` int(10) unsigned NOT NULL,
`userPHID` varbinary(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `packageID` (`packageID`,`userPHID`),
KEY `userPHID` (`userPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_owners`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `owners_package` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`description` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`autoReview` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dominion` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`auditingState` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_owners`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `owners_package_fdocument` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`epochCreated` int(10) unsigned NOT NULL,
`epochModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_owner` (`ownerPHID`),
KEY `key_created` (`epochCreated`),
KEY `key_modified` (`epochModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_owners`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `owners_package_ffield` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`fieldKey` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_owners`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `owners_package_fngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_ngram` (`ngram`,`documentID`),
KEY `key_object` (`documentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_owners`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `owners_package_fngrams_common` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`needsCollection` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ngram` (`ngram`),
KEY `key_collect` (`needsCollection`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_owners`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `owners_packagetransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_owners`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `owners_path` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`packageID` int(10) unsigned NOT NULL,
`repositoryPHID` varbinary(64) NOT NULL,
`path` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`excluded` tinyint(1) NOT NULL DEFAULT '0',
`pathIndex` binary(12) NOT NULL,
`pathDisplay` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_path` (`packageID`,`repositoryPHID`,`pathIndex`),
KEY `key_repository` (`repositoryPHID`,`pathIndex`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_packages` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_packages`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_packages`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_packages`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `packages_package` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(64) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`publisherPHID` varbinary(64) NOT NULL,
`packageKey` varchar(64) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_package` (`publisherPHID`,`packageKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_packages`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `packages_packagename_ngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_object` (`objectID`),
KEY `key_ngram` (`ngram`,`objectID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_packages`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `packages_packagetransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_packages`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `packages_publisher` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(64) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`publisherKey` varchar(64) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_publisher` (`publisherKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_packages`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `packages_publishername_ngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_object` (`objectID`),
KEY `key_ngram` (`ngram`,`objectID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_packages`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `packages_publishertransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_packages`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `packages_version` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(64) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`packagePHID` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_package` (`packagePHID`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_packages`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `packages_versionname_ngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_object` (`objectID`),
KEY `key_ngram` (`ngram`,`objectID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_packages`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `packages_versiontransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_passphrase` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_passphrase`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_passphrase`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_passphrase`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `passphrase_credential` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`credentialType` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`providesType` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`description` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`username` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`secretID` int(10) unsigned DEFAULT NULL,
`isDestroyed` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`isLocked` tinyint(1) NOT NULL,
`allowConduit` tinyint(1) NOT NULL DEFAULT '0',
`authorPHID` varbinary(64) NOT NULL,
`spacePHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_secret` (`secretID`),
KEY `key_type` (`credentialType`),
KEY `key_provides` (`providesType`),
KEY `key_space` (`spacePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_passphrase`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `passphrase_credential_fdocument` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`epochCreated` int(10) unsigned NOT NULL,
`epochModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_owner` (`ownerPHID`),
KEY `key_created` (`epochCreated`),
KEY `key_modified` (`epochModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_passphrase`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `passphrase_credential_ffield` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`fieldKey` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_passphrase`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `passphrase_credential_fngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_ngram` (`ngram`,`documentID`),
KEY `key_object` (`documentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_passphrase`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `passphrase_credential_fngrams_common` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`needsCollection` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ngram` (`ngram`),
KEY `key_collect` (`needsCollection`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_passphrase`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `passphrase_credentialtransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_passphrase`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `passphrase_secret` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`secretData` longblob NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_pastebin` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_pastebin`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_pastebin`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_pastebin`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `pastebin_paste` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`filePHID` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`language` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`parentPHID` varbinary(64) DEFAULT NULL,
`viewPolicy` varbinary(64) DEFAULT NULL,
`editPolicy` varbinary(64) NOT NULL,
`mailKey` binary(20) NOT NULL,
`spacePHID` varbinary(64) DEFAULT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `parentPHID` (`parentPHID`),
KEY `authorPHID` (`authorPHID`),
KEY `key_dateCreated` (`dateCreated`),
KEY `key_language` (`language`),
KEY `key_space` (`spacePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_pastebin`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `pastebin_pastetransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_pastebin`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `pastebin_pastetransaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`lineNumber` int(10) unsigned DEFAULT NULL,
`lineLength` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_phame` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_phame`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phame`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phame`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phame_blog` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`description` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`domain` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`configData` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`creatorPHID` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`viewPolicy` varbinary(64) DEFAULT NULL,
`editPolicy` varbinary(64) DEFAULT NULL,
`mailKey` binary(20) NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`profileImagePHID` varbinary(64) DEFAULT NULL,
`headerImagePHID` varbinary(64) DEFAULT NULL,
`subtitle` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`parentDomain` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`parentSite` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`domainFullURI` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `phid` (`phid`),
UNIQUE KEY `domain` (`domain`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phame`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phame_blog_fdocument` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`epochCreated` int(10) unsigned NOT NULL,
`epochModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_owner` (`ownerPHID`),
KEY `key_created` (`epochCreated`),
KEY `key_modified` (`epochModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phame`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phame_blog_ffield` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`fieldKey` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phame`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phame_blog_fngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_ngram` (`ngram`,`documentID`),
KEY `key_object` (`documentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phame`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phame_blog_fngrams_common` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`needsCollection` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ngram` (`ngram`),
KEY `key_collect` (`needsCollection`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phame`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phame_blogtransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phame`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phame_post` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`bloggerPHID` varbinary(64) NOT NULL,
`title` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL,
`phameTitle` varchar(64) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} DEFAULT NULL,
`body` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT},
`visibility` int(10) unsigned NOT NULL DEFAULT '0',
`configData` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT},
`datePublished` int(10) unsigned NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`blogPHID` varbinary(64) DEFAULT NULL,
`mailKey` binary(20) NOT NULL,
`subtitle` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`headerImagePHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `phid` (`phid`),
KEY `bloggerPosts` (`bloggerPHID`,`visibility`,`datePublished`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phame`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phame_post_fdocument` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`epochCreated` int(10) unsigned NOT NULL,
`epochModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_owner` (`ownerPHID`),
KEY `key_created` (`epochCreated`),
KEY `key_modified` (`epochModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phame`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phame_post_ffield` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`fieldKey` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phame`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phame_post_fngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_ngram` (`ngram`,`documentID`),
KEY `key_object` (`documentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phame`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phame_post_fngrams_common` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`needsCollection` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ngram` (`ngram`),
KEY `key_collect` (`needsCollection`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phame`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phame_posttransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phame`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phame_posttransaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_phlux` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_phlux`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phlux`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phlux`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phlux_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phlux`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phlux_variable` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`variableKey` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`variableValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_key` (`variableKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_pholio` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_pholio`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_pholio`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_pholio`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `pholio_image` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`filePHID` varbinary(64) NOT NULL,
`name` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`description` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`sequence` int(10) unsigned NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`isObsolete` tinyint(1) NOT NULL DEFAULT '0',
`replacesImagePHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`mockPHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_mock` (`mockPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_pholio`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `pholio_mock` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`description` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`coverPHID` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`status` varchar(12) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`spacePHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `authorPHID` (`authorPHID`),
KEY `key_space` (`spacePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_pholio`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `pholio_mock_fdocument` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`epochCreated` int(10) unsigned NOT NULL,
`epochModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_owner` (`ownerPHID`),
KEY `key_created` (`epochCreated`),
KEY `key_modified` (`epochModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_pholio`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `pholio_mock_ffield` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`fieldKey` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_pholio`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `pholio_mock_fngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_ngram` (`ngram`,`documentID`),
KEY `key_object` (`documentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_pholio`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `pholio_mock_fngrams_common` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`needsCollection` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ngram` (`ngram`),
KEY `key_collect` (`needsCollection`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_pholio`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `pholio_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_pholio`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `pholio_transaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`imageID` int(10) unsigned DEFAULT NULL,
`x` int(10) unsigned DEFAULT NULL,
`y` int(10) unsigned DEFAULT NULL,
`width` int(10) unsigned DEFAULT NULL,
`height` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`),
UNIQUE KEY `key_draft` (`authorPHID`,`imageID`,`transactionPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_phortune` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_phortune`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phortune`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phortune`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phortune_account` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`billingName` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`billingAddress` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phortune`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phortune_accounttransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phortune`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phortune_cart` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`accountPHID` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`cartClass` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`merchantPHID` varbinary(64) NOT NULL,
`mailKey` binary(20) NOT NULL,
`subscriptionPHID` varbinary(64) DEFAULT NULL,
`isInvoice` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_account` (`accountPHID`),
KEY `key_merchant` (`merchantPHID`),
KEY `key_subscription` (`subscriptionPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phortune`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phortune_carttransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phortune`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phortune_charge` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`accountPHID` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`cartPHID` varbinary(64) NOT NULL,
`paymentMethodPHID` varbinary(64) DEFAULT NULL,
`amountAsCurrency` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`merchantPHID` varbinary(64) NOT NULL,
`providerPHID` varbinary(64) NOT NULL,
`amountRefundedAsCurrency` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`refundingPHID` varbinary(64) DEFAULT NULL,
`refundedChargePHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_cart` (`cartPHID`),
KEY `key_account` (`accountPHID`),
KEY `key_merchant` (`merchantPHID`),
KEY `key_provider` (`providerPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phortune`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phortune_merchant` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`description` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contactInfo` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`profileImagePHID` varbinary(64) DEFAULT NULL,
`invoiceEmail` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`invoiceFooter` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phortune`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phortune_merchanttransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phortune`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phortune_paymentmethod` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`status` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`accountPHID` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`brand` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`expires` varchar(16) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`lastFourDigits` varchar(16) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`merchantPHID` varbinary(64) NOT NULL,
`providerPHID` varbinary(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_account` (`accountPHID`,`status`),
KEY `key_merchant` (`merchantPHID`,`accountPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phortune`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phortune_paymentproviderconfig` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`merchantPHID` varbinary(64) NOT NULL,
`providerClassKey` binary(12) NOT NULL,
`providerClass` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`isEnabled` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_merchant` (`merchantPHID`,`providerClassKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phortune`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phortune_paymentproviderconfigtransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phortune`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phortune_product` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`productClassKey` binary(12) NOT NULL,
`productClass` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`productRefKey` binary(12) NOT NULL,
`productRef` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_product` (`productClassKey`,`productRefKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phortune`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phortune_purchase` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`productPHID` varbinary(64) NOT NULL,
`accountPHID` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`cartPHID` varbinary(64) DEFAULT NULL,
`basePriceAsCurrency` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`quantity` int(10) unsigned NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_cart` (`cartPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phortune`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phortune_subscription` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`accountPHID` varbinary(64) NOT NULL,
`merchantPHID` varbinary(64) NOT NULL,
`triggerPHID` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`subscriptionClassKey` binary(12) NOT NULL,
`subscriptionClass` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`subscriptionRefKey` binary(12) NOT NULL,
`subscriptionRef` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`defaultPaymentMethodPHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_subscription` (`subscriptionClassKey`,`subscriptionRefKey`),
KEY `key_account` (`accountPHID`),
KEY `key_merchant` (`merchantPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
-CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_phragment` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
-
-USE `{$NAMESPACE}_phragment`;
-
- SET NAMES utf8 ;
-
- SET character_set_client = {$CHARSET} ;
-
-CREATE TABLE `edge` (
- `src` varbinary(64) NOT NULL,
- `type` int(10) unsigned NOT NULL,
- `dst` varbinary(64) NOT NULL,
- `dateCreated` int(10) unsigned NOT NULL,
- `seq` int(10) unsigned NOT NULL,
- `dataID` int(10) unsigned DEFAULT NULL,
- PRIMARY KEY (`src`,`type`,`dst`),
- UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
- KEY `src` (`src`,`type`,`dateCreated`,`seq`)
-) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
-
-USE `{$NAMESPACE}_phragment`;
-
- SET NAMES utf8 ;
-
- SET character_set_client = {$CHARSET} ;
-
-CREATE TABLE `edgedata` (
- `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
- `data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
-
-USE `{$NAMESPACE}_phragment`;
-
- SET NAMES utf8 ;
-
- SET character_set_client = {$CHARSET} ;
-
-CREATE TABLE `phragment_fragment` (
- `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
- `phid` varbinary(64) NOT NULL,
- `path` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `depth` int(10) unsigned NOT NULL,
- `latestVersionPHID` varbinary(64) DEFAULT NULL,
- `viewPolicy` varbinary(64) NOT NULL,
- `editPolicy` varbinary(64) NOT NULL,
- `dateCreated` int(10) unsigned NOT NULL,
- `dateModified` int(10) unsigned NOT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `key_phid` (`phid`),
- UNIQUE KEY `key_path` (`path`)
-) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
-
-USE `{$NAMESPACE}_phragment`;
-
- SET NAMES utf8 ;
-
- SET character_set_client = {$CHARSET} ;
-
-CREATE TABLE `phragment_fragmentversion` (
- `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
- `phid` varbinary(64) NOT NULL,
- `sequence` int(10) unsigned NOT NULL,
- `fragmentPHID` varbinary(64) NOT NULL,
- `filePHID` varbinary(64) DEFAULT NULL,
- `dateCreated` int(10) unsigned NOT NULL,
- `dateModified` int(10) unsigned NOT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `key_version` (`fragmentPHID`,`sequence`),
- UNIQUE KEY `key_phid` (`phid`)
-) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
-
-USE `{$NAMESPACE}_phragment`;
-
- SET NAMES utf8 ;
-
- SET character_set_client = {$CHARSET} ;
-
-CREATE TABLE `phragment_snapshot` (
- `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
- `phid` varbinary(64) NOT NULL,
- `primaryFragmentPHID` varbinary(64) NOT NULL,
- `name` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `dateCreated` int(10) unsigned NOT NULL,
- `dateModified` int(10) unsigned NOT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `key_phid` (`phid`),
- UNIQUE KEY `key_name` (`primaryFragmentPHID`,`name`)
-) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
-
-USE `{$NAMESPACE}_phragment`;
-
- SET NAMES utf8 ;
-
- SET character_set_client = {$CHARSET} ;
-
-CREATE TABLE `phragment_snapshotchild` (
- `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
- `snapshotPHID` varbinary(64) NOT NULL,
- `fragmentPHID` varbinary(64) NOT NULL,
- `fragmentVersionPHID` varbinary(64) DEFAULT NULL,
- `dateCreated` int(10) unsigned NOT NULL,
- `dateModified` int(10) unsigned NOT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `key_child` (`snapshotPHID`,`fragmentPHID`,`fragmentVersionPHID`)
-) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
-
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_phrequent` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_phrequent`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phrequent_usertime` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`userPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) DEFAULT NULL,
`note` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT},
`dateStarted` int(10) unsigned NOT NULL,
`dateEnded` int(10) unsigned DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_phriction` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_phriction`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phriction`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phriction`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phriction_content` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`version` int(10) unsigned NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`title` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`slug` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`description` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`changeType` int(10) unsigned NOT NULL DEFAULT '0',
`changeRef` int(10) unsigned DEFAULT NULL,
`phid` varbinary(64) NOT NULL,
`documentPHID` varbinary(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_version` (`documentPHID`,`version`),
UNIQUE KEY `key_phid` (`phid`),
KEY `authorPHID` (`authorPHID`),
KEY `slug` (`slug`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phriction`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phriction_document` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`slug` varchar(128) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`depth` int(10) unsigned NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`spacePHID` varbinary(64) DEFAULT NULL,
`contentPHID` varbinary(64) NOT NULL,
`editedEpoch` int(10) unsigned NOT NULL,
`maxVersion` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `slug` (`slug`),
UNIQUE KEY `depth` (`depth`,`slug`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_space` (`spacePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phriction`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phriction_document_fdocument` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`epochCreated` int(10) unsigned NOT NULL,
`epochModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_owner` (`ownerPHID`),
KEY `key_created` (`epochCreated`),
KEY `key_modified` (`epochModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phriction`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phriction_document_ffield` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`fieldKey` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phriction`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phriction_document_fngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_ngram` (`ngram`,`documentID`),
KEY `key_object` (`documentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phriction`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phriction_document_fngrams_common` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`needsCollection` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ngram` (`ngram`),
KEY `key_collect` (`needsCollection`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phriction`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phriction_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phriction`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phriction_transaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_phurl` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_phurl`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phurl`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phurl`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phurl_phurlname_ngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_object` (`objectID`),
KEY `key_ngram` (`ngram`,`objectID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phurl`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phurl_url` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`longURL` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`description` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`spacePHID` varbinary(64) DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`alias` varchar(64) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`mailKey` binary(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_instance` (`alias`),
KEY `key_author` (`authorPHID`),
KEY `key_space` (`spacePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phurl`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phurl_urltransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_phurl`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phurl_urltransaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_policy` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_policy`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `policy` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`rules` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`defaultAction` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_ponder` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_ponder`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_ponder`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_ponder`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `ponder_answer` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`questionID` int(10) unsigned NOT NULL,
`phid` varbinary(64) NOT NULL,
`voteCount` int(10) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`mailKey` binary(20) NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `phid` (`phid`),
UNIQUE KEY `key_oneanswerperquestion` (`questionID`,`authorPHID`),
KEY `questionID` (`questionID`),
KEY `authorPHID` (`authorPHID`),
KEY `status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_ponder`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `ponder_answertransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_ponder`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `ponder_answertransaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_ponder`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `ponder_question` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT},
`answerCount` int(10) unsigned NOT NULL,
`mailKey` binary(20) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`spacePHID` varbinary(64) DEFAULT NULL,
`answerWiki` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `phid` (`phid`),
KEY `authorPHID` (`authorPHID`),
KEY `status` (`status`),
KEY `key_space` (`spacePHID`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_ponder`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `ponder_question_fdocument` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`epochCreated` int(10) unsigned NOT NULL,
`epochModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_owner` (`ownerPHID`),
KEY `key_created` (`epochCreated`),
KEY `key_modified` (`epochModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_ponder`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `ponder_question_ffield` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`fieldKey` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_ponder`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `ponder_question_fngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_ngram` (`ngram`,`documentID`),
KEY `key_object` (`documentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_ponder`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `ponder_question_fngrams_common` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`needsCollection` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ngram` (`ngram`),
KEY `key_collect` (`needsCollection`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_ponder`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `ponder_questiontransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_ponder`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `ponder_questiontransaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_project` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `project` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`joinPolicy` varbinary(64) NOT NULL,
`isMembershipLocked` tinyint(1) NOT NULL DEFAULT '0',
`profileImagePHID` varbinary(64) DEFAULT NULL,
`icon` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`color` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`mailKey` binary(20) NOT NULL,
`primarySlug` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`parentProjectPHID` varbinary(64) DEFAULT NULL,
`hasWorkboard` tinyint(1) NOT NULL,
`hasMilestones` tinyint(1) NOT NULL,
`hasSubprojects` tinyint(1) NOT NULL,
`milestoneNumber` int(10) unsigned DEFAULT NULL,
`projectPath` varbinary(64) NOT NULL,
`projectDepth` int(10) unsigned NOT NULL,
`projectPathKey` binary(4) NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`spacePHID` varbinary(64) DEFAULT NULL,
`subtype` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_pathkey` (`projectPathKey`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_primaryslug` (`primarySlug`),
UNIQUE KEY `key_milestone` (`parentProjectPHID`,`milestoneNumber`),
KEY `key_icon` (`icon`),
KEY `key_color` (`color`),
KEY `key_path` (`projectPath`,`projectDepth`),
KEY `key_space` (`spacePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `project_column` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`status` int(10) unsigned NOT NULL,
`sequence` int(10) unsigned NOT NULL,
`projectPHID` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`proxyPHID` varbinary(64) DEFAULT NULL,
`triggerPHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_proxy` (`projectPHID`,`proxyPHID`),
KEY `key_status` (`projectPHID`,`status`,`sequence`),
KEY `key_sequence` (`projectPHID`,`sequence`),
KEY `key_trigger` (`triggerPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `project_columnposition` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`boardPHID` varbinary(64) NOT NULL,
`columnPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`sequence` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `boardPHID` (`boardPHID`,`columnPHID`,`objectPHID`),
KEY `objectPHID` (`objectPHID`,`boardPHID`),
KEY `boardPHID_2` (`boardPHID`,`columnPHID`,`sequence`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `project_columntransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `project_customfieldnumericindex` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`indexKey` binary(12) NOT NULL,
`indexValue` bigint(20) NOT NULL,
PRIMARY KEY (`id`),
KEY `key_join` (`objectPHID`,`indexKey`,`indexValue`),
KEY `key_find` (`indexKey`,`indexValue`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `project_customfieldstorage` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`fieldIndex` binary(12) NOT NULL,
`fieldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `objectPHID` (`objectPHID`,`fieldIndex`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `project_customfieldstringindex` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`indexKey` binary(12) NOT NULL,
`indexValue` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_join` (`objectPHID`,`indexKey`,`indexValue`(64)),
KEY `key_find` (`indexKey`,`indexValue`(64))
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `project_datasourcetoken` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`projectID` int(10) unsigned NOT NULL,
`token` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `token` (`token`,`projectID`),
KEY `projectID` (`projectID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `project_project_fdocument` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`epochCreated` int(10) unsigned NOT NULL,
`epochModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_owner` (`ownerPHID`),
KEY `key_created` (`epochCreated`),
KEY `key_modified` (`epochModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `project_project_ffield` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`fieldKey` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `project_project_fngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_ngram` (`ngram`,`documentID`),
KEY `key_object` (`documentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `project_project_fngrams_common` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`needsCollection` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ngram` (`ngram`),
KEY `key_collect` (`needsCollection`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `project_slug` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`projectPHID` varbinary(64) NOT NULL,
`slug` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_slug` (`slug`),
KEY `key_projectPHID` (`projectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `project_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `project_trigger` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`ruleset` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `project_triggertransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_project`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `project_triggerusage` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`triggerPHID` varbinary(64) NOT NULL,
`examplePHID` varbinary(64) DEFAULT NULL,
`columnCount` int(10) unsigned NOT NULL,
`activeColumnCount` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_trigger` (`triggerPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
-CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_releeph` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
-
-USE `{$NAMESPACE}_releeph`;
-
- SET NAMES utf8 ;
-
- SET character_set_client = {$CHARSET} ;
-
-CREATE TABLE `releeph_branch` (
- `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
- `dateCreated` int(10) unsigned NOT NULL,
- `dateModified` int(10) unsigned NOT NULL,
- `basename` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `releephProjectID` int(10) unsigned NOT NULL,
- `createdByUserPHID` varbinary(64) NOT NULL,
- `cutPointCommitPHID` varbinary(64) NOT NULL,
- `isActive` tinyint(1) NOT NULL DEFAULT '1',
- `symbolicName` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
- `details` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `phid` varbinary(64) NOT NULL,
- `name` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `releephProjectID_2` (`releephProjectID`,`basename`),
- UNIQUE KEY `releephProjectID_name` (`releephProjectID`,`name`),
- UNIQUE KEY `key_phid` (`phid`),
- UNIQUE KEY `releephProjectID` (`releephProjectID`,`symbolicName`)
-) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
-
-USE `{$NAMESPACE}_releeph`;
-
- SET NAMES utf8 ;
-
- SET character_set_client = {$CHARSET} ;
-
-CREATE TABLE `releeph_branchtransaction` (
- `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
- `phid` varbinary(64) NOT NULL,
- `authorPHID` varbinary(64) NOT NULL,
- `objectPHID` varbinary(64) NOT NULL,
- `viewPolicy` varbinary(64) NOT NULL,
- `editPolicy` varbinary(64) NOT NULL,
- `commentPHID` varbinary(64) DEFAULT NULL,
- `commentVersion` int(10) unsigned NOT NULL,
- `transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `dateCreated` int(10) unsigned NOT NULL,
- `dateModified` int(10) unsigned NOT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `key_phid` (`phid`),
- KEY `key_object` (`objectPHID`)
-) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
-
-USE `{$NAMESPACE}_releeph`;
-
- SET NAMES utf8 ;
-
- SET character_set_client = {$CHARSET} ;
-
-CREATE TABLE `releeph_producttransaction` (
- `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
- `phid` varbinary(64) NOT NULL,
- `authorPHID` varbinary(64) NOT NULL,
- `objectPHID` varbinary(64) NOT NULL,
- `viewPolicy` varbinary(64) NOT NULL,
- `editPolicy` varbinary(64) NOT NULL,
- `commentPHID` varbinary(64) DEFAULT NULL,
- `commentVersion` int(10) unsigned NOT NULL,
- `transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `dateCreated` int(10) unsigned NOT NULL,
- `dateModified` int(10) unsigned NOT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `key_phid` (`phid`),
- KEY `key_object` (`objectPHID`)
-) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
-
-USE `{$NAMESPACE}_releeph`;
-
- SET NAMES utf8 ;
-
- SET character_set_client = {$CHARSET} ;
-
-CREATE TABLE `releeph_project` (
- `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
- `dateCreated` int(10) unsigned NOT NULL,
- `dateModified` int(10) unsigned NOT NULL,
- `phid` varbinary(64) NOT NULL,
- `name` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `trunkBranch` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `repositoryPHID` varbinary(64) NOT NULL,
- `createdByUserPHID` varbinary(64) NOT NULL,
- `isActive` tinyint(1) NOT NULL DEFAULT '1',
- `details` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `projectName` (`name`),
- UNIQUE KEY `key_phid` (`phid`)
-) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
-
-USE `{$NAMESPACE}_releeph`;
-
- SET NAMES utf8 ;
-
- SET character_set_client = {$CHARSET} ;
-
-CREATE TABLE `releeph_request` (
- `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
- `dateCreated` int(10) unsigned NOT NULL,
- `dateModified` int(10) unsigned NOT NULL,
- `phid` varbinary(64) NOT NULL,
- `branchID` int(10) unsigned NOT NULL,
- `requestUserPHID` varbinary(64) NOT NULL,
- `requestCommitPHID` varbinary(64) DEFAULT NULL,
- `commitIdentifier` varchar(40) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
- `commitPHID` varbinary(64) DEFAULT NULL,
- `pickStatus` int(10) unsigned DEFAULT NULL,
- `details` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `userIntents` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT},
- `inBranch` tinyint(1) NOT NULL DEFAULT '0',
- `mailKey` binary(20) NOT NULL,
- `requestedObjectPHID` varbinary(64) NOT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `phid` (`phid`),
- UNIQUE KEY `requestIdentifierBranch` (`requestCommitPHID`,`branchID`),
- KEY `branchID` (`branchID`),
- KEY `key_requestedObject` (`requestedObjectPHID`)
-) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
-
-USE `{$NAMESPACE}_releeph`;
-
- SET NAMES utf8 ;
-
- SET character_set_client = {$CHARSET} ;
-
-CREATE TABLE `releeph_requesttransaction` (
- `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
- `phid` varbinary(64) NOT NULL,
- `authorPHID` varbinary(64) NOT NULL,
- `objectPHID` varbinary(64) NOT NULL,
- `viewPolicy` varbinary(64) NOT NULL,
- `editPolicy` varbinary(64) NOT NULL,
- `commentPHID` varbinary(64) DEFAULT NULL,
- `commentVersion` int(10) unsigned NOT NULL,
- `transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `dateCreated` int(10) unsigned NOT NULL,
- `dateModified` int(10) unsigned NOT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `key_phid` (`phid`),
- KEY `key_object` (`objectPHID`)
-) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
-
-USE `{$NAMESPACE}_releeph`;
-
- SET NAMES utf8 ;
-
- SET character_set_client = {$CHARSET} ;
-
-CREATE TABLE `releeph_requesttransaction_comment` (
- `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
- `phid` varbinary(64) NOT NULL,
- `transactionPHID` varbinary(64) DEFAULT NULL,
- `authorPHID` varbinary(64) NOT NULL,
- `viewPolicy` varbinary(64) NOT NULL,
- `editPolicy` varbinary(64) NOT NULL,
- `commentVersion` int(10) unsigned NOT NULL,
- `content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
- `isDeleted` tinyint(1) NOT NULL,
- `dateCreated` int(10) unsigned NOT NULL,
- `dateModified` int(10) unsigned NOT NULL,
- PRIMARY KEY (`id`),
- UNIQUE KEY `key_phid` (`phid`),
- UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
-) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
-
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_repository` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`callsign` varchar(32) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} DEFAULT NULL,
`versionControlSystem` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`details` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`uuid` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`pushPolicy` varbinary(64) NOT NULL,
`credentialPHID` varbinary(64) DEFAULT NULL,
`almanacServicePHID` varbinary(64) DEFAULT NULL,
`spacePHID` varbinary(64) DEFAULT NULL,
`repositorySlug` varchar(64) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} DEFAULT NULL,
`localPath` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`profileImagePHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `callsign` (`callsign`),
UNIQUE KEY `key_slug` (`repositorySlug`),
UNIQUE KEY `key_local` (`localPath`),
KEY `key_vcs` (`versionControlSystem`),
KEY `key_name` (`name`(128)),
KEY `key_space` (`spacePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_auditrequest` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`auditorPHID` varbinary(64) NOT NULL,
`commitPHID` varbinary(64) NOT NULL,
`auditStatus` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`auditReasons` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_unique` (`commitPHID`,`auditorPHID`),
KEY `commitPHID` (`commitPHID`),
KEY `auditorPHID` (`auditorPHID`,`auditStatus`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_branch` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`repositoryID` int(10) unsigned NOT NULL,
`name` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`lintCommit` varchar(40) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `repositoryID` (`repositoryID`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_commit` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`repositoryID` int(10) unsigned NOT NULL,
`phid` varbinary(64) NOT NULL,
`commitIdentifier` varchar(40) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`epoch` int(10) unsigned NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`auditStatus` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`summary` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`importStatus` int(10) unsigned NOT NULL,
`authorIdentityPHID` varbinary(64) DEFAULT NULL,
`committerIdentityPHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `phid` (`phid`),
UNIQUE KEY `key_commit_identity` (`commitIdentifier`,`repositoryID`),
KEY `repositoryID_2` (`repositoryID`,`epoch`),
KEY `authorPHID` (`authorPHID`,`auditStatus`,`epoch`),
KEY `repositoryID` (`repositoryID`,`importStatus`),
KEY `key_epoch` (`epoch`),
KEY `key_author` (`authorPHID`,`epoch`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_commit_fdocument` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`epochCreated` int(10) unsigned NOT NULL,
`epochModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_owner` (`ownerPHID`),
KEY `key_created` (`epochCreated`),
KEY `key_modified` (`epochModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_commit_ffield` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`fieldKey` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_commit_fngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_ngram` (`ngram`,`documentID`),
KEY `key_object` (`documentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_commit_fngrams_common` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`needsCollection` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ngram` (`ngram`),
KEY `key_collect` (`needsCollection`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_commitdata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`commitID` int(10) unsigned NOT NULL,
`authorName` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`commitMessage` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`commitDetails` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `commitID` (`commitID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_commithint` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`repositoryPHID` varbinary(64) NOT NULL,
`oldCommitIdentifier` varchar(40) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newCommitIdentifier` varchar(40) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`hintType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_old` (`repositoryPHID`,`oldCommitIdentifier`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_coverage` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`branchID` int(10) unsigned NOT NULL,
`commitID` int(10) unsigned NOT NULL,
`pathID` int(10) unsigned NOT NULL,
`coverage` longblob NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_path` (`branchID`,`pathID`,`commitID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_filesystem` (
`repositoryID` int(10) unsigned NOT NULL,
`parentID` int(10) unsigned NOT NULL,
`svnCommit` int(10) unsigned NOT NULL,
`pathID` int(10) unsigned NOT NULL,
`existed` tinyint(1) NOT NULL,
`fileType` int(10) unsigned NOT NULL,
PRIMARY KEY (`repositoryID`,`parentID`,`pathID`,`svnCommit`),
KEY `repositoryID` (`repositoryID`,`svnCommit`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_gitlfsref` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`repositoryPHID` varbinary(64) NOT NULL,
`objectHash` binary(64) NOT NULL,
`byteSize` bigint(20) unsigned NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`filePHID` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_hash` (`repositoryPHID`,`objectHash`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_identity` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`automaticGuessedUserPHID` varbinary(64) DEFAULT NULL,
`manuallySetUserPHID` varbinary(64) DEFAULT NULL,
`currentEffectiveUserPHID` varbinary(64) DEFAULT NULL,
`identityNameHash` binary(12) NOT NULL,
`identityNameRaw` longblob NOT NULL,
`identityNameEncoding` varchar(16) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_identity` (`identityNameHash`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_identitytransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_lintmessage` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`branchID` int(10) unsigned NOT NULL,
`path` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`line` int(10) unsigned NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`code` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`severity` varchar(16) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`description` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `branchID` (`branchID`,`path`(64)),
KEY `branchID_2` (`branchID`,`code`,`path`(64)),
KEY `key_author` (`authorPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_mirror` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`repositoryPHID` varbinary(64) NOT NULL,
`remoteURI` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`credentialPHID` varbinary(64) DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_repository` (`repositoryPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_oldref` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`repositoryPHID` varbinary(64) NOT NULL,
`commitIdentifier` varchar(40) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_repository` (`repositoryPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_parents` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`childCommitID` int(10) unsigned NOT NULL,
`parentCommitID` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_child` (`childCommitID`,`parentCommitID`),
KEY `key_parent` (`parentCommitID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_path` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`path` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`pathHash` binary(32) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `pathHash` (`pathHash`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_pathchange` (
`repositoryID` int(10) unsigned NOT NULL,
`pathID` int(10) unsigned NOT NULL,
`commitID` int(10) unsigned NOT NULL,
`targetPathID` int(10) unsigned DEFAULT NULL,
`targetCommitID` int(10) unsigned DEFAULT NULL,
`changeType` int(10) unsigned NOT NULL,
`fileType` int(10) unsigned NOT NULL,
`isDirect` tinyint(1) NOT NULL,
`commitSequence` int(10) unsigned NOT NULL,
PRIMARY KEY (`commitID`,`pathID`),
KEY `repositoryID` (`repositoryID`,`pathID`,`commitSequence`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_pullevent` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`repositoryPHID` varbinary(64) DEFAULT NULL,
`epoch` int(10) unsigned NOT NULL,
`pullerPHID` varbinary(64) DEFAULT NULL,
`remoteAddress` varbinary(64) DEFAULT NULL,
`remoteProtocol` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`resultType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`resultCode` int(10) unsigned NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_repository` (`repositoryPHID`),
KEY `key_epoch` (`epoch`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_pushevent` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`repositoryPHID` varbinary(64) NOT NULL,
`epoch` int(10) unsigned NOT NULL,
`pusherPHID` varbinary(64) NOT NULL,
`remoteAddress` varbinary(64) DEFAULT NULL,
`remoteProtocol` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`rejectCode` int(10) unsigned NOT NULL,
`rejectDetails` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`requestIdentifier` binary(12) DEFAULT NULL,
`writeWait` bigint(20) unsigned DEFAULT NULL,
`readWait` bigint(20) unsigned DEFAULT NULL,
`hostWait` bigint(20) unsigned DEFAULT NULL,
`hookWait` bigint(20) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_repository` (`repositoryPHID`),
KEY `key_identifier` (`requestIdentifier`),
KEY `key_reject` (`rejectCode`,`rejectDetails`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_pushlog` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`epoch` int(10) unsigned NOT NULL,
`pushEventPHID` varbinary(64) NOT NULL,
`repositoryPHID` varbinary(64) NOT NULL,
`pusherPHID` varbinary(64) NOT NULL,
`refType` varchar(12) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`refNameHash` binary(12) DEFAULT NULL,
`refNameRaw` longblob,
`refNameEncoding` varchar(16) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`refOld` varchar(40) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`refNew` varchar(40) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`mergeBase` varchar(40) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`changeFlags` int(10) unsigned NOT NULL,
`devicePHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_repository` (`repositoryPHID`),
KEY `key_ref` (`repositoryPHID`,`refNew`),
KEY `key_pusher` (`pusherPHID`),
KEY `key_name` (`repositoryPHID`,`refNameHash`),
KEY `key_event` (`pushEventPHID`),
KEY `key_epoch` (`epoch`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_refcursor` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`repositoryPHID` varbinary(64) NOT NULL,
`refType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`refNameHash` binary(12) NOT NULL,
`refNameRaw` longblob NOT NULL,
`refNameEncoding` varchar(16) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ref` (`repositoryPHID`,`refType`,`refNameHash`),
UNIQUE KEY `key_phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_refposition` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`cursorID` int(10) unsigned NOT NULL,
`commitIdentifier` varchar(40) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isClosed` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_position` (`cursorID`,`commitIdentifier`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_repository_fdocument` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`epochCreated` int(10) unsigned NOT NULL,
`epochModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_owner` (`ownerPHID`),
KEY `key_created` (`epochCreated`),
KEY `key_modified` (`epochModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_repository_ffield` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`fieldKey` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_repository_fngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_ngram` (`ngram`,`documentID`),
KEY `key_object` (`documentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_repository_fngrams_common` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`needsCollection` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ngram` (`ngram`),
KEY `key_collect` (`needsCollection`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_statusmessage` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`repositoryID` int(10) unsigned NOT NULL,
`statusType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`statusCode` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`parameters` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`epoch` int(10) unsigned NOT NULL,
`messageCount` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `repositoryID` (`repositoryID`,`statusType`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_summary` (
`repositoryID` int(10) unsigned NOT NULL,
`size` int(10) unsigned NOT NULL,
`lastCommitID` int(10) unsigned NOT NULL,
`epoch` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`repositoryID`),
KEY `key_epoch` (`epoch`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_symbol` (
`repositoryPHID` varbinary(64) NOT NULL,
`symbolContext` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`symbolName` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`symbolType` varchar(12) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`symbolLanguage` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`pathID` int(10) unsigned NOT NULL,
`lineNumber` int(10) unsigned NOT NULL,
KEY `symbolName` (`symbolName`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_syncevent` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`repositoryPHID` varbinary(64) NOT NULL,
`epoch` int(10) unsigned NOT NULL,
`devicePHID` varbinary(64) NOT NULL,
`fromDevicePHID` varbinary(64) NOT NULL,
`deviceVersion` int(10) unsigned DEFAULT NULL,
`fromDeviceVersion` int(10) unsigned DEFAULT NULL,
`resultType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`resultCode` int(10) unsigned NOT NULL,
`syncWait` bigint(20) unsigned NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_repository` (`repositoryPHID`),
KEY `key_epoch` (`epoch`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_uri` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`repositoryPHID` varbinary(64) NOT NULL,
`uri` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`builtinProtocol` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`builtinIdentifier` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`ioType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`displayType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDisabled` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`credentialPHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_builtin` (`repositoryPHID`,`builtinProtocol`,`builtinIdentifier`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_uriindex` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`repositoryPHID` varbinary(64) NOT NULL,
`repositoryURI` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_repository` (`repositoryPHID`),
KEY `key_uri` (`repositoryURI`(128))
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_uritransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_repository`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `repository_workingcopyversion` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`repositoryPHID` varbinary(64) NOT NULL,
`devicePHID` varbinary(64) NOT NULL,
`repositoryVersion` int(10) unsigned NOT NULL,
`isWriting` tinyint(1) NOT NULL,
`writeProperties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT},
`lockOwner` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_workingcopy` (`repositoryPHID`,`devicePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_search` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_search`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_search`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_search`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `search_document` (
`phid` varbinary(64) NOT NULL,
`documentType` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`documentTitle` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`documentCreated` int(10) unsigned NOT NULL,
`documentModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`phid`),
KEY `documentCreated` (`documentCreated`),
KEY `key_type` (`documentType`,`documentCreated`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_search`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `search_documentfield` (
`phid` varbinary(64) NOT NULL,
`phidType` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`field` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`auxPHID` varbinary(64) DEFAULT NULL,
`corpus` longtext CHARACTER SET {$CHARSET_FULLTEXT} COLLATE {$COLLATE_FULLTEXT},
`stemmedCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT},
KEY `phid` (`phid`),
FULLTEXT KEY `key_corpus` (`corpus`,`stemmedCorpus`)
) ENGINE=MyISAM DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_search`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `search_documentrelationship` (
`phid` varbinary(64) NOT NULL,
`relatedPHID` varbinary(64) NOT NULL,
`relation` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`relatedType` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`relatedTime` int(10) unsigned NOT NULL,
KEY `phid` (`phid`),
KEY `relatedPHID` (`relatedPHID`,`relation`),
KEY `relation` (`relation`,`relatedPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_search`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `search_editengineconfiguration` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`engineKey` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`builtinKey` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDisabled` tinyint(1) NOT NULL DEFAULT '0',
`isDefault` tinyint(1) NOT NULL DEFAULT '0',
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`isEdit` tinyint(1) NOT NULL,
`createOrder` int(10) unsigned NOT NULL,
`editOrder` int(10) unsigned NOT NULL,
`subtype` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_engine` (`engineKey`,`builtinKey`),
KEY `key_default` (`engineKey`,`isDefault`,`isDisabled`),
KEY `key_edit` (`engineKey`,`isEdit`,`isDisabled`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_search`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `search_editengineconfigurationtransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_search`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `search_indexversion` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`extensionKey` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`version` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`,`extensionKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_search`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `search_namedquery` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`userPHID` varbinary(64) NOT NULL,
`engineClassName` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`queryName` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`queryKey` varchar(12) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`isBuiltin` tinyint(1) NOT NULL DEFAULT '0',
`isDisabled` tinyint(1) NOT NULL DEFAULT '0',
`sequence` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `key_userquery` (`userPHID`,`engineClassName`,`queryKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_search`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `search_namedqueryconfig` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`engineClassName` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`scopePHID` varbinary(64) NOT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_scope` (`engineClassName`,`scopePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_search`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `search_profilepanelconfiguration` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`profilePHID` varbinary(64) NOT NULL,
`menuItemKey` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`builtinKey` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`menuItemOrder` int(10) unsigned DEFAULT NULL,
`visibility` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`menuItemProperties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`customPHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_profile` (`profilePHID`,`menuItemOrder`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_search`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `search_profilepanelconfigurationtransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_search`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `search_savedquery` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`engineClassName` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`parameters` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`queryKey` varchar(12) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_queryKey` (`queryKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_search`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `stopwords` (
`value` varchar(32) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
INSERT INTO `stopwords` VALUES ('the'),('be'),('and'),('of'),('a'),('in'),('to'),('have'),('it'),('I'),('that'),('for'),('you'),('he'),('with'),('on'),('do'),('say'),('this'),('they'),('at'),('but'),('we'),('his'),('from'),('not'),('by'),('or'),('as'),('what'),('go'),('their'),('can'),('who'),('get'),('if'),('would'),('all'),('my'),('will'),('up'),('there'),('so'),('its'),('us');
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_slowvote` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_slowvote`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_slowvote`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_slowvote`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `slowvote_choice` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`pollID` int(10) unsigned NOT NULL,
`optionID` int(10) unsigned NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `pollID` (`pollID`),
KEY `authorPHID` (`authorPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_slowvote`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `slowvote_option` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`pollID` int(10) unsigned NOT NULL,
`name` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `pollID` (`pollID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_slowvote`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `slowvote_poll` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`question` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`responseVisibility` int(10) unsigned NOT NULL,
`shuffle` tinyint(1) NOT NULL DEFAULT '0',
`method` int(10) unsigned NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`description` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`spacePHID` varbinary(64) DEFAULT NULL,
`mailKey` binary(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `phid` (`phid`),
KEY `key_space` (`spacePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_slowvote`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `slowvote_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_slowvote`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `slowvote_transaction_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`transactionPHID` varbinary(64) DEFAULT NULL,
`authorPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`content` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isDeleted` tinyint(1) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_spaces` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_spaces`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_spaces`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_spaces`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `spaces_namespace` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`namespaceName` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`isDefaultNamespace` tinyint(1) DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`description` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isArchived` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_default` (`isDefaultNamespace`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_spaces`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `spaces_namespacetransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_system` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_system`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `system_actionlog` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`actorHash` binary(12) NOT NULL,
`actorIdentity` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`action` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`score` double NOT NULL,
`epoch` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `key_epoch` (`epoch`),
KEY `key_action` (`actorHash`,`action`,`epoch`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_system`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `system_destructionlog` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectClass` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rootLogID` int(10) unsigned DEFAULT NULL,
`objectPHID` varbinary(64) DEFAULT NULL,
`objectMonogram` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`epoch` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `key_epoch` (`epoch`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_token` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_token`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `token_count` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`tokenCount` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_objectPHID` (`objectPHID`),
KEY `key_count` (`tokenCount`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_token`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `token_given` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`tokenPHID` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_all` (`objectPHID`,`authorPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_token` (`tokenPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_token`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `token_token` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`name` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`flavor` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`builtinKey` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`creatorPHID` varbinary(64) NOT NULL,
`tokenImagePHID` varbinary(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_builtin` (`builtinKey`),
KEY `key_creator` (`creatorPHID`,`dateModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_user` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `phabricator_session` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`userPHID` varbinary(64) NOT NULL,
`type` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`sessionKey` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`sessionStart` int(10) unsigned NOT NULL,
`sessionExpires` int(10) unsigned NOT NULL,
`highSecurityUntil` int(10) unsigned DEFAULT NULL,
`isPartial` tinyint(1) NOT NULL DEFAULT '0',
`signedLegalpadDocuments` tinyint(1) NOT NULL DEFAULT '0',
`phid` varbinary(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `sessionKey` (`sessionKey`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_identity` (`userPHID`,`type`),
KEY `key_expires` (`sessionExpires`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`userName` varchar(64) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`realName` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`profileImagePHID` varbinary(64) DEFAULT NULL,
`conduitCertificate` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`isSystemAgent` tinyint(1) NOT NULL DEFAULT '0',
`isDisabled` tinyint(1) NOT NULL,
`isAdmin` tinyint(1) NOT NULL,
`isEmailVerified` int(10) unsigned NOT NULL,
`isApproved` int(10) unsigned NOT NULL,
`accountSecret` binary(64) NOT NULL,
`isEnrolledInMultiFactor` tinyint(1) NOT NULL DEFAULT '0',
`availabilityCache` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`availabilityCacheTTL` int(10) unsigned DEFAULT NULL,
`isMailingList` tinyint(1) NOT NULL,
`defaultProfileImagePHID` varbinary(64) DEFAULT NULL,
`defaultProfileImageVersion` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `userName` (`userName`),
UNIQUE KEY `phid` (`phid`),
KEY `realName` (`realName`),
KEY `key_approved` (`isApproved`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `user_authinvite` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`authorPHID` varbinary(64) NOT NULL,
`emailAddress` varchar(128) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`verificationHash` binary(12) NOT NULL,
`acceptedByPHID` varbinary(64) DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`phid` varbinary(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_address` (`emailAddress`),
UNIQUE KEY `key_code` (`verificationHash`),
UNIQUE KEY `key_phid` (`phid`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `user_cache` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`userPHID` varbinary(64) NOT NULL,
`cacheIndex` binary(12) NOT NULL,
`cacheKey` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`cacheData` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`cacheType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_usercache` (`userPHID`,`cacheIndex`),
KEY `key_cachekey` (`cacheIndex`),
KEY `key_cachetype` (`cacheType`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `user_configuredcustomfieldstorage` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`fieldIndex` binary(12) NOT NULL,
`fieldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `objectPHID` (`objectPHID`,`fieldIndex`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `user_customfieldnumericindex` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`indexKey` binary(12) NOT NULL,
`indexValue` bigint(20) NOT NULL,
PRIMARY KEY (`id`),
KEY `key_join` (`objectPHID`,`indexKey`,`indexValue`),
KEY `key_find` (`indexKey`,`indexValue`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `user_customfieldstringindex` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`indexKey` binary(12) NOT NULL,
`indexValue` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_join` (`objectPHID`,`indexKey`,`indexValue`(64)),
KEY `key_find` (`indexKey`,`indexValue`(64))
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `user_email` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`userPHID` varbinary(64) NOT NULL,
`address` varchar(128) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`isVerified` tinyint(1) NOT NULL DEFAULT '0',
`isPrimary` tinyint(1) NOT NULL DEFAULT '0',
`verificationCode` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `address` (`address`),
KEY `userPHID` (`userPHID`,`isPrimary`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `user_externalaccount` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`userPHID` varbinary(64) DEFAULT NULL,
`accountType` varchar(16) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`accountDomain` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`accountSecret` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT},
`accountID` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`displayName` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`username` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`realName` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`email` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`emailVerified` tinyint(1) NOT NULL,
`accountURI` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`profileImagePHID` varbinary(64) DEFAULT NULL,
`properties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`providerConfigPHID` varbinary(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `account_details` (`accountType`,`accountDomain`,`accountID`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_user` (`userPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `user_log` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`actorPHID` varbinary(64) DEFAULT NULL,
`userPHID` varbinary(64) NOT NULL,
`action` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`details` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`remoteAddr` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`session` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `actorPHID` (`actorPHID`,`dateCreated`),
KEY `userPHID` (`userPHID`,`dateCreated`),
KEY `action` (`action`,`dateCreated`),
KEY `dateCreated` (`dateCreated`),
KEY `remoteAddr` (`remoteAddr`,`dateCreated`),
KEY `session` (`session`,`dateCreated`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `user_nametoken` (
`token` varchar(255) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`userID` int(10) unsigned NOT NULL,
KEY `token` (`token`(128)),
KEY `userID` (`userID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `user_preferences` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`userPHID` varbinary(64) DEFAULT NULL,
`preferences` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`phid` varbinary(64) NOT NULL,
`builtinKey` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_builtin` (`builtinKey`),
UNIQUE KEY `key_user` (`userPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `user_preferencestransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `user_profile` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`userPHID` varbinary(64) NOT NULL,
`title` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`blurb` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`profileImagePHID` varbinary(64) DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`icon` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `userPHID` (`userPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `user_transaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `user_user_fdocument` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`objectPHID` varbinary(64) NOT NULL,
`isClosed` tinyint(1) NOT NULL,
`authorPHID` varbinary(64) DEFAULT NULL,
`ownerPHID` varbinary(64) DEFAULT NULL,
`epochCreated` int(10) unsigned NOT NULL,
`epochModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_object` (`objectPHID`),
KEY `key_author` (`authorPHID`),
KEY `key_owner` (`ownerPHID`),
KEY `key_created` (`epochCreated`),
KEY `key_modified` (`epochModified`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `user_user_ffield` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`fieldKey` varchar(4) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
`normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `user_user_fngrams` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`documentID` int(10) unsigned NOT NULL,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_ngram` (`ngram`,`documentID`),
KEY `key_object` (`documentID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_user`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `user_user_fngrams_common` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ngram` char(3) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`needsCollection` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_ngram` (`ngram`),
KEY `key_collect` (`needsCollection`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_worker` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_worker`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edge` (
`src` varbinary(64) NOT NULL,
`type` int(10) unsigned NOT NULL,
`dst` varbinary(64) NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`seq` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`src`,`type`,`dst`),
UNIQUE KEY `key_dst` (`dst`,`type`,`src`),
KEY `src` (`src`,`type`,`dateCreated`,`seq`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_worker`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `edgedata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_worker`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `lisk_counter` (
`counterName` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`counterValue` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`counterName`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
INSERT INTO `lisk_counter` VALUES ('worker_activetask',5);
USE `{$NAMESPACE}_worker`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `worker_activetask` (
`id` int(10) unsigned NOT NULL,
`taskClass` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`leaseOwner` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`leaseExpires` int(10) unsigned DEFAULT NULL,
`failureCount` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned NOT NULL,
`failureTime` int(10) unsigned DEFAULT NULL,
`priority` int(10) unsigned NOT NULL,
`objectPHID` varbinary(64) DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `leaseExpires` (`leaseExpires`),
KEY `key_failuretime` (`failureTime`),
KEY `taskClass` (`taskClass`),
KEY `key_owner` (`leaseOwner`,`priority`,`id`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
INSERT INTO `worker_activetask` VALUES (3,'PhabricatorRebuildIndexesWorker',NULL,NULL,0,1,NULL,3500,NULL,1556231702,1556231702),(4,'PhabricatorRebuildIndexesWorker',NULL,NULL,0,2,NULL,3500,NULL,1556231702,1556231702),(5,'PhabricatorRebuildIndexesWorker',NULL,NULL,0,3,NULL,3500,NULL,1556231702,1556231702);
USE `{$NAMESPACE}_worker`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `worker_archivetask` (
`id` int(10) unsigned NOT NULL,
`taskClass` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`leaseOwner` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`leaseExpires` int(10) unsigned DEFAULT NULL,
`failureCount` int(10) unsigned NOT NULL,
`dataID` int(10) unsigned NOT NULL,
`result` int(10) unsigned NOT NULL,
`duration` bigint(20) unsigned NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`priority` int(10) unsigned NOT NULL,
`objectPHID` varbinary(64) DEFAULT NULL,
`archivedEpoch` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `dateCreated` (`dateCreated`),
KEY `key_modified` (`dateModified`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_worker`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `worker_bulkjob` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`jobTypeKey` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`parameters` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`size` int(10) unsigned NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
`isSilent` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_type` (`jobTypeKey`),
KEY `key_author` (`authorPHID`),
KEY `key_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_worker`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `worker_bulkjobtransaction` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`authorPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`viewPolicy` varbinary(64) NOT NULL,
`editPolicy` varbinary(64) NOT NULL,
`commentPHID` varbinary(64) DEFAULT NULL,
`commentVersion` int(10) unsigned NOT NULL,
`transactionType` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`oldValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`newValue` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`contentSource` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`metadata` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_worker`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `worker_bulktask` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`bulkJobPHID` varbinary(64) NOT NULL,
`objectPHID` varbinary(64) NOT NULL,
`status` varchar(32) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
KEY `key_job` (`bulkJobPHID`,`status`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_worker`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `worker_taskdata` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
INSERT INTO `worker_taskdata` VALUES (1,'{\"queryClass\":\"PhabricatorDashboardQuery\"}'),(2,'{\"queryClass\":\"PhabricatorDashboardPanelQuery\"}'),(3,'{\"queryClass\":\"HeraldRuleQuery\"}');
USE `{$NAMESPACE}_worker`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `worker_trigger` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`phid` varbinary(64) NOT NULL,
`triggerVersion` int(10) unsigned NOT NULL,
`clockClass` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`clockProperties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`actionClass` varchar(64) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`actionProperties` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_phid` (`phid`),
UNIQUE KEY `key_trigger` (`triggerVersion`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
USE `{$NAMESPACE}_worker`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `worker_triggerevent` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`triggerID` int(10) unsigned NOT NULL,
`lastEventEpoch` int(10) unsigned DEFAULT NULL,
`nextEventEpoch` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key_trigger` (`triggerID`),
KEY `key_next` (`nextEventEpoch`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_xhpast` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_xhpast`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `xhpast_parsetree` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`authorPHID` varbinary(64) DEFAULT NULL,
`input` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`returnCode` int(10) NOT NULL,
`stdout` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`stderr` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_xhprof` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */;
USE `{$NAMESPACE}_xhprof`;
SET NAMES utf8 ;
SET character_set_client = {$CHARSET} ;
CREATE TABLE `xhprof_sample` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`filePHID` varbinary(64) NOT NULL,
`sampleRate` int(10) unsigned NOT NULL,
`usTotal` bigint(20) unsigned NOT NULL,
`hostname` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`requestPath` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`controller` varchar(255) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} DEFAULT NULL,
`userPHID` varbinary(64) DEFAULT NULL,
`dateCreated` int(10) unsigned NOT NULL,
`dateModified` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `filePHID` (`filePHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
diff --git a/scripts/repository/commit_hook.php b/scripts/repository/commit_hook.php
index df49aa7b00..ccbfde08ff 100755
--- a/scripts/repository/commit_hook.php
+++ b/scripts/repository/commit_hook.php
@@ -1,237 +1,237 @@
#!/usr/bin/env php
<?php
// NOTE: This script will sometimes emit a warning like this on startup:
//
// No entry for terminal type "unknown";
// using dumb terminal settings.
//
// This can be fixed by adding "TERM=dumb" to the shebang line, but doing so
// causes some systems to hang mysteriously. See T7119.
// Commit hooks execute in an unusual context where the environment may be
// unavailable, particularly in SVN. The first parameter to this script is
// either a bare repository identifier ("X"), or a repository identifier
// followed by an instance identifier ("X:instance"). If we have an instance
// identifier, unpack it into the environment before we start up. This allows
// subclasses of PhabricatorConfigSiteSource to read it and build an instance
// environment.
$hook_start = microtime(true);
if ($argc > 1) {
$context = $argv[1];
$context = explode(':', $context, 2);
$argv[1] = $context[0];
if (count($context) > 1) {
$_ENV['PHABRICATOR_INSTANCE'] = $context[1];
putenv('PHABRICATOR_INSTANCE='.$context[1]);
}
}
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
if ($argc < 2) {
throw new Exception(pht('usage: commit-hook <repository>'));
}
$engine = id(new DiffusionCommitHookEngine())
->setStartTime($hook_start);
$repository = id(new PhabricatorRepositoryQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withIdentifiers(array($argv[1]))
->needProjectPHIDs(true)
->executeOne();
if (!$repository) {
throw new Exception(pht('No such repository "%s"!', $argv[1]));
}
if (!$repository->isHosted()) {
// In Mercurial, the "pretxnchangegroup" hook fires for both pulls and
// pushes. Normally we only install the hook for hosted repositories, but
// if a hosted repository is later converted into an observed repository we
// can end up with an observed repository that has the hook installed.
// If we're running hooks from an observed repository, just exit without
// taking action. For more discussion, see PHI24.
return 0;
}
$engine->setRepository($repository);
$args = new PhutilArgumentParser($argv);
$args->parsePartial(
array(
array(
'name' => 'hook-mode',
'param' => 'mode',
'help' => pht('Hook execution mode.'),
),
));
$argv = array_merge(
array($argv[0]),
$args->getUnconsumedArgumentVector());
// Figure out which user is writing the commit.
$hook_mode = $args->getArg('hook-mode');
if ($hook_mode !== null) {
$known_modes = array(
'svn-revprop' => true,
);
if (empty($known_modes[$hook_mode])) {
throw new Exception(
pht(
'Invalid Hook Mode: This hook was invoked in "%s" mode, but this '.
'is not a recognized hook mode. Valid modes are: %s.',
$hook_mode,
implode(', ', array_keys($known_modes))));
}
}
$is_svnrevprop = ($hook_mode == 'svn-revprop');
if ($is_svnrevprop) {
// For now, we let these through if the repository allows dangerous changes
// and prevent them if it doesn't. See T11208 for discussion.
$revprop_key = $argv[5];
if ($repository->shouldAllowDangerousChanges()) {
$err = 0;
} else {
$err = 1;
$console = PhutilConsole::getConsole();
$console->writeErr(
pht(
"DANGEROUS CHANGE: Dangerous change protection is enabled for this ".
"repository, so you can not change revision properties (you are ".
"attempting to edit \"%s\").\n".
"Edit the repository configuration before making dangerous changes.",
$revprop_key));
}
exit($err);
} else if ($repository->isGit() || $repository->isHg()) {
$username = getenv(DiffusionCommitHookEngine::ENV_USER);
if (!strlen($username)) {
throw new Exception(
pht(
- 'No Direct Pushes: You are pushing directly to a repository hosted '.
- 'by Phabricator. This will not work. See "No Direct Pushes" in the '.
- 'documentation for more information.'));
+ 'No Direct Pushes: You are pushing directly to a hosted repository. '.
+ 'This will not work. See "No Direct Pushes" in the documentation '.
+ 'for more information.'));
}
if ($repository->isHg()) {
// We respond to several different hooks in Mercurial.
$engine->setMercurialHook($argv[2]);
}
} else if ($repository->isSVN()) {
// NOTE: In Subversion, the entire environment gets wiped so we can't read
// DiffusionCommitHookEngine::ENV_USER. Instead, we've set "--tunnel-user" to
// specify the correct user; read this user out of the commit log.
if ($argc < 4) {
throw new Exception(pht('usage: commit-hook <repository> <repo> <txn>'));
}
$svn_repo = $argv[2];
$svn_txn = $argv[3];
list($username) = execx('svnlook author -t %s %s', $svn_txn, $svn_repo);
$username = rtrim($username, "\n");
$engine->setSubversionTransactionInfo($svn_txn, $svn_repo);
} else {
throw new Exception(pht('Unknown repository type.'));
}
$user = id(new PhabricatorPeopleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withUsernames(array($username))
->executeOne();
if (!$user) {
throw new Exception(pht('No such user "%s"!', $username));
}
$engine->setViewer($user);
// Read stdin for the hook engine.
if ($repository->isHg()) {
// Mercurial leaves stdin open, so we can't just read it until EOF.
$stdin = '';
} else {
// Git and Subversion write data into stdin and then close it. Read the
// data.
$stdin = @file_get_contents('php://stdin');
if ($stdin === false) {
throw new Exception(pht('Failed to read stdin!'));
}
}
$engine->setStdin($stdin);
$engine->setOriginalArgv(array_slice($argv, 2));
$remote_address = getenv(DiffusionCommitHookEngine::ENV_REMOTE_ADDRESS);
if (strlen($remote_address)) {
$engine->setRemoteAddress($remote_address);
}
$remote_protocol = getenv(DiffusionCommitHookEngine::ENV_REMOTE_PROTOCOL);
if (strlen($remote_protocol)) {
$engine->setRemoteProtocol($remote_protocol);
}
$request_identifier = getenv(DiffusionCommitHookEngine::ENV_REQUEST);
if (strlen($request_identifier)) {
$engine->setRequestIdentifier($request_identifier);
}
try {
$err = $engine->execute();
} catch (DiffusionCommitHookRejectException $ex) {
$console = PhutilConsole::getConsole();
if (PhabricatorEnv::getEnvConfig('phabricator.serious-business')) {
$preamble = pht('*** PUSH REJECTED BY COMMIT HOOK ***');
} else {
$preamble = pht(<<<EOTXT
+---------------------------------------------------------------+
| * * * PUSH REJECTED BY EVIL DRAGON BUREAUCRATS * * * |
+---------------------------------------------------------------+
\
\ ^ /^
\ / \ // \
\ |\___/| / \// .\
\ /V V \__ / // | \ \ *----*
/ / \/_/ // | \ \ \ |
@___@` \/_ // | \ \ \/\ \
0/0/| \/_ // | \ \ \ \
0/0/0/0/| \/// | \ \ | |
0/0/0/0/0/_|_ / ( // | \ _\ | /
0/0/0/0/0/0/`/,_ _ _/ ) ; -. | _ _\.-~ / /
,-} _ *-.|.-~-. .~ ~
* \__/ `/\ / ~-. _ .-~ /
\____(Oo) *. } { /
( (..) .----~-.\ \-` .~
//___\\\\ \ DENIED! ///.----..< \ _ -~
// \\\\ ///-._ _ _ _ _ _ _{^ - - - - ~
EOTXT
);
}
$console->writeErr("%s\n\n", $preamble);
$console->writeErr("%s\n\n", $ex->getMessage());
$err = 1;
}
exit($err);
diff --git a/scripts/sql/manage_storage.php b/scripts/sql/manage_storage.php
index 20e006f348..c3de483d72 100755
--- a/scripts/sql/manage_storage.php
+++ b/scripts/sql/manage_storage.php
@@ -1,249 +1,249 @@
#!/usr/bin/env php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/init/init-setup.php';
$args = new PhutilArgumentParser($argv);
-$args->setTagline(pht('manage Phabricator storage and schemata'));
+$args->setTagline(pht('manage storage and schemata'));
$args->setSynopsis(<<<EOHELP
**storage** __workflow__ [__options__]
-Manage Phabricator database storage and schema versioning.
+Manage database storage and schema versioning.
**storage** upgrade
-Initialize or upgrade Phabricator storage.
+Initialize or upgrade storage.
**storage** upgrade --user __root__ --password __hunter2__
Use administrative credentials for schema changes.
EOHELP
);
$args->parseStandardArguments();
$default_namespace = PhabricatorLiskDAO::getDefaultStorageNamespace();
try {
$args->parsePartial(
array(
array(
'name' => 'force',
'short' => 'f',
'help' => pht(
'Do not prompt before performing dangerous operations.'),
),
array(
'name' => 'host',
'param' => 'hostname',
'help' => pht(
'Operate on the database server identified by __hostname__.'),
),
array(
'name' => 'ref',
'param' => 'ref',
'help' => pht(
'Operate on the database identified by __ref__.'),
),
array(
'name' => 'user',
'short' => 'u',
'param' => 'username',
'help' => pht(
'Connect with __username__ instead of the configured default.'),
),
array(
'name' => 'password',
'short' => 'p',
'param' => 'password',
'help' => pht('Use __password__ instead of the configured default.'),
),
array(
'name' => 'namespace',
'param' => 'name',
'default' => $default_namespace,
'help' => pht(
"Use namespace __namespace__ instead of the configured ".
"default ('%s'). This is an advanced feature used by unit tests; ".
"you should not normally use this flag.",
$default_namespace),
),
array(
'name' => 'dryrun',
'help' => pht(
'Do not actually change anything, just show what would be changed.'),
),
array(
'name' => 'disable-utf8mb4',
'help' => pht(
'Disable %s, even if the database supports it. This is an '.
- 'advanced feature used for testing changes to Phabricator; you '.
+ 'advanced feature used for testing internal changes; you '.
'should not normally use this flag.',
'utf8mb4'),
),
));
} catch (PhutilArgumentUsageException $ex) {
$args->printUsageException($ex);
exit(77);
}
// First, test that the Phabricator configuration is set up correctly. After
// we know this works we'll test any administrative credentials specifically.
$refs = PhabricatorDatabaseRef::getActiveDatabaseRefs();
if (!$refs) {
throw new PhutilArgumentUsageException(
pht('No databases are configured.'));
}
$host = $args->getArg('host');
$ref_key = $args->getArg('ref');
if (($host !== null) || ($ref_key !== null)) {
if ($host && $ref_key) {
throw new PhutilArgumentUsageException(
pht(
'Use "--host" or "--ref" to select a database, but not both.'));
}
$refs = PhabricatorDatabaseRef::getActiveDatabaseRefs();
$possible_refs = array();
foreach ($refs as $possible_ref) {
if ($host && ($possible_ref->getHost() == $host)) {
$possible_refs[] = $possible_ref;
break;
}
if ($ref_key && ($possible_ref->getRefKey() == $ref_key)) {
$possible_refs[] = $possible_ref;
break;
}
}
if (!$possible_refs) {
if ($host) {
throw new PhutilArgumentUsageException(
pht(
'There is no configured database on host "%s". This command can '.
'only interact with configured databases.',
$host));
} else {
throw new PhutilArgumentUsageException(
pht(
'There is no configured database with ref "%s". This command can '.
'only interact with configured databases.',
$ref_key));
}
}
if (count($possible_refs) > 1) {
throw new PhutilArgumentUsageException(
pht(
'Host "%s" identifies more than one database. Use "--ref" to select '.
'a specific database.',
$host));
}
$refs = $possible_refs;
}
$apis = array();
foreach ($refs as $ref) {
$default_user = $ref->getUser();
$default_host = $ref->getHost();
$default_port = $ref->getPort();
$test_api = id(new PhabricatorStorageManagementAPI())
->setUser($default_user)
->setHost($default_host)
->setPort($default_port)
->setPassword($ref->getPass())
->setNamespace($args->getArg('namespace'));
try {
queryfx(
$test_api->getConn(null),
'SELECT 1');
} catch (AphrontQueryException $ex) {
$message = phutil_console_format(
"**%s**\n\n%s\n\n%s\n\n%s\n\n**%s**: %s\n",
pht('MySQL Credentials Not Configured'),
pht(
'Unable to connect to MySQL using the configured credentials. '.
'You must configure standard credentials before you can upgrade '.
'storage. Run these commands to set up credentials:'),
- " phabricator/ $ ./bin/config set mysql.host __host__\n".
- " phabricator/ $ ./bin/config set mysql.user __username__\n".
- " phabricator/ $ ./bin/config set mysql.pass __password__",
+ " $ ./bin/config set mysql.host __host__\n".
+ " $ ./bin/config set mysql.user __username__\n".
+ " $ ./bin/config set mysql.pass __password__",
pht(
'These standard credentials are separate from any administrative '.
'credentials provided to this command with __%s__ or '.
'__%s__, and must be configured correctly before you can proceed.',
'--user',
'--password'),
pht('Raw MySQL Error'),
$ex->getMessage());
echo phutil_console_wrap($message);
exit(1);
}
if ($args->getArg('password') === null) {
// This is already a PhutilOpaqueEnvelope.
$password = $ref->getPass();
} else {
// Put this in a PhutilOpaqueEnvelope.
$password = new PhutilOpaqueEnvelope($args->getArg('password'));
PhabricatorEnv::overrideConfig('mysql.pass', $args->getArg('password'));
}
$selected_user = $args->getArg('user');
if ($selected_user === null) {
$selected_user = $default_user;
}
$api = id(new PhabricatorStorageManagementAPI())
->setUser($selected_user)
->setHost($default_host)
->setPort($default_port)
->setPassword($password)
->setNamespace($args->getArg('namespace'))
->setDisableUTF8MB4($args->getArg('disable-utf8mb4'));
PhabricatorEnv::overrideConfig('mysql.user', $api->getUser());
$ref->setUser($selected_user);
$ref->setPass($password);
try {
queryfx(
$api->getConn(null),
'SELECT 1');
} catch (AphrontQueryException $ex) {
$message = phutil_console_format(
"**%s**\n\n%s\n\n**%s**: %s\n",
pht('Bad Administrative Credentials'),
pht(
'Unable to connect to MySQL using the administrative credentials '.
'provided with the __%s__ and __%s__ flags. Check that '.
'you have entered them correctly.',
'--user',
'--password'),
pht('Raw MySQL Error'),
$ex->getMessage());
echo phutil_console_wrap($message);
exit(1);
}
$api->setRef($ref);
$apis[] = $api;
}
$workflows = id(new PhutilClassMapQuery())
->setAncestorClass('PhabricatorStorageManagementWorkflow')
->execute();
$patches = PhabricatorSQLPatchList::buildAllPatches();
foreach ($workflows as $workflow) {
$workflow->setAPIs($apis);
$workflow->setPatches($patches);
}
$workflows[] = new PhutilHelpArgumentWorkflow();
$args->parseWorkflows($workflows);
diff --git a/scripts/ssh/ssh-exec.php b/scripts/ssh/ssh-exec.php
index c3a85b399c..70c95d28da 100755
--- a/scripts/ssh/ssh-exec.php
+++ b/scripts/ssh/ssh-exec.php
@@ -1,347 +1,349 @@
#!/usr/bin/env php
<?php
$ssh_start_time = microtime(true);
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/init/init-script.php';
$error_log = id(new PhutilErrorLog())
->setLogName(pht('SSH Error Log'))
->setLogPath(PhabricatorEnv::getEnvConfig('log.ssh-error.path'))
->activateLog();
$ssh_log = PhabricatorSSHLog::getLog();
$request_identifier = Filesystem::readRandomCharacters(12);
$ssh_log->setData(
array(
'Q' => $request_identifier,
));
$args = new PhutilArgumentParser($argv);
$args->setTagline(pht('execute SSH requests'));
$args->setSynopsis(<<<EOSYNOPSIS
**ssh-exec** --phabricator-ssh-user __user__ [--ssh-command __commmand__]
**ssh-exec** --phabricator-ssh-device __device__ [--ssh-command __commmand__]
Execute authenticated SSH requests. This script is normally invoked
via SSHD, but can be invoked manually for testing.
EOSYNOPSIS
);
$args->parseStandardArguments();
$args->parse(
array(
array(
'name' => 'phabricator-ssh-user',
'param' => 'username',
'help' => pht(
'If the request authenticated with a user key, the name of the '.
'user.'),
),
array(
'name' => 'phabricator-ssh-device',
'param' => 'name',
'help' => pht(
'If the request authenticated with a device key, the name of the '.
'device.'),
),
array(
'name' => 'phabricator-ssh-key',
'param' => 'id',
'help' => pht(
'The ID of the SSH key which authenticated this request. This is '.
'used to allow logs to report when specific keys were used, to make '.
'it easier to manage credentials.'),
),
array(
'name' => 'ssh-command',
'param' => 'command',
'help' => pht(
'Provide a command to execute. This makes testing this script '.
'easier. When running normally, the command is read from the '.
'environment (%s), which is populated by sshd.',
'SSH_ORIGINAL_COMMAND'),
),
));
try {
$remote_address = null;
$ssh_client = getenv('SSH_CLIENT');
if ($ssh_client) {
// This has the format "<ip> <remote-port> <local-port>". Grab the IP.
$remote_address = head(explode(' ', $ssh_client));
$ssh_log->setData(
array(
'r' => $remote_address,
));
}
$key_id = $args->getArg('phabricator-ssh-key');
if ($key_id) {
$ssh_log->setData(
array(
'k' => $key_id,
));
}
$user_name = $args->getArg('phabricator-ssh-user');
$device_name = $args->getArg('phabricator-ssh-device');
$user = null;
$device = null;
$is_cluster_request = false;
if ($user_name && $device_name) {
throw new Exception(
pht(
'The %s and %s flags are mutually exclusive. You can not '.
'authenticate as both a user ("%s") and a device ("%s"). '.
'Specify one or the other, but not both.',
'--phabricator-ssh-user',
'--phabricator-ssh-device',
$user_name,
$device_name));
} else if (strlen($user_name)) {
$user = id(new PhabricatorPeopleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withUsernames(array($user_name))
->executeOne();
if (!$user) {
throw new Exception(
pht(
'Invalid username ("%s"). There is no user with this username.',
$user_name));
}
id(new PhabricatorAuthSessionEngine())
->willServeRequestForUser($user);
} else if (strlen($device_name)) {
if (!$remote_address) {
throw new Exception(
pht(
'Unable to identify remote address from the %s environment '.
'variable. Device authentication is accepted only from trusted '.
'sources.',
'SSH_CLIENT'));
}
if (!PhabricatorEnv::isClusterAddress($remote_address)) {
throw new Exception(
pht(
- 'This request originates from outside of the Phabricator cluster '.
- 'address range. Requests signed with a trusted device key must '.
- 'originate from trusted hosts.'));
+ 'This request originates from outside of the cluster address range. '.
+ 'Requests signed with a trusted device key must originate from '.
+ 'trusted hosts.'));
}
$device = id(new AlmanacDeviceQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withNames(array($device_name))
->executeOne();
if (!$device) {
throw new Exception(
pht(
'Invalid device name ("%s"). There is no device with this name.',
$device_name));
}
if ($device->isDisabled()) {
throw new Exception(
pht(
'This request has authenticated as a device ("%s"), but this '.
'device is disabled.',
$device->getName()));
}
// We're authenticated as a device, but we're going to read the user out of
// the command below.
$is_cluster_request = true;
} else {
throw new Exception(
pht(
'This script must be invoked with either the %s or %s flag.',
'--phabricator-ssh-user',
'--phabricator-ssh-device'));
}
if ($args->getArg('ssh-command')) {
$original_command = $args->getArg('ssh-command');
} else {
$original_command = getenv('SSH_ORIGINAL_COMMAND');
}
$original_argv = id(new PhutilShellLexer())
->splitArguments($original_command);
if ($device) {
// If we're authenticating as a device, the first argument may be a
// "@username" argument to act as a particular user.
$first_argument = head($original_argv);
if (preg_match('/^@/', $first_argument)) {
$act_as_name = array_shift($original_argv);
$act_as_name = substr($act_as_name, 1);
$user = id(new PhabricatorPeopleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withUsernames(array($act_as_name))
->executeOne();
if (!$user) {
throw new Exception(
pht(
'Device request identifies an acting user with an invalid '.
'username ("%s"). There is no user with this username.',
$act_as_name));
}
} else {
$user = PhabricatorUser::getOmnipotentUser();
}
}
if ($user->isOmnipotent()) {
$user_name = 'device/'.$device->getName();
} else {
$user_name = $user->getUsername();
}
$ssh_log->setData(
array(
'u' => $user_name,
'P' => $user->getPHID(),
));
if (!$device) {
if (!$user->canEstablishSSHSessions()) {
throw new Exception(
pht(
'Your account ("%s") does not have permission to establish SSH '.
'sessions. Visit the web interface for more information.',
$user_name));
}
}
$workflows = id(new PhutilClassMapQuery())
->setAncestorClass('PhabricatorSSHWorkflow')
->setUniqueMethod('getName')
->execute();
$command_list = array_keys($workflows);
$command_list = implode(', ', $command_list);
$error_lines = array();
- $error_lines[] = pht('Welcome to Phabricator.');
+ $error_lines[] = pht(
+ 'Welcome to %s.',
+ PlatformSymbols::getPlatformServerName());
$error_lines[] = pht(
'You are logged in as %s.',
$user_name);
if (!$original_argv) {
$error_lines[] = pht(
'You have not specified a command to run. This means you are requesting '.
- 'an interactive shell, but Phabricator does not provide interactive '.
+ 'an interactive shell, but this server does not provide interactive '.
'shells over SSH.');
$error_lines[] = pht(
'(Usually, you should run a command like "git clone" or "hg push" '.
'instead of connecting directly with SSH.)');
$error_lines[] = pht(
'Supported commands are: %s.',
$command_list);
$error_lines = implode("\n\n", $error_lines);
throw new PhutilArgumentUsageException($error_lines);
}
$log_argv = implode(' ', $original_argv);
$log_argv = id(new PhutilUTF8StringTruncator())
->setMaximumCodepoints(128)
->truncateString($log_argv);
$ssh_log->setData(
array(
'C' => $original_argv[0],
'U' => $log_argv,
));
$command = head($original_argv);
$parseable_argv = $original_argv;
array_unshift($parseable_argv, 'phabricator-ssh-exec');
$parsed_args = new PhutilArgumentParser($parseable_argv);
if (empty($workflows[$command])) {
$error_lines[] = pht(
'You have specified the command "%s", but that command is not '.
- 'supported by Phabricator. As received by Phabricator, your entire '.
+ 'supported by this server. As received by this server, your entire '.
'argument list was:',
$command);
$error_lines[] = csprintf(' $ ssh ... -- %Ls', $parseable_argv);
$error_lines[] = pht(
'Supported commands are: %s.',
$command_list);
$error_lines = implode("\n\n", $error_lines);
throw new PhutilArgumentUsageException($error_lines);
}
$workflow = $parsed_args->parseWorkflows($workflows);
$workflow->setSSHUser($user);
$workflow->setOriginalArguments($original_argv);
$workflow->setIsClusterRequest($is_cluster_request);
$workflow->setRequestIdentifier($request_identifier);
$sock_stdin = fopen('php://stdin', 'r');
if (!$sock_stdin) {
throw new Exception(pht('Unable to open stdin.'));
}
$sock_stdout = fopen('php://stdout', 'w');
if (!$sock_stdout) {
throw new Exception(pht('Unable to open stdout.'));
}
$sock_stderr = fopen('php://stderr', 'w');
if (!$sock_stderr) {
throw new Exception(pht('Unable to open stderr.'));
}
$socket_channel = new PhutilSocketChannel(
$sock_stdin,
$sock_stdout);
$error_channel = new PhutilSocketChannel(null, $sock_stderr);
$metrics_channel = new PhutilMetricsChannel($socket_channel);
$workflow->setIOChannel($metrics_channel);
$workflow->setErrorChannel($error_channel);
$rethrow = null;
try {
$err = $workflow->execute($parsed_args);
$metrics_channel->flush();
$error_channel->flush();
} catch (Exception $ex) {
$rethrow = $ex;
}
// Always write this if we got as far as building a metrics channel.
$ssh_log->setData(
array(
'i' => $metrics_channel->getBytesRead(),
'o' => $metrics_channel->getBytesWritten(),
));
if ($rethrow) {
throw $rethrow;
}
} catch (Exception $ex) {
fwrite(STDERR, "phabricator-ssh-exec: ".$ex->getMessage()."\n");
$err = 1;
}
$ssh_log->setData(
array(
'c' => $err,
'T' => phutil_microseconds_since($ssh_start_time),
));
exit($err);
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 2b6f135645..c20a7cb634 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1,13050 +1,12802 @@
<?php
/**
* This file is automatically generated. Use 'arc liberate' to rebuild it.
*
* @generated
* @phutil-library-version 2
*/
phutil_register_library_map(array(
'__library_version__' => 2,
'class' => array(
'AlmanacAddress' => 'applications/almanac/util/AlmanacAddress.php',
'AlmanacBinding' => 'applications/almanac/storage/AlmanacBinding.php',
'AlmanacBindingDeletePropertyTransaction' => 'applications/almanac/xaction/AlmanacBindingDeletePropertyTransaction.php',
'AlmanacBindingDisableController' => 'applications/almanac/controller/AlmanacBindingDisableController.php',
'AlmanacBindingDisableTransaction' => 'applications/almanac/xaction/AlmanacBindingDisableTransaction.php',
'AlmanacBindingEditConduitAPIMethod' => 'applications/almanac/conduit/AlmanacBindingEditConduitAPIMethod.php',
'AlmanacBindingEditController' => 'applications/almanac/controller/AlmanacBindingEditController.php',
'AlmanacBindingEditEngine' => 'applications/almanac/editor/AlmanacBindingEditEngine.php',
'AlmanacBindingEditor' => 'applications/almanac/editor/AlmanacBindingEditor.php',
'AlmanacBindingInterfaceTransaction' => 'applications/almanac/xaction/AlmanacBindingInterfaceTransaction.php',
'AlmanacBindingPHIDType' => 'applications/almanac/phid/AlmanacBindingPHIDType.php',
'AlmanacBindingPropertyEditEngine' => 'applications/almanac/editor/AlmanacBindingPropertyEditEngine.php',
'AlmanacBindingQuery' => 'applications/almanac/query/AlmanacBindingQuery.php',
'AlmanacBindingSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacBindingSearchConduitAPIMethod.php',
'AlmanacBindingSearchEngine' => 'applications/almanac/query/AlmanacBindingSearchEngine.php',
'AlmanacBindingServiceTransaction' => 'applications/almanac/xaction/AlmanacBindingServiceTransaction.php',
'AlmanacBindingSetPropertyTransaction' => 'applications/almanac/xaction/AlmanacBindingSetPropertyTransaction.php',
'AlmanacBindingTableView' => 'applications/almanac/view/AlmanacBindingTableView.php',
'AlmanacBindingTransaction' => 'applications/almanac/storage/AlmanacBindingTransaction.php',
'AlmanacBindingTransactionQuery' => 'applications/almanac/query/AlmanacBindingTransactionQuery.php',
'AlmanacBindingTransactionType' => 'applications/almanac/xaction/AlmanacBindingTransactionType.php',
'AlmanacBindingViewController' => 'applications/almanac/controller/AlmanacBindingViewController.php',
'AlmanacBindingsSearchEngineAttachment' => 'applications/almanac/engineextension/AlmanacBindingsSearchEngineAttachment.php',
'AlmanacCacheEngineExtension' => 'applications/almanac/engineextension/AlmanacCacheEngineExtension.php',
'AlmanacClusterDatabaseServiceType' => 'applications/almanac/servicetype/AlmanacClusterDatabaseServiceType.php',
'AlmanacClusterRepositoryServiceType' => 'applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php',
'AlmanacClusterServiceType' => 'applications/almanac/servicetype/AlmanacClusterServiceType.php',
'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php',
'AlmanacController' => 'applications/almanac/controller/AlmanacController.php',
'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php',
'AlmanacCreateNamespacesCapability' => 'applications/almanac/capability/AlmanacCreateNamespacesCapability.php',
'AlmanacCreateNetworksCapability' => 'applications/almanac/capability/AlmanacCreateNetworksCapability.php',
'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php',
'AlmanacCustomServiceType' => 'applications/almanac/servicetype/AlmanacCustomServiceType.php',
'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php',
'AlmanacDeletePropertyEditField' => 'applications/almanac/engineextension/AlmanacDeletePropertyEditField.php',
'AlmanacDeletePropertyEditType' => 'applications/almanac/engineextension/AlmanacDeletePropertyEditType.php',
'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php',
'AlmanacDeviceController' => 'applications/almanac/controller/AlmanacDeviceController.php',
'AlmanacDeviceDeletePropertyTransaction' => 'applications/almanac/xaction/AlmanacDeviceDeletePropertyTransaction.php',
'AlmanacDeviceEditConduitAPIMethod' => 'applications/almanac/conduit/AlmanacDeviceEditConduitAPIMethod.php',
'AlmanacDeviceEditController' => 'applications/almanac/controller/AlmanacDeviceEditController.php',
'AlmanacDeviceEditEngine' => 'applications/almanac/editor/AlmanacDeviceEditEngine.php',
'AlmanacDeviceEditor' => 'applications/almanac/editor/AlmanacDeviceEditor.php',
'AlmanacDeviceListController' => 'applications/almanac/controller/AlmanacDeviceListController.php',
'AlmanacDeviceNameNgrams' => 'applications/almanac/storage/AlmanacDeviceNameNgrams.php',
'AlmanacDeviceNameTransaction' => 'applications/almanac/xaction/AlmanacDeviceNameTransaction.php',
'AlmanacDevicePHIDType' => 'applications/almanac/phid/AlmanacDevicePHIDType.php',
'AlmanacDevicePropertyEditEngine' => 'applications/almanac/editor/AlmanacDevicePropertyEditEngine.php',
'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php',
'AlmanacDeviceSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacDeviceSearchConduitAPIMethod.php',
'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php',
'AlmanacDeviceSetPropertyTransaction' => 'applications/almanac/xaction/AlmanacDeviceSetPropertyTransaction.php',
'AlmanacDeviceStatus' => 'applications/almanac/constants/AlmanacDeviceStatus.php',
'AlmanacDeviceStatusTransaction' => 'applications/almanac/xaction/AlmanacDeviceStatusTransaction.php',
'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php',
'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php',
'AlmanacDeviceTransactionType' => 'applications/almanac/xaction/AlmanacDeviceTransactionType.php',
'AlmanacDeviceViewController' => 'applications/almanac/controller/AlmanacDeviceViewController.php',
'AlmanacDrydockPoolServiceType' => 'applications/almanac/servicetype/AlmanacDrydockPoolServiceType.php',
'AlmanacEditor' => 'applications/almanac/editor/AlmanacEditor.php',
'AlmanacInterface' => 'applications/almanac/storage/AlmanacInterface.php',
'AlmanacInterfaceAddressTransaction' => 'applications/almanac/xaction/AlmanacInterfaceAddressTransaction.php',
'AlmanacInterfaceDatasource' => 'applications/almanac/typeahead/AlmanacInterfaceDatasource.php',
'AlmanacInterfaceDeleteController' => 'applications/almanac/controller/AlmanacInterfaceDeleteController.php',
'AlmanacInterfaceDestroyTransaction' => 'applications/almanac/xaction/AlmanacInterfaceDestroyTransaction.php',
'AlmanacInterfaceDeviceTransaction' => 'applications/almanac/xaction/AlmanacInterfaceDeviceTransaction.php',
'AlmanacInterfaceEditConduitAPIMethod' => 'applications/almanac/conduit/AlmanacInterfaceEditConduitAPIMethod.php',
'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php',
'AlmanacInterfaceEditEngine' => 'applications/almanac/editor/AlmanacInterfaceEditEngine.php',
'AlmanacInterfaceEditor' => 'applications/almanac/editor/AlmanacInterfaceEditor.php',
'AlmanacInterfaceNetworkTransaction' => 'applications/almanac/xaction/AlmanacInterfaceNetworkTransaction.php',
'AlmanacInterfacePHIDType' => 'applications/almanac/phid/AlmanacInterfacePHIDType.php',
'AlmanacInterfacePortTransaction' => 'applications/almanac/xaction/AlmanacInterfacePortTransaction.php',
'AlmanacInterfaceQuery' => 'applications/almanac/query/AlmanacInterfaceQuery.php',
'AlmanacInterfaceSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacInterfaceSearchConduitAPIMethod.php',
'AlmanacInterfaceSearchEngine' => 'applications/almanac/query/AlmanacInterfaceSearchEngine.php',
'AlmanacInterfaceTableView' => 'applications/almanac/view/AlmanacInterfaceTableView.php',
'AlmanacInterfaceTransaction' => 'applications/almanac/storage/AlmanacInterfaceTransaction.php',
'AlmanacInterfaceTransactionQuery' => 'applications/almanac/query/AlmanacInterfaceTransactionQuery.php',
'AlmanacInterfaceTransactionType' => 'applications/almanac/xaction/AlmanacInterfaceTransactionType.php',
'AlmanacKeys' => 'applications/almanac/util/AlmanacKeys.php',
'AlmanacManageClusterServicesCapability' => 'applications/almanac/capability/AlmanacManageClusterServicesCapability.php',
'AlmanacManagementRegisterWorkflow' => 'applications/almanac/management/AlmanacManagementRegisterWorkflow.php',
'AlmanacManagementTrustKeyWorkflow' => 'applications/almanac/management/AlmanacManagementTrustKeyWorkflow.php',
'AlmanacManagementUntrustKeyWorkflow' => 'applications/almanac/management/AlmanacManagementUntrustKeyWorkflow.php',
'AlmanacManagementWorkflow' => 'applications/almanac/management/AlmanacManagementWorkflow.php',
'AlmanacModularTransaction' => 'applications/almanac/storage/AlmanacModularTransaction.php',
'AlmanacNames' => 'applications/almanac/util/AlmanacNames.php',
'AlmanacNamesTestCase' => 'applications/almanac/util/__tests__/AlmanacNamesTestCase.php',
'AlmanacNamespace' => 'applications/almanac/storage/AlmanacNamespace.php',
'AlmanacNamespaceController' => 'applications/almanac/controller/AlmanacNamespaceController.php',
'AlmanacNamespaceEditConduitAPIMethod' => 'applications/almanac/conduit/AlmanacNamespaceEditConduitAPIMethod.php',
'AlmanacNamespaceEditController' => 'applications/almanac/controller/AlmanacNamespaceEditController.php',
'AlmanacNamespaceEditEngine' => 'applications/almanac/editor/AlmanacNamespaceEditEngine.php',
'AlmanacNamespaceEditor' => 'applications/almanac/editor/AlmanacNamespaceEditor.php',
'AlmanacNamespaceListController' => 'applications/almanac/controller/AlmanacNamespaceListController.php',
'AlmanacNamespaceNameNgrams' => 'applications/almanac/storage/AlmanacNamespaceNameNgrams.php',
'AlmanacNamespaceNameTransaction' => 'applications/almanac/xaction/AlmanacNamespaceNameTransaction.php',
'AlmanacNamespacePHIDType' => 'applications/almanac/phid/AlmanacNamespacePHIDType.php',
'AlmanacNamespaceQuery' => 'applications/almanac/query/AlmanacNamespaceQuery.php',
'AlmanacNamespaceSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacNamespaceSearchConduitAPIMethod.php',
'AlmanacNamespaceSearchEngine' => 'applications/almanac/query/AlmanacNamespaceSearchEngine.php',
'AlmanacNamespaceTransaction' => 'applications/almanac/storage/AlmanacNamespaceTransaction.php',
'AlmanacNamespaceTransactionQuery' => 'applications/almanac/query/AlmanacNamespaceTransactionQuery.php',
'AlmanacNamespaceTransactionType' => 'applications/almanac/xaction/AlmanacNamespaceTransactionType.php',
'AlmanacNamespaceViewController' => 'applications/almanac/controller/AlmanacNamespaceViewController.php',
'AlmanacNetwork' => 'applications/almanac/storage/AlmanacNetwork.php',
'AlmanacNetworkController' => 'applications/almanac/controller/AlmanacNetworkController.php',
'AlmanacNetworkEditConduitAPIMethod' => 'applications/almanac/conduit/AlmanacNetworkEditConduitAPIMethod.php',
'AlmanacNetworkEditController' => 'applications/almanac/controller/AlmanacNetworkEditController.php',
'AlmanacNetworkEditEngine' => 'applications/almanac/editor/AlmanacNetworkEditEngine.php',
'AlmanacNetworkEditor' => 'applications/almanac/editor/AlmanacNetworkEditor.php',
'AlmanacNetworkListController' => 'applications/almanac/controller/AlmanacNetworkListController.php',
'AlmanacNetworkNameNgrams' => 'applications/almanac/storage/AlmanacNetworkNameNgrams.php',
'AlmanacNetworkNameTransaction' => 'applications/almanac/xaction/AlmanacNetworkNameTransaction.php',
'AlmanacNetworkPHIDType' => 'applications/almanac/phid/AlmanacNetworkPHIDType.php',
'AlmanacNetworkQuery' => 'applications/almanac/query/AlmanacNetworkQuery.php',
'AlmanacNetworkSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacNetworkSearchConduitAPIMethod.php',
'AlmanacNetworkSearchEngine' => 'applications/almanac/query/AlmanacNetworkSearchEngine.php',
'AlmanacNetworkTransaction' => 'applications/almanac/storage/AlmanacNetworkTransaction.php',
'AlmanacNetworkTransactionQuery' => 'applications/almanac/query/AlmanacNetworkTransactionQuery.php',
'AlmanacNetworkTransactionType' => 'applications/almanac/xaction/AlmanacNetworkTransactionType.php',
'AlmanacNetworkViewController' => 'applications/almanac/controller/AlmanacNetworkViewController.php',
'AlmanacPropertiesDestructionEngineExtension' => 'applications/almanac/engineextension/AlmanacPropertiesDestructionEngineExtension.php',
'AlmanacPropertiesEditEngineExtension' => 'applications/almanac/engineextension/AlmanacPropertiesEditEngineExtension.php',
'AlmanacPropertiesSearchEngineAttachment' => 'applications/almanac/engineextension/AlmanacPropertiesSearchEngineAttachment.php',
'AlmanacProperty' => 'applications/almanac/storage/AlmanacProperty.php',
'AlmanacPropertyController' => 'applications/almanac/controller/AlmanacPropertyController.php',
'AlmanacPropertyDeleteController' => 'applications/almanac/controller/AlmanacPropertyDeleteController.php',
'AlmanacPropertyEditController' => 'applications/almanac/controller/AlmanacPropertyEditController.php',
'AlmanacPropertyEditEngine' => 'applications/almanac/editor/AlmanacPropertyEditEngine.php',
'AlmanacPropertyInterface' => 'applications/almanac/property/AlmanacPropertyInterface.php',
'AlmanacPropertyQuery' => 'applications/almanac/query/AlmanacPropertyQuery.php',
'AlmanacQuery' => 'applications/almanac/query/AlmanacQuery.php',
'AlmanacSchemaSpec' => 'applications/almanac/storage/AlmanacSchemaSpec.php',
'AlmanacSearchEngineAttachment' => 'applications/almanac/engineextension/AlmanacSearchEngineAttachment.php',
'AlmanacService' => 'applications/almanac/storage/AlmanacService.php',
'AlmanacServiceController' => 'applications/almanac/controller/AlmanacServiceController.php',
'AlmanacServiceDatasource' => 'applications/almanac/typeahead/AlmanacServiceDatasource.php',
'AlmanacServiceDeletePropertyTransaction' => 'applications/almanac/xaction/AlmanacServiceDeletePropertyTransaction.php',
'AlmanacServiceEditConduitAPIMethod' => 'applications/almanac/conduit/AlmanacServiceEditConduitAPIMethod.php',
'AlmanacServiceEditController' => 'applications/almanac/controller/AlmanacServiceEditController.php',
'AlmanacServiceEditEngine' => 'applications/almanac/editor/AlmanacServiceEditEngine.php',
'AlmanacServiceEditor' => 'applications/almanac/editor/AlmanacServiceEditor.php',
'AlmanacServiceListController' => 'applications/almanac/controller/AlmanacServiceListController.php',
'AlmanacServiceNameNgrams' => 'applications/almanac/storage/AlmanacServiceNameNgrams.php',
'AlmanacServiceNameTransaction' => 'applications/almanac/xaction/AlmanacServiceNameTransaction.php',
'AlmanacServicePHIDType' => 'applications/almanac/phid/AlmanacServicePHIDType.php',
'AlmanacServicePropertyEditEngine' => 'applications/almanac/editor/AlmanacServicePropertyEditEngine.php',
'AlmanacServiceQuery' => 'applications/almanac/query/AlmanacServiceQuery.php',
'AlmanacServiceSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacServiceSearchConduitAPIMethod.php',
'AlmanacServiceSearchEngine' => 'applications/almanac/query/AlmanacServiceSearchEngine.php',
'AlmanacServiceSetPropertyTransaction' => 'applications/almanac/xaction/AlmanacServiceSetPropertyTransaction.php',
'AlmanacServiceTransaction' => 'applications/almanac/storage/AlmanacServiceTransaction.php',
'AlmanacServiceTransactionQuery' => 'applications/almanac/query/AlmanacServiceTransactionQuery.php',
'AlmanacServiceTransactionType' => 'applications/almanac/xaction/AlmanacServiceTransactionType.php',
'AlmanacServiceType' => 'applications/almanac/servicetype/AlmanacServiceType.php',
'AlmanacServiceTypeDatasource' => 'applications/almanac/typeahead/AlmanacServiceTypeDatasource.php',
'AlmanacServiceTypeTestCase' => 'applications/almanac/servicetype/__tests__/AlmanacServiceTypeTestCase.php',
'AlmanacServiceTypeTransaction' => 'applications/almanac/xaction/AlmanacServiceTypeTransaction.php',
'AlmanacServiceViewController' => 'applications/almanac/controller/AlmanacServiceViewController.php',
'AlmanacSetPropertyEditField' => 'applications/almanac/engineextension/AlmanacSetPropertyEditField.php',
'AlmanacSetPropertyEditType' => 'applications/almanac/engineextension/AlmanacSetPropertyEditType.php',
'AlmanacTransactionType' => 'applications/almanac/xaction/AlmanacTransactionType.php',
'AphlictDropdownDataQuery' => 'applications/aphlict/query/AphlictDropdownDataQuery.php',
'Aphront304Response' => 'aphront/response/Aphront304Response.php',
'Aphront400Response' => 'aphront/response/Aphront400Response.php',
'Aphront403Response' => 'aphront/response/Aphront403Response.php',
'Aphront404Response' => 'aphront/response/Aphront404Response.php',
'AphrontAccessDeniedQueryException' => 'infrastructure/storage/exception/AphrontAccessDeniedQueryException.php',
'AphrontAjaxResponse' => 'aphront/response/AphrontAjaxResponse.php',
'AphrontApplicationConfiguration' => 'aphront/configuration/AphrontApplicationConfiguration.php',
'AphrontAutoIDView' => 'view/AphrontAutoIDView.php',
'AphrontBarView' => 'view/widget/bars/AphrontBarView.php',
'AphrontBaseMySQLDatabaseConnection' => 'infrastructure/storage/connection/mysql/AphrontBaseMySQLDatabaseConnection.php',
'AphrontBoolHTTPParameterType' => 'aphront/httpparametertype/AphrontBoolHTTPParameterType.php',
'AphrontCalendarEventView' => 'applications/calendar/view/AphrontCalendarEventView.php',
'AphrontCharacterSetQueryException' => 'infrastructure/storage/exception/AphrontCharacterSetQueryException.php',
'AphrontConnectionLostQueryException' => 'infrastructure/storage/exception/AphrontConnectionLostQueryException.php',
'AphrontConnectionQueryException' => 'infrastructure/storage/exception/AphrontConnectionQueryException.php',
'AphrontController' => 'aphront/AphrontController.php',
'AphrontCountQueryException' => 'infrastructure/storage/exception/AphrontCountQueryException.php',
'AphrontCursorPagerView' => 'view/control/AphrontCursorPagerView.php',
'AphrontDatabaseConnection' => 'infrastructure/storage/connection/AphrontDatabaseConnection.php',
'AphrontDatabaseTableRef' => 'infrastructure/storage/xsprintf/AphrontDatabaseTableRef.php',
'AphrontDatabaseTableRefInterface' => 'infrastructure/storage/xsprintf/AphrontDatabaseTableRefInterface.php',
'AphrontDatabaseTransactionState' => 'infrastructure/storage/connection/AphrontDatabaseTransactionState.php',
'AphrontDeadlockQueryException' => 'infrastructure/storage/exception/AphrontDeadlockQueryException.php',
'AphrontDialogResponse' => 'aphront/response/AphrontDialogResponse.php',
'AphrontDialogView' => 'view/AphrontDialogView.php',
'AphrontDuplicateKeyQueryException' => 'infrastructure/storage/exception/AphrontDuplicateKeyQueryException.php',
'AphrontEpochHTTPParameterType' => 'aphront/httpparametertype/AphrontEpochHTTPParameterType.php',
'AphrontException' => 'aphront/exception/AphrontException.php',
'AphrontFileHTTPParameterType' => 'aphront/httpparametertype/AphrontFileHTTPParameterType.php',
'AphrontFileResponse' => 'aphront/response/AphrontFileResponse.php',
'AphrontFormCheckboxControl' => 'view/form/control/AphrontFormCheckboxControl.php',
'AphrontFormControl' => 'view/form/control/AphrontFormControl.php',
'AphrontFormDateControl' => 'view/form/control/AphrontFormDateControl.php',
'AphrontFormDateControlValue' => 'view/form/control/AphrontFormDateControlValue.php',
'AphrontFormDividerControl' => 'view/form/control/AphrontFormDividerControl.php',
'AphrontFormFileControl' => 'view/form/control/AphrontFormFileControl.php',
'AphrontFormHandlesControl' => 'view/form/control/AphrontFormHandlesControl.php',
'AphrontFormMarkupControl' => 'view/form/control/AphrontFormMarkupControl.php',
'AphrontFormPasswordControl' => 'view/form/control/AphrontFormPasswordControl.php',
'AphrontFormPolicyControl' => 'view/form/control/AphrontFormPolicyControl.php',
'AphrontFormRadioButtonControl' => 'view/form/control/AphrontFormRadioButtonControl.php',
'AphrontFormRecaptchaControl' => 'view/form/control/AphrontFormRecaptchaControl.php',
'AphrontFormSelectControl' => 'view/form/control/AphrontFormSelectControl.php',
'AphrontFormStaticControl' => 'view/form/control/AphrontFormStaticControl.php',
'AphrontFormSubmitControl' => 'view/form/control/AphrontFormSubmitControl.php',
'AphrontFormTextAreaControl' => 'view/form/control/AphrontFormTextAreaControl.php',
'AphrontFormTextControl' => 'view/form/control/AphrontFormTextControl.php',
'AphrontFormTextWithSubmitControl' => 'view/form/control/AphrontFormTextWithSubmitControl.php',
'AphrontFormTokenizerControl' => 'view/form/control/AphrontFormTokenizerControl.php',
'AphrontFormTypeaheadControl' => 'view/form/control/AphrontFormTypeaheadControl.php',
'AphrontFormView' => 'view/form/AphrontFormView.php',
'AphrontGlyphBarView' => 'view/widget/bars/AphrontGlyphBarView.php',
'AphrontHTMLResponse' => 'aphront/response/AphrontHTMLResponse.php',
'AphrontHTTPHeaderParser' => 'aphront/headerparser/AphrontHTTPHeaderParser.php',
'AphrontHTTPHeaderParserTestCase' => 'aphront/headerparser/__tests__/AphrontHTTPHeaderParserTestCase.php',
'AphrontHTTPParameterType' => 'aphront/httpparametertype/AphrontHTTPParameterType.php',
'AphrontHTTPProxyResponse' => 'aphront/response/AphrontHTTPProxyResponse.php',
'AphrontHTTPSink' => 'aphront/sink/AphrontHTTPSink.php',
'AphrontHTTPSinkTestCase' => 'aphront/sink/__tests__/AphrontHTTPSinkTestCase.php',
'AphrontIntHTTPParameterType' => 'aphront/httpparametertype/AphrontIntHTTPParameterType.php',
'AphrontInvalidCredentialsQueryException' => 'infrastructure/storage/exception/AphrontInvalidCredentialsQueryException.php',
'AphrontIsolatedDatabaseConnection' => 'infrastructure/storage/connection/AphrontIsolatedDatabaseConnection.php',
'AphrontIsolatedDatabaseConnectionTestCase' => 'infrastructure/storage/__tests__/AphrontIsolatedDatabaseConnectionTestCase.php',
'AphrontIsolatedHTTPSink' => 'aphront/sink/AphrontIsolatedHTTPSink.php',
+ 'AphrontJSONHTTPParameterType' => 'aphront/httpparametertype/AphrontJSONHTTPParameterType.php',
'AphrontJSONResponse' => 'aphront/response/AphrontJSONResponse.php',
'AphrontJavelinView' => 'view/AphrontJavelinView.php',
'AphrontKeyboardShortcutsAvailableView' => 'view/widget/AphrontKeyboardShortcutsAvailableView.php',
'AphrontListFilterView' => 'view/layout/AphrontListFilterView.php',
'AphrontListHTTPParameterType' => 'aphront/httpparametertype/AphrontListHTTPParameterType.php',
'AphrontLockTimeoutQueryException' => 'infrastructure/storage/exception/AphrontLockTimeoutQueryException.php',
'AphrontMalformedRequestException' => 'aphront/exception/AphrontMalformedRequestException.php',
'AphrontMoreView' => 'view/layout/AphrontMoreView.php',
'AphrontMultiColumnView' => 'view/layout/AphrontMultiColumnView.php',
'AphrontMultipartParser' => 'aphront/multipartparser/AphrontMultipartParser.php',
'AphrontMultipartParserTestCase' => 'aphront/multipartparser/__tests__/AphrontMultipartParserTestCase.php',
'AphrontMultipartPart' => 'aphront/multipartparser/AphrontMultipartPart.php',
'AphrontMySQLDatabaseConnection' => 'infrastructure/storage/connection/mysql/AphrontMySQLDatabaseConnection.php',
'AphrontMySQLDatabaseConnectionTestCase' => 'infrastructure/storage/__tests__/AphrontMySQLDatabaseConnectionTestCase.php',
'AphrontMySQLiDatabaseConnection' => 'infrastructure/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php',
'AphrontNotSupportedQueryException' => 'infrastructure/storage/exception/AphrontNotSupportedQueryException.php',
'AphrontNullView' => 'view/AphrontNullView.php',
'AphrontObjectMissingQueryException' => 'infrastructure/storage/exception/AphrontObjectMissingQueryException.php',
'AphrontPHIDHTTPParameterType' => 'aphront/httpparametertype/AphrontPHIDHTTPParameterType.php',
'AphrontPHIDListHTTPParameterType' => 'aphront/httpparametertype/AphrontPHIDListHTTPParameterType.php',
'AphrontPHPHTTPSink' => 'aphront/sink/AphrontPHPHTTPSink.php',
'AphrontPageView' => 'view/page/AphrontPageView.php',
'AphrontParameterQueryException' => 'infrastructure/storage/exception/AphrontParameterQueryException.php',
'AphrontPlainTextResponse' => 'aphront/response/AphrontPlainTextResponse.php',
'AphrontProgressBarView' => 'view/widget/bars/AphrontProgressBarView.php',
'AphrontProjectListHTTPParameterType' => 'aphront/httpparametertype/AphrontProjectListHTTPParameterType.php',
'AphrontProxyResponse' => 'aphront/response/AphrontProxyResponse.php',
'AphrontQueryException' => 'infrastructure/storage/exception/AphrontQueryException.php',
'AphrontQueryTimeoutQueryException' => 'infrastructure/storage/exception/AphrontQueryTimeoutQueryException.php',
'AphrontRecoverableQueryException' => 'infrastructure/storage/exception/AphrontRecoverableQueryException.php',
'AphrontRedirectResponse' => 'aphront/response/AphrontRedirectResponse.php',
'AphrontRedirectResponseTestCase' => 'aphront/response/__tests__/AphrontRedirectResponseTestCase.php',
'AphrontReloadResponse' => 'aphront/response/AphrontReloadResponse.php',
+ 'AphrontRemarkupHTTPParameterType' => 'aphront/httpparametertype/AphrontRemarkupHTTPParameterType.php',
'AphrontRequest' => 'aphront/AphrontRequest.php',
'AphrontRequestExceptionHandler' => 'aphront/handler/AphrontRequestExceptionHandler.php',
'AphrontRequestStream' => 'aphront/requeststream/AphrontRequestStream.php',
'AphrontRequestTestCase' => 'aphront/__tests__/AphrontRequestTestCase.php',
'AphrontResponse' => 'aphront/response/AphrontResponse.php',
'AphrontResponseProducerInterface' => 'aphront/interface/AphrontResponseProducerInterface.php',
'AphrontRoutingMap' => 'aphront/site/AphrontRoutingMap.php',
'AphrontRoutingMapTestCase' => 'aphront/__tests__/AphrontRoutingMapTestCase.php',
'AphrontRoutingResult' => 'aphront/site/AphrontRoutingResult.php',
'AphrontSchemaQueryException' => 'infrastructure/storage/exception/AphrontSchemaQueryException.php',
'AphrontScopedUnguardedWriteCapability' => 'aphront/writeguard/AphrontScopedUnguardedWriteCapability.php',
'AphrontSelectHTTPParameterType' => 'aphront/httpparametertype/AphrontSelectHTTPParameterType.php',
'AphrontSideNavFilterView' => 'view/layout/AphrontSideNavFilterView.php',
'AphrontSite' => 'aphront/site/AphrontSite.php',
'AphrontStackTraceView' => 'view/widget/AphrontStackTraceView.php',
'AphrontStandaloneHTMLResponse' => 'aphront/response/AphrontStandaloneHTMLResponse.php',
'AphrontStringHTTPParameterType' => 'aphront/httpparametertype/AphrontStringHTTPParameterType.php',
'AphrontStringListHTTPParameterType' => 'aphront/httpparametertype/AphrontStringListHTTPParameterType.php',
'AphrontTableView' => 'view/control/AphrontTableView.php',
'AphrontTagView' => 'view/AphrontTagView.php',
'AphrontTokenizerTemplateView' => 'view/control/AphrontTokenizerTemplateView.php',
'AphrontTypeaheadTemplateView' => 'view/control/AphrontTypeaheadTemplateView.php',
'AphrontUnhandledExceptionResponse' => 'aphront/response/AphrontUnhandledExceptionResponse.php',
'AphrontUserListHTTPParameterType' => 'aphront/httpparametertype/AphrontUserListHTTPParameterType.php',
'AphrontView' => 'view/AphrontView.php',
'AphrontWebpageResponse' => 'aphront/response/AphrontWebpageResponse.php',
'AphrontWriteGuard' => 'aphront/writeguard/AphrontWriteGuard.php',
'ArcanistConduitAPIMethod' => 'applications/arcanist/conduit/ArcanistConduitAPIMethod.php',
'AuditConduitAPIMethod' => 'applications/audit/conduit/AuditConduitAPIMethod.php',
'AuditQueryConduitAPIMethod' => 'applications/audit/conduit/AuditQueryConduitAPIMethod.php',
'AuthManageProvidersCapability' => 'applications/auth/capability/AuthManageProvidersCapability.php',
'BulkParameterType' => 'applications/transactions/bulk/type/BulkParameterType.php',
'BulkPointsParameterType' => 'applications/transactions/bulk/type/BulkPointsParameterType.php',
'BulkRemarkupParameterType' => 'applications/transactions/bulk/type/BulkRemarkupParameterType.php',
'BulkSelectParameterType' => 'applications/transactions/bulk/type/BulkSelectParameterType.php',
'BulkStringParameterType' => 'applications/transactions/bulk/type/BulkStringParameterType.php',
'BulkTokenizerParameterType' => 'applications/transactions/bulk/type/BulkTokenizerParameterType.php',
'CalendarTimeUtil' => 'applications/calendar/util/CalendarTimeUtil.php',
'CalendarTimeUtilTestCase' => 'applications/calendar/__tests__/CalendarTimeUtilTestCase.php',
'CelerityAPI' => 'applications/celerity/CelerityAPI.php',
'CelerityDarkModePostprocessor' => 'applications/celerity/postprocessor/CelerityDarkModePostprocessor.php',
'CelerityDefaultPostprocessor' => 'applications/celerity/postprocessor/CelerityDefaultPostprocessor.php',
'CelerityHighContrastPostprocessor' => 'applications/celerity/postprocessor/CelerityHighContrastPostprocessor.php',
'CelerityLargeFontPostprocessor' => 'applications/celerity/postprocessor/CelerityLargeFontPostprocessor.php',
'CelerityManagementMapWorkflow' => 'applications/celerity/management/CelerityManagementMapWorkflow.php',
'CelerityManagementSyntaxWorkflow' => 'applications/celerity/management/CelerityManagementSyntaxWorkflow.php',
'CelerityManagementWorkflow' => 'applications/celerity/management/CelerityManagementWorkflow.php',
'CelerityPhabricatorResourceController' => 'applications/celerity/controller/CelerityPhabricatorResourceController.php',
'CelerityPhabricatorResources' => 'applications/celerity/resources/CelerityPhabricatorResources.php',
'CelerityPhysicalResources' => 'applications/celerity/resources/CelerityPhysicalResources.php',
'CelerityPhysicalResourcesTestCase' => 'applications/celerity/resources/__tests__/CelerityPhysicalResourcesTestCase.php',
'CelerityPostprocessor' => 'applications/celerity/postprocessor/CelerityPostprocessor.php',
'CelerityPostprocessorTestCase' => 'applications/celerity/__tests__/CelerityPostprocessorTestCase.php',
'CelerityRedGreenPostprocessor' => 'applications/celerity/postprocessor/CelerityRedGreenPostprocessor.php',
'CelerityResourceController' => 'applications/celerity/controller/CelerityResourceController.php',
'CelerityResourceGraph' => 'applications/celerity/CelerityResourceGraph.php',
'CelerityResourceMap' => 'applications/celerity/CelerityResourceMap.php',
'CelerityResourceMapGenerator' => 'applications/celerity/CelerityResourceMapGenerator.php',
'CelerityResourceTransformer' => 'applications/celerity/CelerityResourceTransformer.php',
'CelerityResourceTransformerTestCase' => 'applications/celerity/__tests__/CelerityResourceTransformerTestCase.php',
'CelerityResources' => 'applications/celerity/resources/CelerityResources.php',
'CelerityResourcesOnDisk' => 'applications/celerity/resources/CelerityResourcesOnDisk.php',
'CeleritySpriteGenerator' => 'applications/celerity/CeleritySpriteGenerator.php',
'CelerityStaticResourceResponse' => 'applications/celerity/CelerityStaticResourceResponse.php',
'ChatLogConduitAPIMethod' => 'applications/chatlog/conduit/ChatLogConduitAPIMethod.php',
'ChatLogQueryConduitAPIMethod' => 'applications/chatlog/conduit/ChatLogQueryConduitAPIMethod.php',
'ChatLogRecordConduitAPIMethod' => 'applications/chatlog/conduit/ChatLogRecordConduitAPIMethod.php',
'ConduitAPIDocumentationPage' => 'applications/conduit/data/ConduitAPIDocumentationPage.php',
'ConduitAPIMethod' => 'applications/conduit/method/ConduitAPIMethod.php',
'ConduitAPIMethodTestCase' => 'applications/conduit/method/__tests__/ConduitAPIMethodTestCase.php',
'ConduitAPIRequest' => 'applications/conduit/protocol/ConduitAPIRequest.php',
'ConduitAPIResponse' => 'applications/conduit/protocol/ConduitAPIResponse.php',
'ConduitApplicationNotInstalledException' => 'applications/conduit/protocol/exception/ConduitApplicationNotInstalledException.php',
'ConduitBoolParameterType' => 'applications/conduit/parametertype/ConduitBoolParameterType.php',
'ConduitCall' => 'applications/conduit/call/ConduitCall.php',
'ConduitCallTestCase' => 'applications/conduit/call/__tests__/ConduitCallTestCase.php',
'ConduitColumnsParameterType' => 'applications/conduit/parametertype/ConduitColumnsParameterType.php',
'ConduitConnectConduitAPIMethod' => 'applications/conduit/method/ConduitConnectConduitAPIMethod.php',
'ConduitConstantDescription' => 'applications/conduit/data/ConduitConstantDescription.php',
'ConduitEpochParameterType' => 'applications/conduit/parametertype/ConduitEpochParameterType.php',
'ConduitException' => 'applications/conduit/protocol/exception/ConduitException.php',
'ConduitGetCapabilitiesConduitAPIMethod' => 'applications/conduit/method/ConduitGetCapabilitiesConduitAPIMethod.php',
'ConduitGetCertificateConduitAPIMethod' => 'applications/conduit/method/ConduitGetCertificateConduitAPIMethod.php',
'ConduitIntListParameterType' => 'applications/conduit/parametertype/ConduitIntListParameterType.php',
'ConduitIntParameterType' => 'applications/conduit/parametertype/ConduitIntParameterType.php',
'ConduitListParameterType' => 'applications/conduit/parametertype/ConduitListParameterType.php',
'ConduitLogGarbageCollector' => 'applications/conduit/garbagecollector/ConduitLogGarbageCollector.php',
'ConduitMethodDoesNotExistException' => 'applications/conduit/protocol/exception/ConduitMethodDoesNotExistException.php',
'ConduitMethodNotFoundException' => 'applications/conduit/protocol/exception/ConduitMethodNotFoundException.php',
'ConduitPHIDListParameterType' => 'applications/conduit/parametertype/ConduitPHIDListParameterType.php',
'ConduitPHIDParameterType' => 'applications/conduit/parametertype/ConduitPHIDParameterType.php',
'ConduitParameterType' => 'applications/conduit/parametertype/ConduitParameterType.php',
'ConduitPingConduitAPIMethod' => 'applications/conduit/method/ConduitPingConduitAPIMethod.php',
'ConduitPointsParameterType' => 'applications/conduit/parametertype/ConduitPointsParameterType.php',
'ConduitProjectListParameterType' => 'applications/conduit/parametertype/ConduitProjectListParameterType.php',
'ConduitQueryConduitAPIMethod' => 'applications/conduit/method/ConduitQueryConduitAPIMethod.php',
'ConduitResultSearchEngineExtension' => 'applications/conduit/query/ConduitResultSearchEngineExtension.php',
'ConduitSSHWorkflow' => 'applications/conduit/ssh/ConduitSSHWorkflow.php',
'ConduitStringListParameterType' => 'applications/conduit/parametertype/ConduitStringListParameterType.php',
'ConduitStringParameterType' => 'applications/conduit/parametertype/ConduitStringParameterType.php',
'ConduitTokenGarbageCollector' => 'applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php',
'ConduitUserListParameterType' => 'applications/conduit/parametertype/ConduitUserListParameterType.php',
'ConduitUserParameterType' => 'applications/conduit/parametertype/ConduitUserParameterType.php',
'ConduitWildParameterType' => 'applications/conduit/parametertype/ConduitWildParameterType.php',
'ConpherenceColumnViewController' => 'applications/conpherence/controller/ConpherenceColumnViewController.php',
'ConpherenceConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceConduitAPIMethod.php',
'ConpherenceConstants' => 'applications/conpherence/constants/ConpherenceConstants.php',
'ConpherenceController' => 'applications/conpherence/controller/ConpherenceController.php',
'ConpherenceCreateThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php',
'ConpherenceDAO' => 'applications/conpherence/storage/ConpherenceDAO.php',
'ConpherenceDurableColumnView' => 'applications/conpherence/view/ConpherenceDurableColumnView.php',
'ConpherenceEditConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceEditConduitAPIMethod.php',
'ConpherenceEditEngine' => 'applications/conpherence/editor/ConpherenceEditEngine.php',
'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.php',
'ConpherenceFulltextQuery' => 'applications/conpherence/query/ConpherenceFulltextQuery.php',
'ConpherenceIndex' => 'applications/conpherence/storage/ConpherenceIndex.php',
'ConpherenceLayoutView' => 'applications/conpherence/view/ConpherenceLayoutView.php',
'ConpherenceListController' => 'applications/conpherence/controller/ConpherenceListController.php',
'ConpherenceMenuItemView' => 'applications/conpherence/view/ConpherenceMenuItemView.php',
'ConpherenceNotificationPanelController' => 'applications/conpherence/controller/ConpherenceNotificationPanelController.php',
'ConpherenceParticipant' => 'applications/conpherence/storage/ConpherenceParticipant.php',
'ConpherenceParticipantController' => 'applications/conpherence/controller/ConpherenceParticipantController.php',
'ConpherenceParticipantCountQuery' => 'applications/conpherence/query/ConpherenceParticipantCountQuery.php',
'ConpherenceParticipantQuery' => 'applications/conpherence/query/ConpherenceParticipantQuery.php',
'ConpherenceParticipantView' => 'applications/conpherence/view/ConpherenceParticipantView.php',
'ConpherenceQueryThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php',
'ConpherenceQueryTransactionConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceQueryTransactionConduitAPIMethod.php',
'ConpherenceReplyHandler' => 'applications/conpherence/mail/ConpherenceReplyHandler.php',
'ConpherenceRoomEditController' => 'applications/conpherence/controller/ConpherenceRoomEditController.php',
'ConpherenceRoomListController' => 'applications/conpherence/controller/ConpherenceRoomListController.php',
'ConpherenceRoomPictureController' => 'applications/conpherence/controller/ConpherenceRoomPictureController.php',
'ConpherenceRoomPreferencesController' => 'applications/conpherence/controller/ConpherenceRoomPreferencesController.php',
'ConpherenceRoomSettings' => 'applications/conpherence/constants/ConpherenceRoomSettings.php',
'ConpherenceRoomTestCase' => 'applications/conpherence/__tests__/ConpherenceRoomTestCase.php',
'ConpherenceSchemaSpec' => 'applications/conpherence/storage/ConpherenceSchemaSpec.php',
'ConpherenceTestCase' => 'applications/conpherence/__tests__/ConpherenceTestCase.php',
'ConpherenceThread' => 'applications/conpherence/storage/ConpherenceThread.php',
'ConpherenceThreadDatasource' => 'applications/conpherence/typeahead/ConpherenceThreadDatasource.php',
'ConpherenceThreadDateMarkerTransaction' => 'applications/conpherence/xaction/ConpherenceThreadDateMarkerTransaction.php',
'ConpherenceThreadIndexEngineExtension' => 'applications/conpherence/engineextension/ConpherenceThreadIndexEngineExtension.php',
'ConpherenceThreadListView' => 'applications/conpherence/view/ConpherenceThreadListView.php',
'ConpherenceThreadMailReceiver' => 'applications/conpherence/mail/ConpherenceThreadMailReceiver.php',
'ConpherenceThreadMembersPolicyRule' => 'applications/conpherence/policyrule/ConpherenceThreadMembersPolicyRule.php',
'ConpherenceThreadParticipantsTransaction' => 'applications/conpherence/xaction/ConpherenceThreadParticipantsTransaction.php',
'ConpherenceThreadPictureTransaction' => 'applications/conpherence/xaction/ConpherenceThreadPictureTransaction.php',
'ConpherenceThreadQuery' => 'applications/conpherence/query/ConpherenceThreadQuery.php',
'ConpherenceThreadRemarkupRule' => 'applications/conpherence/remarkup/ConpherenceThreadRemarkupRule.php',
'ConpherenceThreadSearchController' => 'applications/conpherence/controller/ConpherenceThreadSearchController.php',
'ConpherenceThreadSearchEngine' => 'applications/conpherence/query/ConpherenceThreadSearchEngine.php',
'ConpherenceThreadTitleNgrams' => 'applications/conpherence/storage/ConpherenceThreadTitleNgrams.php',
'ConpherenceThreadTitleTransaction' => 'applications/conpherence/xaction/ConpherenceThreadTitleTransaction.php',
'ConpherenceThreadTopicTransaction' => 'applications/conpherence/xaction/ConpherenceThreadTopicTransaction.php',
'ConpherenceThreadTransactionType' => 'applications/conpherence/xaction/ConpherenceThreadTransactionType.php',
'ConpherenceTransaction' => 'applications/conpherence/storage/ConpherenceTransaction.php',
'ConpherenceTransactionComment' => 'applications/conpherence/storage/ConpherenceTransactionComment.php',
'ConpherenceTransactionQuery' => 'applications/conpherence/query/ConpherenceTransactionQuery.php',
'ConpherenceTransactionRenderer' => 'applications/conpherence/ConpherenceTransactionRenderer.php',
'ConpherenceTransactionView' => 'applications/conpherence/view/ConpherenceTransactionView.php',
'ConpherenceUpdateActions' => 'applications/conpherence/constants/ConpherenceUpdateActions.php',
'ConpherenceUpdateController' => 'applications/conpherence/controller/ConpherenceUpdateController.php',
'ConpherenceUpdateThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php',
'ConpherenceViewController' => 'applications/conpherence/controller/ConpherenceViewController.php',
'CountdownEditConduitAPIMethod' => 'applications/countdown/conduit/CountdownEditConduitAPIMethod.php',
'CountdownSearchConduitAPIMethod' => 'applications/countdown/conduit/CountdownSearchConduitAPIMethod.php',
'DarkConsoleController' => 'applications/console/controller/DarkConsoleController.php',
'DarkConsoleCore' => 'applications/console/core/DarkConsoleCore.php',
'DarkConsoleDataController' => 'applications/console/controller/DarkConsoleDataController.php',
'DarkConsoleErrorLogPlugin' => 'applications/console/plugin/DarkConsoleErrorLogPlugin.php',
'DarkConsoleErrorLogPluginAPI' => 'applications/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php',
'DarkConsoleEventPlugin' => 'applications/console/plugin/DarkConsoleEventPlugin.php',
'DarkConsoleEventPluginAPI' => 'applications/console/plugin/event/DarkConsoleEventPluginAPI.php',
'DarkConsolePlugin' => 'applications/console/plugin/DarkConsolePlugin.php',
'DarkConsoleRealtimePlugin' => 'applications/console/plugin/DarkConsoleRealtimePlugin.php',
'DarkConsoleRequestPlugin' => 'applications/console/plugin/DarkConsoleRequestPlugin.php',
'DarkConsoleServicesPlugin' => 'applications/console/plugin/DarkConsoleServicesPlugin.php',
'DarkConsoleStartupPlugin' => 'applications/console/plugin/DarkConsoleStartupPlugin.php',
'DarkConsoleXHProfPlugin' => 'applications/console/plugin/DarkConsoleXHProfPlugin.php',
'DarkConsoleXHProfPluginAPI' => 'applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php',
'DifferentialAction' => 'applications/differential/constants/DifferentialAction.php',
'DifferentialActionEmailCommand' => 'applications/differential/command/DifferentialActionEmailCommand.php',
'DifferentialAdjustmentMapTestCase' => 'applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php',
'DifferentialAffectedPath' => 'applications/differential/storage/DifferentialAffectedPath.php',
'DifferentialAffectedPathEngine' => 'applications/differential/engine/DifferentialAffectedPathEngine.php',
'DifferentialAsanaRepresentationField' => 'applications/differential/customfield/DifferentialAsanaRepresentationField.php',
'DifferentialAuditorsCommitMessageField' => 'applications/differential/field/DifferentialAuditorsCommitMessageField.php',
'DifferentialAuditorsField' => 'applications/differential/customfield/DifferentialAuditorsField.php',
'DifferentialBlameRevisionCommitMessageField' => 'applications/differential/field/DifferentialBlameRevisionCommitMessageField.php',
'DifferentialBlameRevisionField' => 'applications/differential/customfield/DifferentialBlameRevisionField.php',
'DifferentialBlockHeraldAction' => 'applications/differential/herald/DifferentialBlockHeraldAction.php',
'DifferentialBlockingReviewerDatasource' => 'applications/differential/typeahead/DifferentialBlockingReviewerDatasource.php',
'DifferentialBranchField' => 'applications/differential/customfield/DifferentialBranchField.php',
'DifferentialBuildableEngine' => 'applications/differential/harbormaster/DifferentialBuildableEngine.php',
'DifferentialChangeDetailMailView' => 'applications/differential/mail/DifferentialChangeDetailMailView.php',
'DifferentialChangeHeraldFieldGroup' => 'applications/differential/herald/DifferentialChangeHeraldFieldGroup.php',
'DifferentialChangeType' => 'applications/differential/constants/DifferentialChangeType.php',
'DifferentialChangesSinceLastUpdateField' => 'applications/differential/customfield/DifferentialChangesSinceLastUpdateField.php',
'DifferentialChangeset' => 'applications/differential/storage/DifferentialChangeset.php',
'DifferentialChangesetDetailView' => 'applications/differential/view/DifferentialChangesetDetailView.php',
'DifferentialChangesetEngine' => 'applications/differential/engine/DifferentialChangesetEngine.php',
'DifferentialChangesetHTMLRenderer' => 'applications/differential/render/DifferentialChangesetHTMLRenderer.php',
'DifferentialChangesetListController' => 'applications/differential/controller/DifferentialChangesetListController.php',
'DifferentialChangesetListView' => 'applications/differential/view/DifferentialChangesetListView.php',
'DifferentialChangesetOneUpMailRenderer' => 'applications/differential/render/DifferentialChangesetOneUpMailRenderer.php',
'DifferentialChangesetOneUpRenderer' => 'applications/differential/render/DifferentialChangesetOneUpRenderer.php',
'DifferentialChangesetOneUpTestRenderer' => 'applications/differential/render/DifferentialChangesetOneUpTestRenderer.php',
'DifferentialChangesetPHIDType' => 'applications/differential/phid/DifferentialChangesetPHIDType.php',
'DifferentialChangesetParser' => 'applications/differential/parser/DifferentialChangesetParser.php',
'DifferentialChangesetParserTestCase' => 'applications/differential/parser/__tests__/DifferentialChangesetParserTestCase.php',
'DifferentialChangesetQuery' => 'applications/differential/query/DifferentialChangesetQuery.php',
'DifferentialChangesetRenderer' => 'applications/differential/render/DifferentialChangesetRenderer.php',
'DifferentialChangesetSearchConduitAPIMethod' => 'applications/differential/conduit/DifferentialChangesetSearchConduitAPIMethod.php',
'DifferentialChangesetSearchEngine' => 'applications/differential/query/DifferentialChangesetSearchEngine.php',
'DifferentialChangesetTestRenderer' => 'applications/differential/render/DifferentialChangesetTestRenderer.php',
'DifferentialChangesetTwoUpRenderer' => 'applications/differential/render/DifferentialChangesetTwoUpRenderer.php',
'DifferentialChangesetTwoUpTestRenderer' => 'applications/differential/render/DifferentialChangesetTwoUpTestRenderer.php',
'DifferentialChangesetViewController' => 'applications/differential/controller/DifferentialChangesetViewController.php',
'DifferentialCloseConduitAPIMethod' => 'applications/differential/conduit/DifferentialCloseConduitAPIMethod.php',
'DifferentialCommitMessageCustomField' => 'applications/differential/field/DifferentialCommitMessageCustomField.php',
'DifferentialCommitMessageField' => 'applications/differential/field/DifferentialCommitMessageField.php',
'DifferentialCommitMessageFieldTestCase' => 'applications/differential/field/__tests__/DifferentialCommitMessageFieldTestCase.php',
'DifferentialCommitMessageParser' => 'applications/differential/parser/DifferentialCommitMessageParser.php',
'DifferentialCommitMessageParserTestCase' => 'applications/differential/parser/__tests__/DifferentialCommitMessageParserTestCase.php',
'DifferentialCommitsField' => 'applications/differential/customfield/DifferentialCommitsField.php',
'DifferentialCommitsSearchEngineAttachment' => 'applications/differential/engineextension/DifferentialCommitsSearchEngineAttachment.php',
'DifferentialConduitAPIMethod' => 'applications/differential/conduit/DifferentialConduitAPIMethod.php',
'DifferentialConflictsCommitMessageField' => 'applications/differential/field/DifferentialConflictsCommitMessageField.php',
'DifferentialConstantsModule' => 'applications/differential/constants/DifferentialConstantsModule.php',
'DifferentialController' => 'applications/differential/controller/DifferentialController.php',
'DifferentialCoreCustomField' => 'applications/differential/customfield/DifferentialCoreCustomField.php',
'DifferentialCreateCommentConduitAPIMethod' => 'applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php',
'DifferentialCreateDiffConduitAPIMethod' => 'applications/differential/conduit/DifferentialCreateDiffConduitAPIMethod.php',
'DifferentialCreateInlineConduitAPIMethod' => 'applications/differential/conduit/DifferentialCreateInlineConduitAPIMethod.php',
'DifferentialCreateMailReceiver' => 'applications/differential/mail/DifferentialCreateMailReceiver.php',
'DifferentialCreateRawDiffConduitAPIMethod' => 'applications/differential/conduit/DifferentialCreateRawDiffConduitAPIMethod.php',
'DifferentialCreateRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialCreateRevisionConduitAPIMethod.php',
'DifferentialCustomField' => 'applications/differential/customfield/DifferentialCustomField.php',
'DifferentialCustomFieldDependsOnParser' => 'applications/differential/parser/DifferentialCustomFieldDependsOnParser.php',
'DifferentialCustomFieldDependsOnParserTestCase' => 'applications/differential/parser/__tests__/DifferentialCustomFieldDependsOnParserTestCase.php',
'DifferentialCustomFieldNumericIndex' => 'applications/differential/storage/DifferentialCustomFieldNumericIndex.php',
'DifferentialCustomFieldRevertsParser' => 'applications/differential/parser/DifferentialCustomFieldRevertsParser.php',
'DifferentialCustomFieldRevertsParserTestCase' => 'applications/differential/parser/__tests__/DifferentialCustomFieldRevertsParserTestCase.php',
'DifferentialCustomFieldStorage' => 'applications/differential/storage/DifferentialCustomFieldStorage.php',
'DifferentialCustomFieldStringIndex' => 'applications/differential/storage/DifferentialCustomFieldStringIndex.php',
'DifferentialDAO' => 'applications/differential/storage/DifferentialDAO.php',
'DifferentialDefaultViewCapability' => 'applications/differential/capability/DifferentialDefaultViewCapability.php',
'DifferentialDiff' => 'applications/differential/storage/DifferentialDiff.php',
'DifferentialDiffAffectedFilesHeraldField' => 'applications/differential/herald/DifferentialDiffAffectedFilesHeraldField.php',
'DifferentialDiffAuthorHeraldField' => 'applications/differential/herald/DifferentialDiffAuthorHeraldField.php',
'DifferentialDiffAuthorProjectsHeraldField' => 'applications/differential/herald/DifferentialDiffAuthorProjectsHeraldField.php',
'DifferentialDiffContentAddedHeraldField' => 'applications/differential/herald/DifferentialDiffContentAddedHeraldField.php',
'DifferentialDiffContentHeraldField' => 'applications/differential/herald/DifferentialDiffContentHeraldField.php',
'DifferentialDiffContentRemovedHeraldField' => 'applications/differential/herald/DifferentialDiffContentRemovedHeraldField.php',
'DifferentialDiffCreateController' => 'applications/differential/controller/DifferentialDiffCreateController.php',
'DifferentialDiffEditor' => 'applications/differential/editor/DifferentialDiffEditor.php',
'DifferentialDiffExtractionEngine' => 'applications/differential/engine/DifferentialDiffExtractionEngine.php',
'DifferentialDiffHeraldField' => 'applications/differential/herald/DifferentialDiffHeraldField.php',
'DifferentialDiffHeraldFieldGroup' => 'applications/differential/herald/DifferentialDiffHeraldFieldGroup.php',
'DifferentialDiffInlineCommentQuery' => 'applications/differential/query/DifferentialDiffInlineCommentQuery.php',
'DifferentialDiffPHIDType' => 'applications/differential/phid/DifferentialDiffPHIDType.php',
'DifferentialDiffProperty' => 'applications/differential/storage/DifferentialDiffProperty.php',
'DifferentialDiffQuery' => 'applications/differential/query/DifferentialDiffQuery.php',
'DifferentialDiffRepositoryHeraldField' => 'applications/differential/herald/DifferentialDiffRepositoryHeraldField.php',
'DifferentialDiffRepositoryProjectsHeraldField' => 'applications/differential/herald/DifferentialDiffRepositoryProjectsHeraldField.php',
'DifferentialDiffSearchConduitAPIMethod' => 'applications/differential/conduit/DifferentialDiffSearchConduitAPIMethod.php',
'DifferentialDiffSearchEngine' => 'applications/differential/query/DifferentialDiffSearchEngine.php',
'DifferentialDiffTestCase' => 'applications/differential/storage/__tests__/DifferentialDiffTestCase.php',
'DifferentialDiffTransaction' => 'applications/differential/storage/DifferentialDiffTransaction.php',
'DifferentialDiffTransactionQuery' => 'applications/differential/query/DifferentialDiffTransactionQuery.php',
'DifferentialDiffViewController' => 'applications/differential/controller/DifferentialDiffViewController.php',
'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php',
'DifferentialDraftField' => 'applications/differential/customfield/DifferentialDraftField.php',
'DifferentialExactUserFunctionDatasource' => 'applications/differential/typeahead/DifferentialExactUserFunctionDatasource.php',
'DifferentialFieldParseException' => 'applications/differential/exception/DifferentialFieldParseException.php',
'DifferentialFieldValidationException' => 'applications/differential/exception/DifferentialFieldValidationException.php',
'DifferentialFileTreeEngine' => 'applications/differential/engine/DifferentialFileTreeEngine.php',
'DifferentialGetAllDiffsConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetAllDiffsConduitAPIMethod.php',
'DifferentialGetCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetCommitMessageConduitAPIMethod.php',
'DifferentialGetCommitPathsConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetCommitPathsConduitAPIMethod.php',
'DifferentialGetDiffConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetDiffConduitAPIMethod.php',
'DifferentialGetRawDiffConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetRawDiffConduitAPIMethod.php',
'DifferentialGetRevisionCommentsConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetRevisionCommentsConduitAPIMethod.php',
'DifferentialGetRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetRevisionConduitAPIMethod.php',
'DifferentialGetWorkingCopy' => 'applications/differential/DifferentialGetWorkingCopy.php',
'DifferentialGitSVNIDCommitMessageField' => 'applications/differential/field/DifferentialGitSVNIDCommitMessageField.php',
'DifferentialHarbormasterField' => 'applications/differential/customfield/DifferentialHarbormasterField.php',
'DifferentialHeraldStateReasons' => 'applications/differential/herald/DifferentialHeraldStateReasons.php',
'DifferentialHiddenComment' => 'applications/differential/storage/DifferentialHiddenComment.php',
'DifferentialHostField' => 'applications/differential/customfield/DifferentialHostField.php',
'DifferentialHovercardEngineExtension' => 'applications/differential/engineextension/DifferentialHovercardEngineExtension.php',
'DifferentialHunk' => 'applications/differential/storage/DifferentialHunk.php',
'DifferentialHunkParser' => 'applications/differential/parser/DifferentialHunkParser.php',
'DifferentialHunkParserTestCase' => 'applications/differential/parser/__tests__/DifferentialHunkParserTestCase.php',
'DifferentialHunkQuery' => 'applications/differential/query/DifferentialHunkQuery.php',
'DifferentialHunkTestCase' => 'applications/differential/storage/__tests__/DifferentialHunkTestCase.php',
'DifferentialInlineComment' => 'applications/differential/storage/DifferentialInlineComment.php',
'DifferentialInlineCommentEditController' => 'applications/differential/controller/DifferentialInlineCommentEditController.php',
'DifferentialInlineCommentMailView' => 'applications/differential/mail/DifferentialInlineCommentMailView.php',
'DifferentialJIRAIssuesCommitMessageField' => 'applications/differential/field/DifferentialJIRAIssuesCommitMessageField.php',
'DifferentialJIRAIssuesField' => 'applications/differential/customfield/DifferentialJIRAIssuesField.php',
'DifferentialLegacyQuery' => 'applications/differential/constants/DifferentialLegacyQuery.php',
'DifferentialLineAdjustmentMap' => 'applications/differential/parser/DifferentialLineAdjustmentMap.php',
'DifferentialLintField' => 'applications/differential/customfield/DifferentialLintField.php',
'DifferentialLintStatus' => 'applications/differential/constants/DifferentialLintStatus.php',
'DifferentialLocalCommitsView' => 'applications/differential/view/DifferentialLocalCommitsView.php',
'DifferentialMailEngineExtension' => 'applications/differential/engineextension/DifferentialMailEngineExtension.php',
'DifferentialMailView' => 'applications/differential/mail/DifferentialMailView.php',
'DifferentialManiphestTasksField' => 'applications/differential/customfield/DifferentialManiphestTasksField.php',
'DifferentialNoReviewersDatasource' => 'applications/differential/typeahead/DifferentialNoReviewersDatasource.php',
'DifferentialParseCacheGarbageCollector' => 'applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php',
'DifferentialParseCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php',
'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php',
'DifferentialPathField' => 'applications/differential/customfield/DifferentialPathField.php',
'DifferentialProjectReviewersField' => 'applications/differential/customfield/DifferentialProjectReviewersField.php',
'DifferentialQueryConduitAPIMethod' => 'applications/differential/conduit/DifferentialQueryConduitAPIMethod.php',
'DifferentialQueryDiffsConduitAPIMethod' => 'applications/differential/conduit/DifferentialQueryDiffsConduitAPIMethod.php',
'DifferentialRawDiffRenderer' => 'applications/differential/render/DifferentialRawDiffRenderer.php',
- 'DifferentialReleephRequestFieldSpecification' => 'applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php',
'DifferentialRemarkupRule' => 'applications/differential/remarkup/DifferentialRemarkupRule.php',
'DifferentialReplyHandler' => 'applications/differential/mail/DifferentialReplyHandler.php',
'DifferentialRepositoryField' => 'applications/differential/customfield/DifferentialRepositoryField.php',
'DifferentialRepositoryLookup' => 'applications/differential/query/DifferentialRepositoryLookup.php',
'DifferentialRequiredSignaturesField' => 'applications/differential/customfield/DifferentialRequiredSignaturesField.php',
'DifferentialResponsibleDatasource' => 'applications/differential/typeahead/DifferentialResponsibleDatasource.php',
'DifferentialResponsibleUserDatasource' => 'applications/differential/typeahead/DifferentialResponsibleUserDatasource.php',
'DifferentialResponsibleViewerFunctionDatasource' => 'applications/differential/typeahead/DifferentialResponsibleViewerFunctionDatasource.php',
'DifferentialRevertPlanCommitMessageField' => 'applications/differential/field/DifferentialRevertPlanCommitMessageField.php',
'DifferentialRevertPlanField' => 'applications/differential/customfield/DifferentialRevertPlanField.php',
'DifferentialReviewedByCommitMessageField' => 'applications/differential/field/DifferentialReviewedByCommitMessageField.php',
'DifferentialReviewer' => 'applications/differential/storage/DifferentialReviewer.php',
'DifferentialReviewerDatasource' => 'applications/differential/typeahead/DifferentialReviewerDatasource.php',
'DifferentialReviewerForRevisionEdgeType' => 'applications/differential/edge/DifferentialReviewerForRevisionEdgeType.php',
'DifferentialReviewerFunctionDatasource' => 'applications/differential/typeahead/DifferentialReviewerFunctionDatasource.php',
'DifferentialReviewerStatus' => 'applications/differential/constants/DifferentialReviewerStatus.php',
'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php',
'DifferentialReviewersAddBlockingSelfHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingSelfHeraldAction.php',
'DifferentialReviewersAddReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php',
'DifferentialReviewersAddSelfHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddSelfHeraldAction.php',
'DifferentialReviewersCommitMessageField' => 'applications/differential/field/DifferentialReviewersCommitMessageField.php',
'DifferentialReviewersField' => 'applications/differential/customfield/DifferentialReviewersField.php',
'DifferentialReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersHeraldAction.php',
'DifferentialReviewersSearchEngineAttachment' => 'applications/differential/engineextension/DifferentialReviewersSearchEngineAttachment.php',
'DifferentialReviewersView' => 'applications/differential/view/DifferentialReviewersView.php',
'DifferentialRevision' => 'applications/differential/storage/DifferentialRevision.php',
'DifferentialRevisionAbandonTransaction' => 'applications/differential/xaction/DifferentialRevisionAbandonTransaction.php',
'DifferentialRevisionAcceptTransaction' => 'applications/differential/xaction/DifferentialRevisionAcceptTransaction.php',
'DifferentialRevisionActionTransaction' => 'applications/differential/xaction/DifferentialRevisionActionTransaction.php',
'DifferentialRevisionAffectedFilesHeraldField' => 'applications/differential/herald/DifferentialRevisionAffectedFilesHeraldField.php',
'DifferentialRevisionAffectedPathsController' => 'applications/differential/controller/DifferentialRevisionAffectedPathsController.php',
'DifferentialRevisionAuthorHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorHeraldField.php',
'DifferentialRevisionAuthorPackagesHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorPackagesHeraldField.php',
'DifferentialRevisionAuthorProjectsHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorProjectsHeraldField.php',
'DifferentialRevisionAuthorTransaction' => 'applications/differential/xaction/DifferentialRevisionAuthorTransaction.php',
'DifferentialRevisionBuildableTransaction' => 'applications/differential/xaction/DifferentialRevisionBuildableTransaction.php',
'DifferentialRevisionCloseDetailsController' => 'applications/differential/controller/DifferentialRevisionCloseDetailsController.php',
'DifferentialRevisionCloseTransaction' => 'applications/differential/xaction/DifferentialRevisionCloseTransaction.php',
'DifferentialRevisionClosedStatusDatasource' => 'applications/differential/typeahead/DifferentialRevisionClosedStatusDatasource.php',
'DifferentialRevisionCommandeerTransaction' => 'applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php',
'DifferentialRevisionContentAddedHeraldField' => 'applications/differential/herald/DifferentialRevisionContentAddedHeraldField.php',
'DifferentialRevisionContentHeraldField' => 'applications/differential/herald/DifferentialRevisionContentHeraldField.php',
'DifferentialRevisionContentRemovedHeraldField' => 'applications/differential/herald/DifferentialRevisionContentRemovedHeraldField.php',
'DifferentialRevisionControlSystem' => 'applications/differential/constants/DifferentialRevisionControlSystem.php',
'DifferentialRevisionDependedOnByRevisionEdgeType' => 'applications/differential/edge/DifferentialRevisionDependedOnByRevisionEdgeType.php',
'DifferentialRevisionDependsOnRevisionEdgeType' => 'applications/differential/edge/DifferentialRevisionDependsOnRevisionEdgeType.php',
'DifferentialRevisionDraftEngine' => 'applications/differential/engine/DifferentialRevisionDraftEngine.php',
'DifferentialRevisionEditConduitAPIMethod' => 'applications/differential/conduit/DifferentialRevisionEditConduitAPIMethod.php',
'DifferentialRevisionEditController' => 'applications/differential/controller/DifferentialRevisionEditController.php',
'DifferentialRevisionEditEngine' => 'applications/differential/editor/DifferentialRevisionEditEngine.php',
'DifferentialRevisionFerretEngine' => 'applications/differential/search/DifferentialRevisionFerretEngine.php',
'DifferentialRevisionFulltextEngine' => 'applications/differential/search/DifferentialRevisionFulltextEngine.php',
'DifferentialRevisionGraph' => 'infrastructure/graph/DifferentialRevisionGraph.php',
'DifferentialRevisionHasChildRelationship' => 'applications/differential/relationships/DifferentialRevisionHasChildRelationship.php',
'DifferentialRevisionHasCommitEdgeType' => 'applications/differential/edge/DifferentialRevisionHasCommitEdgeType.php',
'DifferentialRevisionHasCommitRelationship' => 'applications/differential/relationships/DifferentialRevisionHasCommitRelationship.php',
'DifferentialRevisionHasParentRelationship' => 'applications/differential/relationships/DifferentialRevisionHasParentRelationship.php',
'DifferentialRevisionHasReviewerEdgeType' => 'applications/differential/edge/DifferentialRevisionHasReviewerEdgeType.php',
'DifferentialRevisionHasTaskEdgeType' => 'applications/differential/edge/DifferentialRevisionHasTaskEdgeType.php',
'DifferentialRevisionHasTaskRelationship' => 'applications/differential/relationships/DifferentialRevisionHasTaskRelationship.php',
'DifferentialRevisionHeraldField' => 'applications/differential/herald/DifferentialRevisionHeraldField.php',
'DifferentialRevisionHeraldFieldGroup' => 'applications/differential/herald/DifferentialRevisionHeraldFieldGroup.php',
'DifferentialRevisionHoldDraftTransaction' => 'applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php',
'DifferentialRevisionIDCommitMessageField' => 'applications/differential/field/DifferentialRevisionIDCommitMessageField.php',
'DifferentialRevisionInlineTransaction' => 'applications/differential/xaction/DifferentialRevisionInlineTransaction.php',
'DifferentialRevisionInlinesController' => 'applications/differential/controller/DifferentialRevisionInlinesController.php',
'DifferentialRevisionJIRAIssueURIsHeraldField' => 'applications/differential/herald/DifferentialRevisionJIRAIssueURIsHeraldField.php',
'DifferentialRevisionListController' => 'applications/differential/controller/DifferentialRevisionListController.php',
'DifferentialRevisionListView' => 'applications/differential/view/DifferentialRevisionListView.php',
'DifferentialRevisionMailReceiver' => 'applications/differential/mail/DifferentialRevisionMailReceiver.php',
'DifferentialRevisionOpenStatusDatasource' => 'applications/differential/typeahead/DifferentialRevisionOpenStatusDatasource.php',
'DifferentialRevisionOperationController' => 'applications/differential/controller/DifferentialRevisionOperationController.php',
'DifferentialRevisionPHIDType' => 'applications/differential/phid/DifferentialRevisionPHIDType.php',
'DifferentialRevisionPackageHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageHeraldField.php',
'DifferentialRevisionPackageOwnerHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageOwnerHeraldField.php',
'DifferentialRevisionPlanChangesTransaction' => 'applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php',
'DifferentialRevisionQuery' => 'applications/differential/query/DifferentialRevisionQuery.php',
'DifferentialRevisionReclaimTransaction' => 'applications/differential/xaction/DifferentialRevisionReclaimTransaction.php',
'DifferentialRevisionRejectTransaction' => 'applications/differential/xaction/DifferentialRevisionRejectTransaction.php',
'DifferentialRevisionRelationship' => 'applications/differential/relationships/DifferentialRevisionRelationship.php',
'DifferentialRevisionRelationshipSource' => 'applications/search/relationship/DifferentialRevisionRelationshipSource.php',
'DifferentialRevisionReopenTransaction' => 'applications/differential/xaction/DifferentialRevisionReopenTransaction.php',
'DifferentialRevisionRepositoryHeraldField' => 'applications/differential/herald/DifferentialRevisionRepositoryHeraldField.php',
'DifferentialRevisionRepositoryProjectsHeraldField' => 'applications/differential/herald/DifferentialRevisionRepositoryProjectsHeraldField.php',
'DifferentialRevisionRepositoryTransaction' => 'applications/differential/xaction/DifferentialRevisionRepositoryTransaction.php',
'DifferentialRevisionRequestReviewTransaction' => 'applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php',
'DifferentialRevisionRequiredActionResultBucket' => 'applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php',
'DifferentialRevisionResignTransaction' => 'applications/differential/xaction/DifferentialRevisionResignTransaction.php',
'DifferentialRevisionResultBucket' => 'applications/differential/query/DifferentialRevisionResultBucket.php',
'DifferentialRevisionReviewTransaction' => 'applications/differential/xaction/DifferentialRevisionReviewTransaction.php',
'DifferentialRevisionReviewersHeraldField' => 'applications/differential/herald/DifferentialRevisionReviewersHeraldField.php',
'DifferentialRevisionReviewersTransaction' => 'applications/differential/xaction/DifferentialRevisionReviewersTransaction.php',
'DifferentialRevisionSearchConduitAPIMethod' => 'applications/differential/conduit/DifferentialRevisionSearchConduitAPIMethod.php',
'DifferentialRevisionSearchEngine' => 'applications/differential/query/DifferentialRevisionSearchEngine.php',
'DifferentialRevisionStatus' => 'applications/differential/constants/DifferentialRevisionStatus.php',
'DifferentialRevisionStatusDatasource' => 'applications/differential/typeahead/DifferentialRevisionStatusDatasource.php',
'DifferentialRevisionStatusFunctionDatasource' => 'applications/differential/typeahead/DifferentialRevisionStatusFunctionDatasource.php',
'DifferentialRevisionStatusHeraldField' => 'applications/differential/herald/DifferentialRevisionStatusHeraldField.php',
'DifferentialRevisionStatusTransaction' => 'applications/differential/xaction/DifferentialRevisionStatusTransaction.php',
'DifferentialRevisionSummaryHeraldField' => 'applications/differential/herald/DifferentialRevisionSummaryHeraldField.php',
'DifferentialRevisionSummaryTransaction' => 'applications/differential/xaction/DifferentialRevisionSummaryTransaction.php',
'DifferentialRevisionTestPlanHeraldField' => 'applications/differential/herald/DifferentialRevisionTestPlanHeraldField.php',
'DifferentialRevisionTestPlanTransaction' => 'applications/differential/xaction/DifferentialRevisionTestPlanTransaction.php',
'DifferentialRevisionTimelineEngine' => 'applications/differential/engine/DifferentialRevisionTimelineEngine.php',
'DifferentialRevisionTitleHeraldField' => 'applications/differential/herald/DifferentialRevisionTitleHeraldField.php',
'DifferentialRevisionTitleTransaction' => 'applications/differential/xaction/DifferentialRevisionTitleTransaction.php',
'DifferentialRevisionTransactionType' => 'applications/differential/xaction/DifferentialRevisionTransactionType.php',
'DifferentialRevisionUpdateHistoryView' => 'applications/differential/view/DifferentialRevisionUpdateHistoryView.php',
'DifferentialRevisionUpdateTransaction' => 'applications/differential/xaction/DifferentialRevisionUpdateTransaction.php',
'DifferentialRevisionViewController' => 'applications/differential/controller/DifferentialRevisionViewController.php',
'DifferentialRevisionVoidTransaction' => 'applications/differential/xaction/DifferentialRevisionVoidTransaction.php',
'DifferentialRevisionWrongBuildsTransaction' => 'applications/differential/xaction/DifferentialRevisionWrongBuildsTransaction.php',
'DifferentialRevisionWrongStateTransaction' => 'applications/differential/xaction/DifferentialRevisionWrongStateTransaction.php',
'DifferentialSchemaSpec' => 'applications/differential/storage/DifferentialSchemaSpec.php',
'DifferentialSetDiffPropertyConduitAPIMethod' => 'applications/differential/conduit/DifferentialSetDiffPropertyConduitAPIMethod.php',
'DifferentialStoredCustomField' => 'applications/differential/customfield/DifferentialStoredCustomField.php',
'DifferentialSubscribersCommitMessageField' => 'applications/differential/field/DifferentialSubscribersCommitMessageField.php',
'DifferentialSummaryCommitMessageField' => 'applications/differential/field/DifferentialSummaryCommitMessageField.php',
'DifferentialSummaryField' => 'applications/differential/customfield/DifferentialSummaryField.php',
'DifferentialTabReplacementTestCase' => 'applications/differential/parser/__tests__/DifferentialTabReplacementTestCase.php',
'DifferentialTagsCommitMessageField' => 'applications/differential/field/DifferentialTagsCommitMessageField.php',
'DifferentialTasksCommitMessageField' => 'applications/differential/field/DifferentialTasksCommitMessageField.php',
'DifferentialTestPlanCommitMessageField' => 'applications/differential/field/DifferentialTestPlanCommitMessageField.php',
'DifferentialTestPlanField' => 'applications/differential/customfield/DifferentialTestPlanField.php',
'DifferentialTitleCommitMessageField' => 'applications/differential/field/DifferentialTitleCommitMessageField.php',
'DifferentialTransaction' => 'applications/differential/storage/DifferentialTransaction.php',
'DifferentialTransactionComment' => 'applications/differential/storage/DifferentialTransactionComment.php',
'DifferentialTransactionEditor' => 'applications/differential/editor/DifferentialTransactionEditor.php',
'DifferentialTransactionQuery' => 'applications/differential/query/DifferentialTransactionQuery.php',
'DifferentialTransactionView' => 'applications/differential/view/DifferentialTransactionView.php',
'DifferentialUnitField' => 'applications/differential/customfield/DifferentialUnitField.php',
'DifferentialUnitStatus' => 'applications/differential/constants/DifferentialUnitStatus.php',
'DifferentialUnitTestResult' => 'applications/differential/constants/DifferentialUnitTestResult.php',
'DifferentialUpdateRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php',
'DifferentialViewState' => 'applications/differential/storage/DifferentialViewState.php',
'DifferentialViewStateGarbageCollector' => 'applications/differential/garbagecollector/DifferentialViewStateGarbageCollector.php',
'DifferentialViewStateQuery' => 'applications/differential/query/DifferentialViewStateQuery.php',
'DiffusionAuditorDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorDatasource.php',
'DiffusionAuditorFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorFunctionDatasource.php',
'DiffusionAuditorsAddAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php',
'DiffusionAuditorsAddSelfHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddSelfHeraldAction.php',
'DiffusionAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsHeraldAction.php',
'DiffusionAuditorsSearchEngineAttachment' => 'applications/diffusion/engineextension/DiffusionAuditorsSearchEngineAttachment.php',
'DiffusionBlameConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBlameConduitAPIMethod.php',
'DiffusionBlameController' => 'applications/diffusion/controller/DiffusionBlameController.php',
'DiffusionBlameQuery' => 'applications/diffusion/query/blame/DiffusionBlameQuery.php',
'DiffusionBlockHeraldAction' => 'applications/diffusion/herald/DiffusionBlockHeraldAction.php',
'DiffusionBranchListView' => 'applications/diffusion/view/DiffusionBranchListView.php',
'DiffusionBranchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php',
'DiffusionBranchTableController' => 'applications/diffusion/controller/DiffusionBranchTableController.php',
'DiffusionBrowseController' => 'applications/diffusion/controller/DiffusionBrowseController.php',
'DiffusionBrowseQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php',
'DiffusionBrowseResultSet' => 'applications/diffusion/data/DiffusionBrowseResultSet.php',
'DiffusionBrowseTableView' => 'applications/diffusion/view/DiffusionBrowseTableView.php',
'DiffusionBuildableEngine' => 'applications/diffusion/harbormaster/DiffusionBuildableEngine.php',
'DiffusionCacheEngineExtension' => 'applications/diffusion/engineextension/DiffusionCacheEngineExtension.php',
'DiffusionCachedResolveRefsQuery' => 'applications/diffusion/query/DiffusionCachedResolveRefsQuery.php',
'DiffusionChangeController' => 'applications/diffusion/controller/DiffusionChangeController.php',
'DiffusionChangeHeraldFieldGroup' => 'applications/diffusion/herald/DiffusionChangeHeraldFieldGroup.php',
'DiffusionCloneController' => 'applications/diffusion/controller/DiffusionCloneController.php',
'DiffusionCloneURIView' => 'applications/diffusion/view/DiffusionCloneURIView.php',
'DiffusionCommandEngine' => 'applications/diffusion/protocol/DiffusionCommandEngine.php',
'DiffusionCommandEngineTestCase' => 'applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php',
'DiffusionCommitAcceptTransaction' => 'applications/diffusion/xaction/DiffusionCommitAcceptTransaction.php',
'DiffusionCommitActionTransaction' => 'applications/diffusion/xaction/DiffusionCommitActionTransaction.php',
'DiffusionCommitAffectedFilesHeraldField' => 'applications/diffusion/herald/DiffusionCommitAffectedFilesHeraldField.php',
'DiffusionCommitAuditStatus' => 'applications/diffusion/DiffusionCommitAuditStatus.php',
'DiffusionCommitAuditTransaction' => 'applications/diffusion/xaction/DiffusionCommitAuditTransaction.php',
'DiffusionCommitAuditorsHeraldField' => 'applications/diffusion/herald/DiffusionCommitAuditorsHeraldField.php',
'DiffusionCommitAuditorsTransaction' => 'applications/diffusion/xaction/DiffusionCommitAuditorsTransaction.php',
'DiffusionCommitAuthorHeraldField' => 'applications/diffusion/herald/DiffusionCommitAuthorHeraldField.php',
'DiffusionCommitAuthorPackagesHeraldField' => 'applications/diffusion/herald/DiffusionCommitAuthorPackagesHeraldField.php',
'DiffusionCommitAuthorProjectsHeraldField' => 'applications/diffusion/herald/DiffusionCommitAuthorProjectsHeraldField.php',
'DiffusionCommitAutocloseHeraldField' => 'applications/diffusion/herald/DiffusionCommitAutocloseHeraldField.php',
'DiffusionCommitBranchesController' => 'applications/diffusion/controller/DiffusionCommitBranchesController.php',
'DiffusionCommitBranchesHeraldField' => 'applications/diffusion/herald/DiffusionCommitBranchesHeraldField.php',
'DiffusionCommitBuildableTransaction' => 'applications/diffusion/xaction/DiffusionCommitBuildableTransaction.php',
'DiffusionCommitCommitterHeraldField' => 'applications/diffusion/herald/DiffusionCommitCommitterHeraldField.php',
'DiffusionCommitCommitterPackagesHeraldField' => 'applications/diffusion/herald/DiffusionCommitCommitterPackagesHeraldField.php',
'DiffusionCommitCommitterProjectsHeraldField' => 'applications/diffusion/herald/DiffusionCommitCommitterProjectsHeraldField.php',
'DiffusionCommitConcernTransaction' => 'applications/diffusion/xaction/DiffusionCommitConcernTransaction.php',
'DiffusionCommitController' => 'applications/diffusion/controller/DiffusionCommitController.php',
'DiffusionCommitDiffContentAddedHeraldField' => 'applications/diffusion/herald/DiffusionCommitDiffContentAddedHeraldField.php',
'DiffusionCommitDiffContentHeraldField' => 'applications/diffusion/herald/DiffusionCommitDiffContentHeraldField.php',
'DiffusionCommitDiffContentRemovedHeraldField' => 'applications/diffusion/herald/DiffusionCommitDiffContentRemovedHeraldField.php',
'DiffusionCommitDiffEnormousHeraldField' => 'applications/diffusion/herald/DiffusionCommitDiffEnormousHeraldField.php',
'DiffusionCommitDraftEngine' => 'applications/diffusion/engine/DiffusionCommitDraftEngine.php',
'DiffusionCommitEditConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionCommitEditConduitAPIMethod.php',
'DiffusionCommitEditController' => 'applications/diffusion/controller/DiffusionCommitEditController.php',
'DiffusionCommitEditEngine' => 'applications/diffusion/editor/DiffusionCommitEditEngine.php',
'DiffusionCommitFerretEngine' => 'applications/repository/search/DiffusionCommitFerretEngine.php',
'DiffusionCommitFulltextEngine' => 'applications/repository/search/DiffusionCommitFulltextEngine.php',
'DiffusionCommitGraphView' => 'applications/diffusion/view/DiffusionCommitGraphView.php',
'DiffusionCommitHasPackageEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasPackageEdgeType.php',
'DiffusionCommitHasRevisionEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasRevisionEdgeType.php',
'DiffusionCommitHasRevisionRelationship' => 'applications/diffusion/relationships/DiffusionCommitHasRevisionRelationship.php',
'DiffusionCommitHasTaskEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasTaskEdgeType.php',
'DiffusionCommitHasTaskRelationship' => 'applications/diffusion/relationships/DiffusionCommitHasTaskRelationship.php',
'DiffusionCommitHash' => 'applications/diffusion/data/DiffusionCommitHash.php',
'DiffusionCommitHeraldField' => 'applications/diffusion/herald/DiffusionCommitHeraldField.php',
'DiffusionCommitHeraldFieldGroup' => 'applications/diffusion/herald/DiffusionCommitHeraldFieldGroup.php',
'DiffusionCommitHintQuery' => 'applications/diffusion/query/DiffusionCommitHintQuery.php',
'DiffusionCommitHookEngine' => 'applications/diffusion/engine/DiffusionCommitHookEngine.php',
'DiffusionCommitHookRejectException' => 'applications/diffusion/exception/DiffusionCommitHookRejectException.php',
'DiffusionCommitListController' => 'applications/diffusion/controller/DiffusionCommitListController.php',
'DiffusionCommitMergeHeraldField' => 'applications/diffusion/herald/DiffusionCommitMergeHeraldField.php',
'DiffusionCommitMessageHeraldField' => 'applications/diffusion/herald/DiffusionCommitMessageHeraldField.php',
'DiffusionCommitPackageAuditHeraldField' => 'applications/diffusion/herald/DiffusionCommitPackageAuditHeraldField.php',
'DiffusionCommitPackageHeraldField' => 'applications/diffusion/herald/DiffusionCommitPackageHeraldField.php',
'DiffusionCommitPackageOwnerHeraldField' => 'applications/diffusion/herald/DiffusionCommitPackageOwnerHeraldField.php',
'DiffusionCommitParentsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionCommitParentsQueryConduitAPIMethod.php',
'DiffusionCommitQuery' => 'applications/diffusion/query/DiffusionCommitQuery.php',
'DiffusionCommitRef' => 'applications/diffusion/data/DiffusionCommitRef.php',
'DiffusionCommitRelationship' => 'applications/diffusion/relationships/DiffusionCommitRelationship.php',
'DiffusionCommitRelationshipSource' => 'applications/search/relationship/DiffusionCommitRelationshipSource.php',
'DiffusionCommitRemarkupRule' => 'applications/diffusion/remarkup/DiffusionCommitRemarkupRule.php',
'DiffusionCommitRemarkupRuleTestCase' => 'applications/diffusion/remarkup/__tests__/DiffusionCommitRemarkupRuleTestCase.php',
'DiffusionCommitRepositoryHeraldField' => 'applications/diffusion/herald/DiffusionCommitRepositoryHeraldField.php',
'DiffusionCommitRepositoryProjectsHeraldField' => 'applications/diffusion/herald/DiffusionCommitRepositoryProjectsHeraldField.php',
'DiffusionCommitRequiredActionResultBucket' => 'applications/diffusion/query/DiffusionCommitRequiredActionResultBucket.php',
'DiffusionCommitResignTransaction' => 'applications/diffusion/xaction/DiffusionCommitResignTransaction.php',
'DiffusionCommitResultBucket' => 'applications/diffusion/query/DiffusionCommitResultBucket.php',
'DiffusionCommitRevertedByCommitEdgeType' => 'applications/diffusion/edge/DiffusionCommitRevertedByCommitEdgeType.php',
'DiffusionCommitRevertsCommitEdgeType' => 'applications/diffusion/edge/DiffusionCommitRevertsCommitEdgeType.php',
'DiffusionCommitReviewerHeraldField' => 'applications/diffusion/herald/DiffusionCommitReviewerHeraldField.php',
'DiffusionCommitRevisionAcceptedHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionAcceptedHeraldField.php',
'DiffusionCommitRevisionAcceptingReviewersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionAcceptingReviewersHeraldField.php',
'DiffusionCommitRevisionHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionHeraldField.php',
'DiffusionCommitRevisionQuery' => 'applications/diffusion/query/DiffusionCommitRevisionQuery.php',
'DiffusionCommitRevisionReviewersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php',
'DiffusionCommitRevisionSubscribersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionSubscribersHeraldField.php',
'DiffusionCommitSearchConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionCommitSearchConduitAPIMethod.php',
'DiffusionCommitStateTransaction' => 'applications/diffusion/xaction/DiffusionCommitStateTransaction.php',
'DiffusionCommitTagsController' => 'applications/diffusion/controller/DiffusionCommitTagsController.php',
'DiffusionCommitTimelineEngine' => 'applications/diffusion/engine/DiffusionCommitTimelineEngine.php',
'DiffusionCommitTransactionType' => 'applications/diffusion/xaction/DiffusionCommitTransactionType.php',
'DiffusionCommitVerifyTransaction' => 'applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php',
'DiffusionCommitWrongBuildsHeraldField' => 'applications/diffusion/herald/DiffusionCommitWrongBuildsHeraldField.php',
'DiffusionCompareController' => 'applications/diffusion/controller/DiffusionCompareController.php',
'DiffusionConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionConduitAPIMethod.php',
'DiffusionController' => 'applications/diffusion/controller/DiffusionController.php',
'DiffusionCreateRepositoriesCapability' => 'applications/diffusion/capability/DiffusionCreateRepositoriesCapability.php',
'DiffusionDaemonLockException' => 'applications/diffusion/exception/DiffusionDaemonLockException.php',
'DiffusionDatasourceEngineExtension' => 'applications/diffusion/engineextension/DiffusionDatasourceEngineExtension.php',
'DiffusionDefaultEditCapability' => 'applications/diffusion/capability/DiffusionDefaultEditCapability.php',
'DiffusionDefaultPushCapability' => 'applications/diffusion/capability/DiffusionDefaultPushCapability.php',
'DiffusionDefaultViewCapability' => 'applications/diffusion/capability/DiffusionDefaultViewCapability.php',
'DiffusionDiffController' => 'applications/diffusion/controller/DiffusionDiffController.php',
'DiffusionDiffInlineCommentQuery' => 'applications/diffusion/query/DiffusionDiffInlineCommentQuery.php',
'DiffusionDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionDiffQueryConduitAPIMethod.php',
'DiffusionDocumentController' => 'applications/diffusion/controller/DiffusionDocumentController.php',
'DiffusionDocumentRenderingEngine' => 'applications/diffusion/document/DiffusionDocumentRenderingEngine.php',
'DiffusionDoorkeeperCommitFeedStoryPublisher' => 'applications/diffusion/doorkeeper/DiffusionDoorkeeperCommitFeedStoryPublisher.php',
'DiffusionEmptyResultView' => 'applications/diffusion/view/DiffusionEmptyResultView.php',
'DiffusionExistsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionExistsQueryConduitAPIMethod.php',
'DiffusionExternalController' => 'applications/diffusion/controller/DiffusionExternalController.php',
'DiffusionExternalSymbolQuery' => 'applications/diffusion/symbol/DiffusionExternalSymbolQuery.php',
'DiffusionExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionExternalSymbolsSource.php',
'DiffusionFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionFileContentQuery.php',
'DiffusionFileContentQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php',
'DiffusionFileFutureQuery' => 'applications/diffusion/query/DiffusionFileFutureQuery.php',
'DiffusionFindSymbolsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionFindSymbolsConduitAPIMethod.php',
'DiffusionGetLintMessagesConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionGetLintMessagesConduitAPIMethod.php',
'DiffusionGetRecentCommitsByPathConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionGetRecentCommitsByPathConduitAPIMethod.php',
'DiffusionGitBlameQuery' => 'applications/diffusion/query/blame/DiffusionGitBlameQuery.php',
'DiffusionGitBranch' => 'applications/diffusion/data/DiffusionGitBranch.php',
'DiffusionGitBranchTestCase' => 'applications/diffusion/data/__tests__/DiffusionGitBranchTestCase.php',
'DiffusionGitCommandEngine' => 'applications/diffusion/protocol/DiffusionGitCommandEngine.php',
'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php',
'DiffusionGitLFSAuthenticateWorkflow' => 'applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php',
'DiffusionGitLFSResponse' => 'applications/diffusion/response/DiffusionGitLFSResponse.php',
'DiffusionGitLFSTemporaryTokenType' => 'applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php',
'DiffusionGitRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php',
'DiffusionGitReceivePackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php',
'DiffusionGitRequest' => 'applications/diffusion/request/DiffusionGitRequest.php',
'DiffusionGitResponse' => 'applications/diffusion/response/DiffusionGitResponse.php',
'DiffusionGitSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitSSHWorkflow.php',
'DiffusionGitUploadPackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php',
'DiffusionGitUploadPackWireProtocol' => 'applications/diffusion/protocol/DiffusionGitUploadPackWireProtocol.php',
'DiffusionGitWireProtocol' => 'applications/diffusion/protocol/DiffusionGitWireProtocol.php',
'DiffusionGitWireProtocolCapabilities' => 'applications/diffusion/protocol/DiffusionGitWireProtocolCapabilities.php',
'DiffusionGitWireProtocolRef' => 'applications/diffusion/protocol/DiffusionGitWireProtocolRef.php',
'DiffusionGitWireProtocolRefList' => 'applications/diffusion/protocol/DiffusionGitWireProtocolRefList.php',
'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php',
'DiffusionHistoryQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php',
'DiffusionHovercardEngineExtension' => 'applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php',
'DiffusionIdentityAssigneeDatasource' => 'applications/diffusion/typeahead/DiffusionIdentityAssigneeDatasource.php',
'DiffusionIdentityAssigneeEditField' => 'applications/diffusion/editfield/DiffusionIdentityAssigneeEditField.php',
'DiffusionIdentityAssigneeSearchField' => 'applications/diffusion/searchfield/DiffusionIdentityAssigneeSearchField.php',
'DiffusionIdentityEditController' => 'applications/diffusion/controller/DiffusionIdentityEditController.php',
'DiffusionIdentityListController' => 'applications/diffusion/controller/DiffusionIdentityListController.php',
'DiffusionIdentityUnassignedDatasource' => 'applications/diffusion/typeahead/DiffusionIdentityUnassignedDatasource.php',
'DiffusionIdentityViewController' => 'applications/diffusion/controller/DiffusionIdentityViewController.php',
'DiffusionInlineCommentController' => 'applications/diffusion/controller/DiffusionInlineCommentController.php',
'DiffusionInternalAncestorsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalAncestorsConduitAPIMethod.php',
'DiffusionInternalCommitSearchConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalCommitSearchConduitAPIMethod.php',
'DiffusionInternalCommitSearchEngine' => 'applications/audit/query/DiffusionInternalCommitSearchEngine.php',
'DiffusionInternalGitRawDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalGitRawDiffQueryConduitAPIMethod.php',
'DiffusionLastModifiedController' => 'applications/diffusion/controller/DiffusionLastModifiedController.php',
'DiffusionLastModifiedQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLastModifiedQueryConduitAPIMethod.php',
'DiffusionLintController' => 'applications/diffusion/controller/DiffusionLintController.php',
'DiffusionLintCountQuery' => 'applications/diffusion/query/DiffusionLintCountQuery.php',
'DiffusionLintSaveRunner' => 'applications/diffusion/DiffusionLintSaveRunner.php',
'DiffusionLocalRepositoryFilter' => 'applications/diffusion/data/DiffusionLocalRepositoryFilter.php',
'DiffusionLogController' => 'applications/diffusion/controller/DiffusionLogController.php',
'DiffusionLookSoonConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php',
'DiffusionLowLevelCommitFieldsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php',
'DiffusionLowLevelCommitQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelCommitQuery.php',
'DiffusionLowLevelFilesizeQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelFilesizeQuery.php',
'DiffusionLowLevelGitRefQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelGitRefQuery.php',
'DiffusionLowLevelMercurialBranchesQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialBranchesQuery.php',
'DiffusionLowLevelMercurialPathsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialPathsQuery.php',
'DiffusionLowLevelParentsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelParentsQuery.php',
'DiffusionLowLevelQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelQuery.php',
'DiffusionLowLevelResolveRefsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php',
'DiffusionMercurialBlameQuery' => 'applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php',
'DiffusionMercurialCommandEngine' => 'applications/diffusion/protocol/DiffusionMercurialCommandEngine.php',
'DiffusionMercurialCommandEngineTests' => 'applications/diffusion/protocol/__tests__/DiffusionMercurialCommandEngineTests.php',
'DiffusionMercurialFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php',
'DiffusionMercurialFlagInjectionException' => 'applications/diffusion/exception/DiffusionMercurialFlagInjectionException.php',
'DiffusionMercurialRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionMercurialRawDiffQuery.php',
'DiffusionMercurialRequest' => 'applications/diffusion/request/DiffusionMercurialRequest.php',
'DiffusionMercurialResponse' => 'applications/diffusion/response/DiffusionMercurialResponse.php',
'DiffusionMercurialSSHWorkflow' => 'applications/diffusion/ssh/DiffusionMercurialSSHWorkflow.php',
'DiffusionMercurialServeSSHWorkflow' => 'applications/diffusion/ssh/DiffusionMercurialServeSSHWorkflow.php',
'DiffusionMercurialWireClientSSHProtocolChannel' => 'applications/diffusion/ssh/DiffusionMercurialWireClientSSHProtocolChannel.php',
'DiffusionMercurialWireProtocol' => 'applications/diffusion/protocol/DiffusionMercurialWireProtocol.php',
'DiffusionMercurialWireProtocolTests' => 'applications/diffusion/protocol/__tests__/DiffusionMercurialWireProtocolTests.php',
'DiffusionMercurialWireSSHTestCase' => 'applications/diffusion/ssh/__tests__/DiffusionMercurialWireSSHTestCase.php',
'DiffusionMergedCommitsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionMergedCommitsQueryConduitAPIMethod.php',
'DiffusionPathChange' => 'applications/diffusion/data/DiffusionPathChange.php',
'DiffusionPathChangeQuery' => 'applications/diffusion/query/pathchange/DiffusionPathChangeQuery.php',
'DiffusionPathCompleteController' => 'applications/diffusion/controller/DiffusionPathCompleteController.php',
'DiffusionPathIDQuery' => 'applications/diffusion/query/pathid/DiffusionPathIDQuery.php',
'DiffusionPathQuery' => 'applications/diffusion/query/DiffusionPathQuery.php',
'DiffusionPathQueryTestCase' => 'applications/diffusion/query/pathid/__tests__/DiffusionPathQueryTestCase.php',
'DiffusionPathTreeController' => 'applications/diffusion/controller/DiffusionPathTreeController.php',
'DiffusionPathValidateController' => 'applications/diffusion/controller/DiffusionPathValidateController.php',
'DiffusionPatternSearchView' => 'applications/diffusion/view/DiffusionPatternSearchView.php',
'DiffusionPhpExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionPhpExternalSymbolsSource.php',
'DiffusionPreCommitContentAffectedFilesHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentAffectedFilesHeraldField.php',
'DiffusionPreCommitContentAuthorHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentAuthorHeraldField.php',
'DiffusionPreCommitContentAuthorPackagesHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentAuthorPackagesHeraldField.php',
'DiffusionPreCommitContentAuthorProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentAuthorProjectsHeraldField.php',
'DiffusionPreCommitContentAuthorRawHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentAuthorRawHeraldField.php',
'DiffusionPreCommitContentBranchesHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentBranchesHeraldField.php',
'DiffusionPreCommitContentCommitterHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentCommitterHeraldField.php',
'DiffusionPreCommitContentCommitterPackagesHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentCommitterPackagesHeraldField.php',
'DiffusionPreCommitContentCommitterProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentCommitterProjectsHeraldField.php',
'DiffusionPreCommitContentCommitterRawHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentCommitterRawHeraldField.php',
'DiffusionPreCommitContentDiffContentAddedHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentDiffContentAddedHeraldField.php',
'DiffusionPreCommitContentDiffContentHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentDiffContentHeraldField.php',
'DiffusionPreCommitContentDiffContentRemovedHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentDiffContentRemovedHeraldField.php',
'DiffusionPreCommitContentDiffEnormousHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentDiffEnormousHeraldField.php',
'DiffusionPreCommitContentHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentHeraldField.php',
'DiffusionPreCommitContentMergeHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentMergeHeraldField.php',
'DiffusionPreCommitContentMessageHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentMessageHeraldField.php',
'DiffusionPreCommitContentPackageHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPackageHeraldField.php',
'DiffusionPreCommitContentPackageOwnerHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPackageOwnerHeraldField.php',
'DiffusionPreCommitContentPusherHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPusherHeraldField.php',
'DiffusionPreCommitContentPusherIsCommitterHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPusherIsCommitterHeraldField.php',
'DiffusionPreCommitContentPusherProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPusherProjectsHeraldField.php',
'DiffusionPreCommitContentRepositoryHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRepositoryHeraldField.php',
'DiffusionPreCommitContentRepositoryProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRepositoryProjectsHeraldField.php',
'DiffusionPreCommitContentRevisionAcceptedHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptedHeraldField.php',
'DiffusionPreCommitContentRevisionAcceptingReviewersHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptingReviewersHeraldField.php',
'DiffusionPreCommitContentRevisionHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionHeraldField.php',
'DiffusionPreCommitContentRevisionReviewersHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionReviewersHeraldField.php',
'DiffusionPreCommitContentRevisionSubscribersHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionSubscribersHeraldField.php',
'DiffusionPreCommitContentWrongBuildsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentWrongBuildsHeraldField.php',
'DiffusionPreCommitRefChangeHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefChangeHeraldField.php',
'DiffusionPreCommitRefHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefHeraldField.php',
'DiffusionPreCommitRefHeraldFieldGroup' => 'applications/diffusion/herald/DiffusionPreCommitRefHeraldFieldGroup.php',
'DiffusionPreCommitRefNameHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefNameHeraldField.php',
'DiffusionPreCommitRefPusherHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefPusherHeraldField.php',
'DiffusionPreCommitRefPusherProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefPusherProjectsHeraldField.php',
'DiffusionPreCommitRefRepositoryHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefRepositoryHeraldField.php',
'DiffusionPreCommitRefRepositoryProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefRepositoryProjectsHeraldField.php',
'DiffusionPreCommitRefTypeHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefTypeHeraldField.php',
'DiffusionPreCommitUsesGitLFSHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitUsesGitLFSHeraldField.php',
'DiffusionPullEventGarbageCollector' => 'applications/diffusion/garbagecollector/DiffusionPullEventGarbageCollector.php',
'DiffusionPullLogListController' => 'applications/diffusion/controller/DiffusionPullLogListController.php',
'DiffusionPullLogListView' => 'applications/diffusion/view/DiffusionPullLogListView.php',
'DiffusionPullLogSearchEngine' => 'applications/diffusion/query/DiffusionPullLogSearchEngine.php',
'DiffusionPushCapability' => 'applications/diffusion/capability/DiffusionPushCapability.php',
'DiffusionPushEventViewController' => 'applications/diffusion/controller/DiffusionPushEventViewController.php',
'DiffusionPushLogListController' => 'applications/diffusion/controller/DiffusionPushLogListController.php',
'DiffusionPushLogListView' => 'applications/diffusion/view/DiffusionPushLogListView.php',
'DiffusionPythonExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionPythonExternalSymbolsSource.php',
'DiffusionQuery' => 'applications/diffusion/query/DiffusionQuery.php',
'DiffusionQueryCommitsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php',
'DiffusionQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php',
'DiffusionQueryPathsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php',
'DiffusionRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionRawDiffQuery.php',
'DiffusionRawDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRawDiffQueryConduitAPIMethod.php',
'DiffusionReadmeView' => 'applications/diffusion/view/DiffusionReadmeView.php',
'DiffusionRefDatasource' => 'applications/diffusion/typeahead/DiffusionRefDatasource.php',
'DiffusionRefNotFoundException' => 'applications/diffusion/exception/DiffusionRefNotFoundException.php',
'DiffusionRefTableController' => 'applications/diffusion/controller/DiffusionRefTableController.php',
'DiffusionRefsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRefsQueryConduitAPIMethod.php',
'DiffusionRenameHistoryQuery' => 'applications/diffusion/query/DiffusionRenameHistoryQuery.php',
'DiffusionRepositoryAutomationManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryAutomationManagementPanel.php',
'DiffusionRepositoryBasicsManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php',
'DiffusionRepositoryBranchesManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php',
'DiffusionRepositoryByIDRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRepositoryByIDRemarkupRule.php',
'DiffusionRepositoryClusterEngine' => 'applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php',
'DiffusionRepositoryClusterEngineLogInterface' => 'applications/diffusion/protocol/DiffusionRepositoryClusterEngineLogInterface.php',
'DiffusionRepositoryController' => 'applications/diffusion/controller/DiffusionRepositoryController.php',
'DiffusionRepositoryDatasource' => 'applications/diffusion/typeahead/DiffusionRepositoryDatasource.php',
'DiffusionRepositoryDefaultController' => 'applications/diffusion/controller/DiffusionRepositoryDefaultController.php',
'DiffusionRepositoryEditActivateController' => 'applications/diffusion/controller/DiffusionRepositoryEditActivateController.php',
'DiffusionRepositoryEditConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRepositoryEditConduitAPIMethod.php',
'DiffusionRepositoryEditController' => 'applications/diffusion/controller/DiffusionRepositoryEditController.php',
'DiffusionRepositoryEditDangerousController' => 'applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php',
'DiffusionRepositoryEditDeleteController' => 'applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php',
'DiffusionRepositoryEditEngine' => 'applications/diffusion/editor/DiffusionRepositoryEditEngine.php',
'DiffusionRepositoryEditEnormousController' => 'applications/diffusion/controller/DiffusionRepositoryEditEnormousController.php',
'DiffusionRepositoryEditPublishingController' => 'applications/diffusion/controller/DiffusionRepositoryEditPublishingController.php',
'DiffusionRepositoryEditUpdateController' => 'applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php',
'DiffusionRepositoryFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionRepositoryFunctionDatasource.php',
'DiffusionRepositoryHistoryManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryHistoryManagementPanel.php',
'DiffusionRepositoryIdentityDestructionEngineExtension' => 'applications/diffusion/identity/DiffusionRepositoryIdentityDestructionEngineExtension.php',
'DiffusionRepositoryIdentityEditor' => 'applications/diffusion/editor/DiffusionRepositoryIdentityEditor.php',
'DiffusionRepositoryIdentityEngine' => 'applications/diffusion/identity/DiffusionRepositoryIdentityEngine.php',
'DiffusionRepositoryIdentitySearchEngine' => 'applications/diffusion/query/DiffusionRepositoryIdentitySearchEngine.php',
'DiffusionRepositoryLimitsManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryLimitsManagementPanel.php',
'DiffusionRepositoryListController' => 'applications/diffusion/controller/DiffusionRepositoryListController.php',
'DiffusionRepositoryManageController' => 'applications/diffusion/controller/DiffusionRepositoryManageController.php',
'DiffusionRepositoryManagePanelsController' => 'applications/diffusion/controller/DiffusionRepositoryManagePanelsController.php',
'DiffusionRepositoryManagementBuildsPanelGroup' => 'applications/diffusion/management/DiffusionRepositoryManagementBuildsPanelGroup.php',
'DiffusionRepositoryManagementIntegrationsPanelGroup' => 'applications/diffusion/management/DiffusionRepositoryManagementIntegrationsPanelGroup.php',
'DiffusionRepositoryManagementMainPanelGroup' => 'applications/diffusion/management/DiffusionRepositoryManagementMainPanelGroup.php',
'DiffusionRepositoryManagementOtherPanelGroup' => 'applications/diffusion/management/DiffusionRepositoryManagementOtherPanelGroup.php',
'DiffusionRepositoryManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryManagementPanel.php',
'DiffusionRepositoryManagementPanelGroup' => 'applications/diffusion/management/DiffusionRepositoryManagementPanelGroup.php',
'DiffusionRepositoryMetricsSearchEngineAttachment' => 'applications/diffusion/engineextension/DiffusionRepositoryMetricsSearchEngineAttachment.php',
'DiffusionRepositoryPath' => 'applications/diffusion/data/DiffusionRepositoryPath.php',
'DiffusionRepositoryPoliciesManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryPoliciesManagementPanel.php',
'DiffusionRepositoryProfilePictureController' => 'applications/diffusion/controller/DiffusionRepositoryProfilePictureController.php',
'DiffusionRepositoryRef' => 'applications/diffusion/data/DiffusionRepositoryRef.php',
'DiffusionRepositoryRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRepositoryRemarkupRule.php',
'DiffusionRepositorySearchConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRepositorySearchConduitAPIMethod.php',
'DiffusionRepositoryStagingManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryStagingManagementPanel.php',
'DiffusionRepositoryStorageManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryStorageManagementPanel.php',
'DiffusionRepositorySubversionManagementPanel' => 'applications/diffusion/management/DiffusionRepositorySubversionManagementPanel.php',
'DiffusionRepositorySymbolsManagementPanel' => 'applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php',
'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php',
'DiffusionRepositoryTestAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php',
'DiffusionRepositoryURICredentialController' => 'applications/diffusion/controller/DiffusionRepositoryURICredentialController.php',
'DiffusionRepositoryURIDisableController' => 'applications/diffusion/controller/DiffusionRepositoryURIDisableController.php',
'DiffusionRepositoryURIEditController' => 'applications/diffusion/controller/DiffusionRepositoryURIEditController.php',
'DiffusionRepositoryURIViewController' => 'applications/diffusion/controller/DiffusionRepositoryURIViewController.php',
'DiffusionRepositoryURIsIndexEngineExtension' => 'applications/diffusion/engineextension/DiffusionRepositoryURIsIndexEngineExtension.php',
'DiffusionRepositoryURIsManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php',
'DiffusionRepositoryURIsSearchEngineAttachment' => 'applications/diffusion/engineextension/DiffusionRepositoryURIsSearchEngineAttachment.php',
'DiffusionRequest' => 'applications/diffusion/request/DiffusionRequest.php',
'DiffusionResolveRefsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionResolveRefsConduitAPIMethod.php',
'DiffusionResolveUserQuery' => 'applications/diffusion/query/DiffusionResolveUserQuery.php',
'DiffusionSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSSHWorkflow.php',
'DiffusionSearchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php',
'DiffusionServeController' => 'applications/diffusion/controller/DiffusionServeController.php',
'DiffusionServiceRef' => 'applications/diffusion/ref/DiffusionServiceRef.php',
'DiffusionSetPasswordSettingsPanel' => 'applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php',
'DiffusionSetupException' => 'applications/diffusion/exception/DiffusionSetupException.php',
'DiffusionSourceHyperlinkEngineExtension' => 'applications/diffusion/engineextension/DiffusionSourceHyperlinkEngineExtension.php',
'DiffusionSourceLinkRemarkupRule' => 'applications/diffusion/remarkup/DiffusionSourceLinkRemarkupRule.php',
'DiffusionSourceLinkView' => 'applications/diffusion/view/DiffusionSourceLinkView.php',
'DiffusionSubversionCommandEngine' => 'applications/diffusion/protocol/DiffusionSubversionCommandEngine.php',
'DiffusionSubversionSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSubversionSSHWorkflow.php',
'DiffusionSubversionServeSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSubversionServeSSHWorkflow.php',
'DiffusionSubversionWireProtocol' => 'applications/diffusion/protocol/DiffusionSubversionWireProtocol.php',
'DiffusionSubversionWireProtocolTestCase' => 'applications/diffusion/protocol/__tests__/DiffusionSubversionWireProtocolTestCase.php',
'DiffusionSvnBlameQuery' => 'applications/diffusion/query/blame/DiffusionSvnBlameQuery.php',
'DiffusionSvnFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionSvnFileContentQuery.php',
'DiffusionSvnRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionSvnRawDiffQuery.php',
'DiffusionSvnRequest' => 'applications/diffusion/request/DiffusionSvnRequest.php',
'DiffusionSymbolController' => 'applications/diffusion/controller/DiffusionSymbolController.php',
'DiffusionSymbolDatasource' => 'applications/diffusion/typeahead/DiffusionSymbolDatasource.php',
'DiffusionSymbolQuery' => 'applications/diffusion/query/DiffusionSymbolQuery.php',
'DiffusionSyncLogListController' => 'applications/diffusion/controller/DiffusionSyncLogListController.php',
'DiffusionSyncLogListView' => 'applications/diffusion/view/DiffusionSyncLogListView.php',
'DiffusionSyncLogSearchEngine' => 'applications/diffusion/query/DiffusionSyncLogSearchEngine.php',
'DiffusionTagListController' => 'applications/diffusion/controller/DiffusionTagListController.php',
'DiffusionTagListView' => 'applications/diffusion/view/DiffusionTagListView.php',
'DiffusionTaggedRepositoriesFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionTaggedRepositoriesFunctionDatasource.php',
'DiffusionTagsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionTagsQueryConduitAPIMethod.php',
'DiffusionURIEditConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionURIEditConduitAPIMethod.php',
'DiffusionURIEditEngine' => 'applications/diffusion/editor/DiffusionURIEditEngine.php',
'DiffusionURIEditor' => 'applications/diffusion/editor/DiffusionURIEditor.php',
'DiffusionURITestCase' => 'applications/diffusion/request/__tests__/DiffusionURITestCase.php',
'DiffusionUpdateCoverageConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionUpdateCoverageConduitAPIMethod.php',
'DiffusionUpdateObjectAfterCommitWorker' => 'applications/diffusion/worker/DiffusionUpdateObjectAfterCommitWorker.php',
'DiffusionView' => 'applications/diffusion/view/DiffusionView.php',
'DivinerArticleAtomizer' => 'applications/diviner/atomizer/DivinerArticleAtomizer.php',
'DivinerAtom' => 'applications/diviner/atom/DivinerAtom.php',
'DivinerAtomCache' => 'applications/diviner/cache/DivinerAtomCache.php',
'DivinerAtomController' => 'applications/diviner/controller/DivinerAtomController.php',
'DivinerAtomListController' => 'applications/diviner/controller/DivinerAtomListController.php',
'DivinerAtomPHIDType' => 'applications/diviner/phid/DivinerAtomPHIDType.php',
'DivinerAtomQuery' => 'applications/diviner/query/DivinerAtomQuery.php',
'DivinerAtomRef' => 'applications/diviner/atom/DivinerAtomRef.php',
'DivinerAtomSearchEngine' => 'applications/diviner/query/DivinerAtomSearchEngine.php',
'DivinerAtomizeWorkflow' => 'applications/diviner/workflow/DivinerAtomizeWorkflow.php',
'DivinerAtomizer' => 'applications/diviner/atomizer/DivinerAtomizer.php',
'DivinerBookController' => 'applications/diviner/controller/DivinerBookController.php',
'DivinerBookDatasource' => 'applications/diviner/typeahead/DivinerBookDatasource.php',
'DivinerBookEditController' => 'applications/diviner/controller/DivinerBookEditController.php',
'DivinerBookItemView' => 'applications/diviner/view/DivinerBookItemView.php',
'DivinerBookPHIDType' => 'applications/diviner/phid/DivinerBookPHIDType.php',
'DivinerBookQuery' => 'applications/diviner/query/DivinerBookQuery.php',
'DivinerController' => 'applications/diviner/controller/DivinerController.php',
'DivinerDAO' => 'applications/diviner/storage/DivinerDAO.php',
'DivinerDefaultEditCapability' => 'applications/diviner/capability/DivinerDefaultEditCapability.php',
'DivinerDefaultRenderer' => 'applications/diviner/renderer/DivinerDefaultRenderer.php',
'DivinerDefaultViewCapability' => 'applications/diviner/capability/DivinerDefaultViewCapability.php',
'DivinerDiskCache' => 'applications/diviner/cache/DivinerDiskCache.php',
'DivinerFileAtomizer' => 'applications/diviner/atomizer/DivinerFileAtomizer.php',
'DivinerFindController' => 'applications/diviner/controller/DivinerFindController.php',
'DivinerGenerateWorkflow' => 'applications/diviner/workflow/DivinerGenerateWorkflow.php',
'DivinerLiveAtom' => 'applications/diviner/storage/DivinerLiveAtom.php',
'DivinerLiveBook' => 'applications/diviner/storage/DivinerLiveBook.php',
'DivinerLiveBookEditor' => 'applications/diviner/editor/DivinerLiveBookEditor.php',
'DivinerLiveBookFulltextEngine' => 'applications/diviner/search/DivinerLiveBookFulltextEngine.php',
'DivinerLiveBookTransaction' => 'applications/diviner/storage/DivinerLiveBookTransaction.php',
'DivinerLiveBookTransactionQuery' => 'applications/diviner/query/DivinerLiveBookTransactionQuery.php',
'DivinerLivePublisher' => 'applications/diviner/publisher/DivinerLivePublisher.php',
'DivinerLiveSymbol' => 'applications/diviner/storage/DivinerLiveSymbol.php',
'DivinerLiveSymbolFulltextEngine' => 'applications/diviner/search/DivinerLiveSymbolFulltextEngine.php',
'DivinerMainController' => 'applications/diviner/controller/DivinerMainController.php',
'DivinerPHPAtomizer' => 'applications/diviner/atomizer/DivinerPHPAtomizer.php',
'DivinerParameterTableView' => 'applications/diviner/view/DivinerParameterTableView.php',
'DivinerPublishCache' => 'applications/diviner/cache/DivinerPublishCache.php',
'DivinerPublisher' => 'applications/diviner/publisher/DivinerPublisher.php',
'DivinerRenderer' => 'applications/diviner/renderer/DivinerRenderer.php',
'DivinerReturnTableView' => 'applications/diviner/view/DivinerReturnTableView.php',
'DivinerSchemaSpec' => 'applications/diviner/storage/DivinerSchemaSpec.php',
'DivinerSectionView' => 'applications/diviner/view/DivinerSectionView.php',
'DivinerStaticPublisher' => 'applications/diviner/publisher/DivinerStaticPublisher.php',
'DivinerSymbolRemarkupRule' => 'applications/diviner/markup/DivinerSymbolRemarkupRule.php',
'DivinerWorkflow' => 'applications/diviner/workflow/DivinerWorkflow.php',
'DoorkeeperAsanaFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperAsanaFeedWorker.php',
'DoorkeeperBridge' => 'applications/doorkeeper/bridge/DoorkeeperBridge.php',
'DoorkeeperBridgeAsana' => 'applications/doorkeeper/bridge/DoorkeeperBridgeAsana.php',
'DoorkeeperBridgeGitHub' => 'applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php',
'DoorkeeperBridgeGitHubIssue' => 'applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php',
'DoorkeeperBridgeGitHubUser' => 'applications/doorkeeper/bridge/DoorkeeperBridgeGitHubUser.php',
'DoorkeeperBridgeJIRA' => 'applications/doorkeeper/bridge/DoorkeeperBridgeJIRA.php',
'DoorkeeperBridgeJIRATestCase' => 'applications/doorkeeper/bridge/__tests__/DoorkeeperBridgeJIRATestCase.php',
'DoorkeeperBridgedObjectCurtainExtension' => 'applications/doorkeeper/engineextension/DoorkeeperBridgedObjectCurtainExtension.php',
'DoorkeeperBridgedObjectInterface' => 'applications/doorkeeper/bridge/DoorkeeperBridgedObjectInterface.php',
'DoorkeeperDAO' => 'applications/doorkeeper/storage/DoorkeeperDAO.php',
'DoorkeeperExternalObject' => 'applications/doorkeeper/storage/DoorkeeperExternalObject.php',
'DoorkeeperExternalObjectPHIDType' => 'applications/doorkeeper/phid/DoorkeeperExternalObjectPHIDType.php',
'DoorkeeperExternalObjectQuery' => 'applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php',
'DoorkeeperFeedStoryPublisher' => 'applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php',
'DoorkeeperFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperFeedWorker.php',
'DoorkeeperHyperlinkEngineExtension' => 'applications/doorkeeper/engineextension/DoorkeeperHyperlinkEngineExtension.php',
'DoorkeeperImportEngine' => 'applications/doorkeeper/engine/DoorkeeperImportEngine.php',
'DoorkeeperJIRAFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperJIRAFeedWorker.php',
'DoorkeeperMissingLinkException' => 'applications/doorkeeper/exception/DoorkeeperMissingLinkException.php',
'DoorkeeperObjectRef' => 'applications/doorkeeper/engine/DoorkeeperObjectRef.php',
'DoorkeeperRemarkupURIInterface' => 'applications/doorkeeper/interface/DoorkeeperRemarkupURIInterface.php',
'DoorkeeperSchemaSpec' => 'applications/doorkeeper/storage/DoorkeeperSchemaSpec.php',
'DoorkeeperTagView' => 'applications/doorkeeper/view/DoorkeeperTagView.php',
'DoorkeeperTagsController' => 'applications/doorkeeper/controller/DoorkeeperTagsController.php',
'DoorkeeperURIRef' => 'applications/doorkeeper/engine/DoorkeeperURIRef.php',
'DrydockAcquiredBrokenResourceException' => 'applications/drydock/exception/DrydockAcquiredBrokenResourceException.php',
'DrydockAlmanacServiceHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php',
'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php',
'DrydockAuthorization' => 'applications/drydock/storage/DrydockAuthorization.php',
'DrydockAuthorizationAuthorizeController' => 'applications/drydock/controller/DrydockAuthorizationAuthorizeController.php',
'DrydockAuthorizationListController' => 'applications/drydock/controller/DrydockAuthorizationListController.php',
'DrydockAuthorizationListView' => 'applications/drydock/view/DrydockAuthorizationListView.php',
'DrydockAuthorizationPHIDType' => 'applications/drydock/phid/DrydockAuthorizationPHIDType.php',
'DrydockAuthorizationQuery' => 'applications/drydock/query/DrydockAuthorizationQuery.php',
'DrydockAuthorizationSearchConduitAPIMethod' => 'applications/drydock/conduit/DrydockAuthorizationSearchConduitAPIMethod.php',
'DrydockAuthorizationSearchEngine' => 'applications/drydock/query/DrydockAuthorizationSearchEngine.php',
'DrydockAuthorizationViewController' => 'applications/drydock/controller/DrydockAuthorizationViewController.php',
'DrydockBlueprint' => 'applications/drydock/storage/DrydockBlueprint.php',
'DrydockBlueprintController' => 'applications/drydock/controller/DrydockBlueprintController.php',
'DrydockBlueprintCoreCustomField' => 'applications/drydock/customfield/DrydockBlueprintCoreCustomField.php',
'DrydockBlueprintCustomField' => 'applications/drydock/customfield/DrydockBlueprintCustomField.php',
'DrydockBlueprintDatasource' => 'applications/drydock/typeahead/DrydockBlueprintDatasource.php',
'DrydockBlueprintDisableController' => 'applications/drydock/controller/DrydockBlueprintDisableController.php',
'DrydockBlueprintDisableTransaction' => 'applications/drydock/xaction/DrydockBlueprintDisableTransaction.php',
'DrydockBlueprintEditConduitAPIMethod' => 'applications/drydock/conduit/DrydockBlueprintEditConduitAPIMethod.php',
'DrydockBlueprintEditController' => 'applications/drydock/controller/DrydockBlueprintEditController.php',
'DrydockBlueprintEditEngine' => 'applications/drydock/editor/DrydockBlueprintEditEngine.php',
'DrydockBlueprintEditor' => 'applications/drydock/editor/DrydockBlueprintEditor.php',
'DrydockBlueprintImplementation' => 'applications/drydock/blueprint/DrydockBlueprintImplementation.php',
'DrydockBlueprintImplementationTestCase' => 'applications/drydock/blueprint/__tests__/DrydockBlueprintImplementationTestCase.php',
'DrydockBlueprintListController' => 'applications/drydock/controller/DrydockBlueprintListController.php',
'DrydockBlueprintNameNgrams' => 'applications/drydock/storage/DrydockBlueprintNameNgrams.php',
'DrydockBlueprintNameTransaction' => 'applications/drydock/xaction/DrydockBlueprintNameTransaction.php',
'DrydockBlueprintPHIDType' => 'applications/drydock/phid/DrydockBlueprintPHIDType.php',
'DrydockBlueprintQuery' => 'applications/drydock/query/DrydockBlueprintQuery.php',
'DrydockBlueprintSearchConduitAPIMethod' => 'applications/drydock/conduit/DrydockBlueprintSearchConduitAPIMethod.php',
'DrydockBlueprintSearchEngine' => 'applications/drydock/query/DrydockBlueprintSearchEngine.php',
'DrydockBlueprintTransaction' => 'applications/drydock/storage/DrydockBlueprintTransaction.php',
'DrydockBlueprintTransactionQuery' => 'applications/drydock/query/DrydockBlueprintTransactionQuery.php',
'DrydockBlueprintTransactionType' => 'applications/drydock/xaction/DrydockBlueprintTransactionType.php',
'DrydockBlueprintTypeTransaction' => 'applications/drydock/xaction/DrydockBlueprintTypeTransaction.php',
'DrydockBlueprintViewController' => 'applications/drydock/controller/DrydockBlueprintViewController.php',
'DrydockCommand' => 'applications/drydock/storage/DrydockCommand.php',
'DrydockCommandError' => 'applications/drydock/exception/DrydockCommandError.php',
'DrydockCommandInterface' => 'applications/drydock/interface/command/DrydockCommandInterface.php',
'DrydockCommandQuery' => 'applications/drydock/query/DrydockCommandQuery.php',
'DrydockConsoleController' => 'applications/drydock/controller/DrydockConsoleController.php',
'DrydockController' => 'applications/drydock/controller/DrydockController.php',
'DrydockCreateBlueprintsCapability' => 'applications/drydock/capability/DrydockCreateBlueprintsCapability.php',
'DrydockDAO' => 'applications/drydock/storage/DrydockDAO.php',
'DrydockDefaultEditCapability' => 'applications/drydock/capability/DrydockDefaultEditCapability.php',
'DrydockDefaultViewCapability' => 'applications/drydock/capability/DrydockDefaultViewCapability.php',
'DrydockFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockFilesystemInterface.php',
'DrydockInterface' => 'applications/drydock/interface/DrydockInterface.php',
'DrydockLandRepositoryOperation' => 'applications/drydock/operation/DrydockLandRepositoryOperation.php',
'DrydockLease' => 'applications/drydock/storage/DrydockLease.php',
'DrydockLeaseAcquiredLogType' => 'applications/drydock/logtype/DrydockLeaseAcquiredLogType.php',
'DrydockLeaseActivatedLogType' => 'applications/drydock/logtype/DrydockLeaseActivatedLogType.php',
'DrydockLeaseActivationFailureLogType' => 'applications/drydock/logtype/DrydockLeaseActivationFailureLogType.php',
'DrydockLeaseActivationYieldLogType' => 'applications/drydock/logtype/DrydockLeaseActivationYieldLogType.php',
'DrydockLeaseAllocationFailureLogType' => 'applications/drydock/logtype/DrydockLeaseAllocationFailureLogType.php',
'DrydockLeaseController' => 'applications/drydock/controller/DrydockLeaseController.php',
'DrydockLeaseDatasource' => 'applications/drydock/typeahead/DrydockLeaseDatasource.php',
'DrydockLeaseDestroyedLogType' => 'applications/drydock/logtype/DrydockLeaseDestroyedLogType.php',
'DrydockLeaseListController' => 'applications/drydock/controller/DrydockLeaseListController.php',
'DrydockLeaseListView' => 'applications/drydock/view/DrydockLeaseListView.php',
'DrydockLeaseNoAuthorizationsLogType' => 'applications/drydock/logtype/DrydockLeaseNoAuthorizationsLogType.php',
'DrydockLeaseNoBlueprintsLogType' => 'applications/drydock/logtype/DrydockLeaseNoBlueprintsLogType.php',
'DrydockLeasePHIDType' => 'applications/drydock/phid/DrydockLeasePHIDType.php',
'DrydockLeaseQuery' => 'applications/drydock/query/DrydockLeaseQuery.php',
'DrydockLeaseQueuedLogType' => 'applications/drydock/logtype/DrydockLeaseQueuedLogType.php',
'DrydockLeaseReacquireLogType' => 'applications/drydock/logtype/DrydockLeaseReacquireLogType.php',
'DrydockLeaseReclaimLogType' => 'applications/drydock/logtype/DrydockLeaseReclaimLogType.php',
'DrydockLeaseReleaseController' => 'applications/drydock/controller/DrydockLeaseReleaseController.php',
'DrydockLeaseReleasedLogType' => 'applications/drydock/logtype/DrydockLeaseReleasedLogType.php',
'DrydockLeaseSearchConduitAPIMethod' => 'applications/drydock/conduit/DrydockLeaseSearchConduitAPIMethod.php',
'DrydockLeaseSearchEngine' => 'applications/drydock/query/DrydockLeaseSearchEngine.php',
'DrydockLeaseStatus' => 'applications/drydock/constants/DrydockLeaseStatus.php',
'DrydockLeaseUpdateWorker' => 'applications/drydock/worker/DrydockLeaseUpdateWorker.php',
'DrydockLeaseViewController' => 'applications/drydock/controller/DrydockLeaseViewController.php',
+ 'DrydockLeaseWaitingForActivationLogType' => 'applications/drydock/logtype/DrydockLeaseWaitingForActivationLogType.php',
+ 'DrydockLeaseWaitingForReclamationLogType' => 'applications/drydock/logtype/DrydockLeaseWaitingForReclamationLogType.php',
'DrydockLeaseWaitingForResourcesLogType' => 'applications/drydock/logtype/DrydockLeaseWaitingForResourcesLogType.php',
'DrydockLog' => 'applications/drydock/storage/DrydockLog.php',
'DrydockLogController' => 'applications/drydock/controller/DrydockLogController.php',
'DrydockLogGarbageCollector' => 'applications/drydock/garbagecollector/DrydockLogGarbageCollector.php',
'DrydockLogListController' => 'applications/drydock/controller/DrydockLogListController.php',
'DrydockLogListView' => 'applications/drydock/view/DrydockLogListView.php',
'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php',
'DrydockLogSearchEngine' => 'applications/drydock/query/DrydockLogSearchEngine.php',
'DrydockLogType' => 'applications/drydock/logtype/DrydockLogType.php',
'DrydockManagementCommandWorkflow' => 'applications/drydock/management/DrydockManagementCommandWorkflow.php',
'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php',
'DrydockManagementReclaimWorkflow' => 'applications/drydock/management/DrydockManagementReclaimWorkflow.php',
'DrydockManagementReleaseLeaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php',
'DrydockManagementReleaseResourceWorkflow' => 'applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php',
'DrydockManagementUpdateLeaseWorkflow' => 'applications/drydock/management/DrydockManagementUpdateLeaseWorkflow.php',
'DrydockManagementUpdateResourceWorkflow' => 'applications/drydock/management/DrydockManagementUpdateResourceWorkflow.php',
'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php',
'DrydockObjectAuthorizationView' => 'applications/drydock/view/DrydockObjectAuthorizationView.php',
'DrydockOperationWorkLogType' => 'applications/drydock/logtype/DrydockOperationWorkLogType.php',
'DrydockQuery' => 'applications/drydock/query/DrydockQuery.php',
'DrydockRepositoryOperation' => 'applications/drydock/storage/DrydockRepositoryOperation.php',
'DrydockRepositoryOperationController' => 'applications/drydock/controller/DrydockRepositoryOperationController.php',
'DrydockRepositoryOperationDismissController' => 'applications/drydock/controller/DrydockRepositoryOperationDismissController.php',
'DrydockRepositoryOperationListController' => 'applications/drydock/controller/DrydockRepositoryOperationListController.php',
'DrydockRepositoryOperationPHIDType' => 'applications/drydock/phid/DrydockRepositoryOperationPHIDType.php',
'DrydockRepositoryOperationQuery' => 'applications/drydock/query/DrydockRepositoryOperationQuery.php',
'DrydockRepositoryOperationSearchEngine' => 'applications/drydock/query/DrydockRepositoryOperationSearchEngine.php',
'DrydockRepositoryOperationStatusController' => 'applications/drydock/controller/DrydockRepositoryOperationStatusController.php',
'DrydockRepositoryOperationStatusView' => 'applications/drydock/view/DrydockRepositoryOperationStatusView.php',
'DrydockRepositoryOperationType' => 'applications/drydock/operation/DrydockRepositoryOperationType.php',
'DrydockRepositoryOperationUpdateWorker' => 'applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php',
'DrydockRepositoryOperationViewController' => 'applications/drydock/controller/DrydockRepositoryOperationViewController.php',
'DrydockResource' => 'applications/drydock/storage/DrydockResource.php',
'DrydockResourceActivationFailureLogType' => 'applications/drydock/logtype/DrydockResourceActivationFailureLogType.php',
'DrydockResourceActivationYieldLogType' => 'applications/drydock/logtype/DrydockResourceActivationYieldLogType.php',
'DrydockResourceAllocationFailureLogType' => 'applications/drydock/logtype/DrydockResourceAllocationFailureLogType.php',
'DrydockResourceController' => 'applications/drydock/controller/DrydockResourceController.php',
'DrydockResourceDatasource' => 'applications/drydock/typeahead/DrydockResourceDatasource.php',
'DrydockResourceListController' => 'applications/drydock/controller/DrydockResourceListController.php',
'DrydockResourceListView' => 'applications/drydock/view/DrydockResourceListView.php',
'DrydockResourceLockException' => 'applications/drydock/exception/DrydockResourceLockException.php',
'DrydockResourcePHIDType' => 'applications/drydock/phid/DrydockResourcePHIDType.php',
'DrydockResourceQuery' => 'applications/drydock/query/DrydockResourceQuery.php',
'DrydockResourceReclaimLogType' => 'applications/drydock/logtype/DrydockResourceReclaimLogType.php',
'DrydockResourceReleaseController' => 'applications/drydock/controller/DrydockResourceReleaseController.php',
'DrydockResourceSearchConduitAPIMethod' => 'applications/drydock/conduit/DrydockResourceSearchConduitAPIMethod.php',
'DrydockResourceSearchEngine' => 'applications/drydock/query/DrydockResourceSearchEngine.php',
'DrydockResourceStatus' => 'applications/drydock/constants/DrydockResourceStatus.php',
'DrydockResourceUpdateWorker' => 'applications/drydock/worker/DrydockResourceUpdateWorker.php',
'DrydockResourceViewController' => 'applications/drydock/controller/DrydockResourceViewController.php',
'DrydockSFTPFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockSFTPFilesystemInterface.php',
'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/DrydockSSHCommandInterface.php',
'DrydockSchemaSpec' => 'applications/drydock/storage/DrydockSchemaSpec.php',
'DrydockSlotLock' => 'applications/drydock/storage/DrydockSlotLock.php',
'DrydockSlotLockException' => 'applications/drydock/exception/DrydockSlotLockException.php',
'DrydockSlotLockFailureLogType' => 'applications/drydock/logtype/DrydockSlotLockFailureLogType.php',
'DrydockTestRepositoryOperation' => 'applications/drydock/operation/DrydockTestRepositoryOperation.php',
'DrydockTextLogType' => 'applications/drydock/logtype/DrydockTextLogType.php',
'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php',
'DrydockWorker' => 'applications/drydock/worker/DrydockWorker.php',
'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php',
'EdgeSearchConduitAPIMethod' => 'infrastructure/edges/conduit/EdgeSearchConduitAPIMethod.php',
'FeedConduitAPIMethod' => 'applications/feed/conduit/FeedConduitAPIMethod.php',
- 'FeedPublishConduitAPIMethod' => 'applications/feed/conduit/FeedPublishConduitAPIMethod.php',
'FeedPublisherHTTPWorker' => 'applications/feed/worker/FeedPublisherHTTPWorker.php',
'FeedPublisherWorker' => 'applications/feed/worker/FeedPublisherWorker.php',
'FeedPushWorker' => 'applications/feed/worker/FeedPushWorker.php',
'FeedQueryConduitAPIMethod' => 'applications/feed/conduit/FeedQueryConduitAPIMethod.php',
'FeedStoryNotificationGarbageCollector' => 'applications/notification/garbagecollector/FeedStoryNotificationGarbageCollector.php',
'FerretConfigurableSearchFunction' => 'applications/search/ferret/function/FerretConfigurableSearchFunction.php',
'FerretSearchFunction' => 'applications/search/ferret/function/FerretSearchFunction.php',
'FileAllocateConduitAPIMethod' => 'applications/files/conduit/FileAllocateConduitAPIMethod.php',
'FileConduitAPIMethod' => 'applications/files/conduit/FileConduitAPIMethod.php',
'FileCreateMailReceiver' => 'applications/files/mail/FileCreateMailReceiver.php',
'FileDeletionWorker' => 'applications/files/worker/FileDeletionWorker.php',
'FileDownloadConduitAPIMethod' => 'applications/files/conduit/FileDownloadConduitAPIMethod.php',
'FileInfoConduitAPIMethod' => 'applications/files/conduit/FileInfoConduitAPIMethod.php',
'FileMailReceiver' => 'applications/files/mail/FileMailReceiver.php',
'FileQueryChunksConduitAPIMethod' => 'applications/files/conduit/FileQueryChunksConduitAPIMethod.php',
'FileReplyHandler' => 'applications/files/mail/FileReplyHandler.php',
'FileTypeIcon' => 'applications/files/constants/FileTypeIcon.php',
'FileUploadChunkConduitAPIMethod' => 'applications/files/conduit/FileUploadChunkConduitAPIMethod.php',
'FileUploadConduitAPIMethod' => 'applications/files/conduit/FileUploadConduitAPIMethod.php',
'FileUploadHashConduitAPIMethod' => 'applications/files/conduit/FileUploadHashConduitAPIMethod.php',
'FilesDefaultViewCapability' => 'applications/files/capability/FilesDefaultViewCapability.php',
'FlagConduitAPIMethod' => 'applications/flag/conduit/FlagConduitAPIMethod.php',
'FlagDeleteConduitAPIMethod' => 'applications/flag/conduit/FlagDeleteConduitAPIMethod.php',
'FlagEditConduitAPIMethod' => 'applications/flag/conduit/FlagEditConduitAPIMethod.php',
'FlagQueryConduitAPIMethod' => 'applications/flag/conduit/FlagQueryConduitAPIMethod.php',
'FuelComponentView' => 'view/fuel/FuelComponentView.php',
'FuelGridCellView' => 'view/fuel/FuelGridCellView.php',
'FuelGridRowView' => 'view/fuel/FuelGridRowView.php',
'FuelGridView' => 'view/fuel/FuelGridView.php',
'FuelHandleListItemView' => 'view/fuel/FuelHandleListItemView.php',
'FuelHandleListView' => 'view/fuel/FuelHandleListView.php',
'FuelMapItemView' => 'view/fuel/FuelMapItemView.php',
'FuelMapView' => 'view/fuel/FuelMapView.php',
'FuelMenuItemView' => 'view/fuel/FuelMenuItemView.php',
'FuelMenuView' => 'view/fuel/FuelMenuView.php',
'FuelView' => 'view/fuel/FuelView.php',
'FundBacker' => 'applications/fund/storage/FundBacker.php',
'FundBackerCart' => 'applications/fund/phortune/FundBackerCart.php',
'FundBackerEditor' => 'applications/fund/editor/FundBackerEditor.php',
'FundBackerListController' => 'applications/fund/controller/FundBackerListController.php',
'FundBackerPHIDType' => 'applications/fund/phid/FundBackerPHIDType.php',
'FundBackerProduct' => 'applications/fund/phortune/FundBackerProduct.php',
'FundBackerQuery' => 'applications/fund/query/FundBackerQuery.php',
'FundBackerRefundTransaction' => 'applications/fund/xaction/FundBackerRefundTransaction.php',
'FundBackerSearchEngine' => 'applications/fund/query/FundBackerSearchEngine.php',
'FundBackerStatusTransaction' => 'applications/fund/xaction/FundBackerStatusTransaction.php',
'FundBackerTransaction' => 'applications/fund/storage/FundBackerTransaction.php',
'FundBackerTransactionQuery' => 'applications/fund/query/FundBackerTransactionQuery.php',
'FundBackerTransactionType' => 'applications/fund/xaction/FundBackerTransactionType.php',
'FundController' => 'applications/fund/controller/FundController.php',
'FundCreateInitiativesCapability' => 'applications/fund/capability/FundCreateInitiativesCapability.php',
'FundDAO' => 'applications/fund/storage/FundDAO.php',
'FundDefaultViewCapability' => 'applications/fund/capability/FundDefaultViewCapability.php',
'FundInitiative' => 'applications/fund/storage/FundInitiative.php',
'FundInitiativeBackController' => 'applications/fund/controller/FundInitiativeBackController.php',
'FundInitiativeBackerTransaction' => 'applications/fund/xaction/FundInitiativeBackerTransaction.php',
'FundInitiativeCloseController' => 'applications/fund/controller/FundInitiativeCloseController.php',
'FundInitiativeDescriptionTransaction' => 'applications/fund/xaction/FundInitiativeDescriptionTransaction.php',
'FundInitiativeEditController' => 'applications/fund/controller/FundInitiativeEditController.php',
'FundInitiativeEditEngine' => 'applications/fund/editor/FundInitiativeEditEngine.php',
'FundInitiativeEditor' => 'applications/fund/editor/FundInitiativeEditor.php',
'FundInitiativeFerretEngine' => 'applications/fund/search/FundInitiativeFerretEngine.php',
'FundInitiativeFulltextEngine' => 'applications/fund/search/FundInitiativeFulltextEngine.php',
'FundInitiativeListController' => 'applications/fund/controller/FundInitiativeListController.php',
'FundInitiativeMerchantTransaction' => 'applications/fund/xaction/FundInitiativeMerchantTransaction.php',
'FundInitiativeNameTransaction' => 'applications/fund/xaction/FundInitiativeNameTransaction.php',
'FundInitiativePHIDType' => 'applications/fund/phid/FundInitiativePHIDType.php',
'FundInitiativeQuery' => 'applications/fund/query/FundInitiativeQuery.php',
'FundInitiativeRefundTransaction' => 'applications/fund/xaction/FundInitiativeRefundTransaction.php',
'FundInitiativeRemarkupRule' => 'applications/fund/remarkup/FundInitiativeRemarkupRule.php',
'FundInitiativeReplyHandler' => 'applications/fund/mail/FundInitiativeReplyHandler.php',
'FundInitiativeRisksTransaction' => 'applications/fund/xaction/FundInitiativeRisksTransaction.php',
'FundInitiativeSearchEngine' => 'applications/fund/query/FundInitiativeSearchEngine.php',
'FundInitiativeStatusTransaction' => 'applications/fund/xaction/FundInitiativeStatusTransaction.php',
'FundInitiativeTransaction' => 'applications/fund/storage/FundInitiativeTransaction.php',
'FundInitiativeTransactionComment' => 'applications/fund/storage/FundInitiativeTransactionComment.php',
'FundInitiativeTransactionQuery' => 'applications/fund/query/FundInitiativeTransactionQuery.php',
'FundInitiativeTransactionType' => 'applications/fund/xaction/FundInitiativeTransactionType.php',
'FundInitiativeViewController' => 'applications/fund/controller/FundInitiativeViewController.php',
'FundSchemaSpec' => 'applications/fund/storage/FundSchemaSpec.php',
'HarbormasterAbortOlderBuildsBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterAbortOlderBuildsBuildStepImplementation.php',
'HarbormasterArcLintBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php',
'HarbormasterArcUnitBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php',
'HarbormasterArtifact' => 'applications/harbormaster/artifact/HarbormasterArtifact.php',
'HarbormasterArtifactSearchConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterArtifactSearchConduitAPIMethod.php',
'HarbormasterArtifactSearchEngine' => 'applications/harbormaster/query/HarbormasterArtifactSearchEngine.php',
'HarbormasterAutotargetsTestCase' => 'applications/harbormaster/__tests__/HarbormasterAutotargetsTestCase.php',
'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php',
'HarbormasterBuildAbortedException' => 'applications/harbormaster/exception/HarbormasterBuildAbortedException.php',
'HarbormasterBuildActionController' => 'applications/harbormaster/controller/HarbormasterBuildActionController.php',
'HarbormasterBuildArcanistAutoplan' => 'applications/harbormaster/autoplan/HarbormasterBuildArcanistAutoplan.php',
'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php',
'HarbormasterBuildArtifactPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildArtifactPHIDType.php',
'HarbormasterBuildArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildArtifactQuery.php',
'HarbormasterBuildAutoplan' => 'applications/harbormaster/autoplan/HarbormasterBuildAutoplan.php',
'HarbormasterBuildDependencyDatasource' => 'applications/harbormaster/typeahead/HarbormasterBuildDependencyDatasource.php',
'HarbormasterBuildEditAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildEditAPIMethod.php',
'HarbormasterBuildEditEngine' => 'applications/harbormaster/editor/HarbormasterBuildEditEngine.php',
'HarbormasterBuildEngine' => 'applications/harbormaster/engine/HarbormasterBuildEngine.php',
'HarbormasterBuildFailureException' => 'applications/harbormaster/exception/HarbormasterBuildFailureException.php',
'HarbormasterBuildGraph' => 'applications/harbormaster/engine/HarbormasterBuildGraph.php',
'HarbormasterBuildInitiatorDatasource' => 'applications/harbormaster/typeahead/HarbormasterBuildInitiatorDatasource.php',
'HarbormasterBuildLintMessage' => 'applications/harbormaster/storage/build/HarbormasterBuildLintMessage.php',
'HarbormasterBuildListController' => 'applications/harbormaster/controller/HarbormasterBuildListController.php',
'HarbormasterBuildLog' => 'applications/harbormaster/storage/build/HarbormasterBuildLog.php',
'HarbormasterBuildLogChunk' => 'applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php',
'HarbormasterBuildLogChunkIterator' => 'applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php',
'HarbormasterBuildLogDownloadController' => 'applications/harbormaster/controller/HarbormasterBuildLogDownloadController.php',
'HarbormasterBuildLogPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildLogPHIDType.php',
'HarbormasterBuildLogQuery' => 'applications/harbormaster/query/HarbormasterBuildLogQuery.php',
'HarbormasterBuildLogRenderController' => 'applications/harbormaster/controller/HarbormasterBuildLogRenderController.php',
'HarbormasterBuildLogSearchConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildLogSearchConduitAPIMethod.php',
'HarbormasterBuildLogSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildLogSearchEngine.php',
'HarbormasterBuildLogTestCase' => 'applications/harbormaster/__tests__/HarbormasterBuildLogTestCase.php',
'HarbormasterBuildLogView' => 'applications/harbormaster/view/HarbormasterBuildLogView.php',
'HarbormasterBuildLogViewController' => 'applications/harbormaster/controller/HarbormasterBuildLogViewController.php',
'HarbormasterBuildMessage' => 'applications/harbormaster/storage/HarbormasterBuildMessage.php',
'HarbormasterBuildMessageAbortTransaction' => 'applications/harbormaster/xaction/build/HarbormasterBuildMessageAbortTransaction.php',
'HarbormasterBuildMessagePauseTransaction' => 'applications/harbormaster/xaction/build/HarbormasterBuildMessagePauseTransaction.php',
'HarbormasterBuildMessageQuery' => 'applications/harbormaster/query/HarbormasterBuildMessageQuery.php',
'HarbormasterBuildMessageRestartTransaction' => 'applications/harbormaster/xaction/build/HarbormasterBuildMessageRestartTransaction.php',
'HarbormasterBuildMessageResumeTransaction' => 'applications/harbormaster/xaction/build/HarbormasterBuildMessageResumeTransaction.php',
'HarbormasterBuildMessageTransaction' => 'applications/harbormaster/xaction/build/HarbormasterBuildMessageTransaction.php',
'HarbormasterBuildPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPHIDType.php',
'HarbormasterBuildPlan' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php',
'HarbormasterBuildPlanBehavior' => 'applications/harbormaster/plan/HarbormasterBuildPlanBehavior.php',
'HarbormasterBuildPlanBehaviorOption' => 'applications/harbormaster/plan/HarbormasterBuildPlanBehaviorOption.php',
'HarbormasterBuildPlanBehaviorTransaction' => 'applications/harbormaster/xaction/plan/HarbormasterBuildPlanBehaviorTransaction.php',
'HarbormasterBuildPlanDatasource' => 'applications/harbormaster/typeahead/HarbormasterBuildPlanDatasource.php',
'HarbormasterBuildPlanDefaultEditCapability' => 'applications/harbormaster/capability/HarbormasterBuildPlanDefaultEditCapability.php',
'HarbormasterBuildPlanDefaultViewCapability' => 'applications/harbormaster/capability/HarbormasterBuildPlanDefaultViewCapability.php',
'HarbormasterBuildPlanEditAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildPlanEditAPIMethod.php',
'HarbormasterBuildPlanEditEngine' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditEngine.php',
'HarbormasterBuildPlanEditor' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditor.php',
'HarbormasterBuildPlanNameNgrams' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanNameNgrams.php',
'HarbormasterBuildPlanNameTransaction' => 'applications/harbormaster/xaction/plan/HarbormasterBuildPlanNameTransaction.php',
'HarbormasterBuildPlanPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPlanPHIDType.php',
'HarbormasterBuildPlanPolicyCodex' => 'applications/harbormaster/codex/HarbormasterBuildPlanPolicyCodex.php',
'HarbormasterBuildPlanQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanQuery.php',
'HarbormasterBuildPlanSearchAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildPlanSearchAPIMethod.php',
'HarbormasterBuildPlanSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php',
'HarbormasterBuildPlanStatusTransaction' => 'applications/harbormaster/xaction/plan/HarbormasterBuildPlanStatusTransaction.php',
'HarbormasterBuildPlanTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php',
'HarbormasterBuildPlanTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php',
'HarbormasterBuildPlanTransactionType' => 'applications/harbormaster/xaction/plan/HarbormasterBuildPlanTransactionType.php',
'HarbormasterBuildQuery' => 'applications/harbormaster/query/HarbormasterBuildQuery.php',
'HarbormasterBuildRequest' => 'applications/harbormaster/engine/HarbormasterBuildRequest.php',
'HarbormasterBuildSearchConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildSearchConduitAPIMethod.php',
'HarbormasterBuildSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildSearchEngine.php',
'HarbormasterBuildStatus' => 'applications/harbormaster/constants/HarbormasterBuildStatus.php',
'HarbormasterBuildStatusDatasource' => 'applications/harbormaster/typeahead/HarbormasterBuildStatusDatasource.php',
'HarbormasterBuildStep' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStep.php',
'HarbormasterBuildStepCoreCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php',
'HarbormasterBuildStepCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCustomField.php',
'HarbormasterBuildStepEditAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildStepEditAPIMethod.php',
'HarbormasterBuildStepEditEngine' => 'applications/harbormaster/editor/HarbormasterBuildStepEditEngine.php',
'HarbormasterBuildStepEditor' => 'applications/harbormaster/editor/HarbormasterBuildStepEditor.php',
'HarbormasterBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php',
'HarbormasterBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterBuildStepImplementation.php',
'HarbormasterBuildStepImplementationTestCase' => 'applications/harbormaster/step/__tests__/HarbormasterBuildStepImplementationTestCase.php',
'HarbormasterBuildStepPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildStepPHIDType.php',
'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php',
'HarbormasterBuildStepSearchAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildStepSearchAPIMethod.php',
'HarbormasterBuildStepSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildStepSearchEngine.php',
'HarbormasterBuildStepTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStepTransaction.php',
'HarbormasterBuildStepTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildStepTransactionQuery.php',
'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php',
'HarbormasterBuildTargetPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildTargetPHIDType.php',
'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php',
'HarbormasterBuildTargetSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildTargetSearchEngine.php',
'HarbormasterBuildTransaction' => 'applications/harbormaster/storage/HarbormasterBuildTransaction.php',
'HarbormasterBuildTransactionEditor' => 'applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php',
'HarbormasterBuildTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildTransactionQuery.php',
'HarbormasterBuildTransactionType' => 'applications/harbormaster/xaction/build/HarbormasterBuildTransactionType.php',
'HarbormasterBuildUnitMessage' => 'applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php',
'HarbormasterBuildUnitMessageQuery' => 'applications/harbormaster/query/HarbormasterBuildUnitMessageQuery.php',
'HarbormasterBuildView' => 'applications/harbormaster/view/HarbormasterBuildView.php',
'HarbormasterBuildViewController' => 'applications/harbormaster/controller/HarbormasterBuildViewController.php',
'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php',
'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php',
'HarbormasterBuildableActionController' => 'applications/harbormaster/controller/HarbormasterBuildableActionController.php',
'HarbormasterBuildableAdapterInterface' => 'applications/harbormaster/herald/HarbormasterBuildableAdapterInterface.php',
'HarbormasterBuildableEditAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildableEditAPIMethod.php',
'HarbormasterBuildableEditEngine' => 'applications/harbormaster/editor/HarbormasterBuildableEditEngine.php',
'HarbormasterBuildableEngine' => 'applications/harbormaster/engine/HarbormasterBuildableEngine.php',
'HarbormasterBuildableInterface' => 'applications/harbormaster/interface/HarbormasterBuildableInterface.php',
'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php',
'HarbormasterBuildableMessageTransaction' => 'applications/harbormaster/xaction/buildable/HarbormasterBuildableMessageTransaction.php',
'HarbormasterBuildablePHIDType' => 'applications/harbormaster/phid/HarbormasterBuildablePHIDType.php',
'HarbormasterBuildableQuery' => 'applications/harbormaster/query/HarbormasterBuildableQuery.php',
'HarbormasterBuildableSearchAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildableSearchAPIMethod.php',
'HarbormasterBuildableSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildableSearchEngine.php',
'HarbormasterBuildableStatus' => 'applications/harbormaster/constants/HarbormasterBuildableStatus.php',
'HarbormasterBuildableTransaction' => 'applications/harbormaster/storage/HarbormasterBuildableTransaction.php',
'HarbormasterBuildableTransactionEditor' => 'applications/harbormaster/editor/HarbormasterBuildableTransactionEditor.php',
'HarbormasterBuildableTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildableTransactionQuery.php',
'HarbormasterBuildableTransactionType' => 'applications/harbormaster/xaction/buildable/HarbormasterBuildableTransactionType.php',
'HarbormasterBuildableViewController' => 'applications/harbormaster/controller/HarbormasterBuildableViewController.php',
'HarbormasterBuildkiteBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php',
'HarbormasterBuildkiteBuildableInterface' => 'applications/harbormaster/interface/HarbormasterBuildkiteBuildableInterface.php',
'HarbormasterBuildkiteHookController' => 'applications/harbormaster/controller/HarbormasterBuildkiteHookController.php',
'HarbormasterBuiltinBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterBuiltinBuildStepGroup.php',
'HarbormasterCircleCIBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php',
'HarbormasterCircleCIBuildableInterface' => 'applications/harbormaster/interface/HarbormasterCircleCIBuildableInterface.php',
'HarbormasterCircleCIHookController' => 'applications/harbormaster/controller/HarbormasterCircleCIHookController.php',
'HarbormasterConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php',
'HarbormasterControlBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterControlBuildStepGroup.php',
'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php',
'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php',
'HarbormasterCreatePlansCapability' => 'applications/harbormaster/capability/HarbormasterCreatePlansCapability.php',
'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php',
'HarbormasterDrydockBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterDrydockBuildStepGroup.php',
'HarbormasterDrydockCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php',
'HarbormasterDrydockLeaseArtifact' => 'applications/harbormaster/artifact/HarbormasterDrydockLeaseArtifact.php',
'HarbormasterExecFuture' => 'applications/harbormaster/future/HarbormasterExecFuture.php',
'HarbormasterExternalBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterExternalBuildStepGroup.php',
'HarbormasterFileArtifact' => 'applications/harbormaster/artifact/HarbormasterFileArtifact.php',
'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php',
'HarbormasterHostArtifact' => 'applications/harbormaster/artifact/HarbormasterHostArtifact.php',
'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php',
'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php',
'HarbormasterLintPropertyView' => 'applications/harbormaster/view/HarbormasterLintPropertyView.php',
'HarbormasterLogWorker' => 'applications/harbormaster/worker/HarbormasterLogWorker.php',
'HarbormasterManagementArchiveLogsWorkflow' => 'applications/harbormaster/management/HarbormasterManagementArchiveLogsWorkflow.php',
'HarbormasterManagementBuildWorkflow' => 'applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php',
'HarbormasterManagementPublishWorkflow' => 'applications/harbormaster/management/HarbormasterManagementPublishWorkflow.php',
'HarbormasterManagementRebuildLogWorkflow' => 'applications/harbormaster/management/HarbormasterManagementRebuildLogWorkflow.php',
'HarbormasterManagementRestartWorkflow' => 'applications/harbormaster/management/HarbormasterManagementRestartWorkflow.php',
'HarbormasterManagementUpdateWorkflow' => 'applications/harbormaster/management/HarbormasterManagementUpdateWorkflow.php',
'HarbormasterManagementWorkflow' => 'applications/harbormaster/management/HarbormasterManagementWorkflow.php',
'HarbormasterManagementWriteLogWorkflow' => 'applications/harbormaster/management/HarbormasterManagementWriteLogWorkflow.php',
'HarbormasterMessageException' => 'applications/harbormaster/exception/HarbormasterMessageException.php',
'HarbormasterMessageType' => 'applications/harbormaster/engine/HarbormasterMessageType.php',
'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php',
'HarbormasterOtherBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterOtherBuildStepGroup.php',
'HarbormasterPlanBehaviorController' => 'applications/harbormaster/controller/HarbormasterPlanBehaviorController.php',
'HarbormasterPlanController' => 'applications/harbormaster/controller/HarbormasterPlanController.php',
'HarbormasterPlanDisableController' => 'applications/harbormaster/controller/HarbormasterPlanDisableController.php',
'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php',
'HarbormasterPlanListController' => 'applications/harbormaster/controller/HarbormasterPlanListController.php',
'HarbormasterPlanRunController' => 'applications/harbormaster/controller/HarbormasterPlanRunController.php',
'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php',
'HarbormasterPrototypeBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterPrototypeBuildStepGroup.php',
- 'HarbormasterPublishFragmentBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php',
'HarbormasterQueryAutotargetsConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryAutotargetsConduitAPIMethod.php',
'HarbormasterQueryBuildablesConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildablesConduitAPIMethod.php',
'HarbormasterQueryBuildsConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildsConduitAPIMethod.php',
'HarbormasterQueryBuildsSearchEngineAttachment' => 'applications/harbormaster/engineextension/HarbormasterQueryBuildsSearchEngineAttachment.php',
'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php',
'HarbormasterRunBuildPlansHeraldAction' => 'applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php',
'HarbormasterSchemaSpec' => 'applications/harbormaster/storage/HarbormasterSchemaSpec.php',
'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php',
'HarbormasterSendMessageConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php',
'HarbormasterSleepBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterSleepBuildStepImplementation.php',
'HarbormasterStepAddController' => 'applications/harbormaster/controller/HarbormasterStepAddController.php',
'HarbormasterStepDeleteController' => 'applications/harbormaster/controller/HarbormasterStepDeleteController.php',
'HarbormasterStepEditController' => 'applications/harbormaster/controller/HarbormasterStepEditController.php',
'HarbormasterStepViewController' => 'applications/harbormaster/controller/HarbormasterStepViewController.php',
'HarbormasterString' => 'applications/harbormaster/storage/HarbormasterString.php',
'HarbormasterTargetEngine' => 'applications/harbormaster/engine/HarbormasterTargetEngine.php',
'HarbormasterTargetSearchAPIMethod' => 'applications/harbormaster/conduit/HarbormasterTargetSearchAPIMethod.php',
'HarbormasterTargetWorker' => 'applications/harbormaster/worker/HarbormasterTargetWorker.php',
'HarbormasterTestBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterTestBuildStepGroup.php',
'HarbormasterThrowExceptionBuildStep' => 'applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php',
'HarbormasterUIEventListener' => 'applications/harbormaster/event/HarbormasterUIEventListener.php',
'HarbormasterURIArtifact' => 'applications/harbormaster/artifact/HarbormasterURIArtifact.php',
'HarbormasterUnitMessageListController' => 'applications/harbormaster/controller/HarbormasterUnitMessageListController.php',
'HarbormasterUnitMessageViewController' => 'applications/harbormaster/controller/HarbormasterUnitMessageViewController.php',
'HarbormasterUnitPropertyView' => 'applications/harbormaster/view/HarbormasterUnitPropertyView.php',
'HarbormasterUnitStatus' => 'applications/harbormaster/constants/HarbormasterUnitStatus.php',
'HarbormasterUnitSummaryView' => 'applications/harbormaster/view/HarbormasterUnitSummaryView.php',
'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php',
'HarbormasterWaitForPreviousBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php',
'HarbormasterWorker' => 'applications/harbormaster/worker/HarbormasterWorker.php',
'HarbormasterWorkingCopyArtifact' => 'applications/harbormaster/artifact/HarbormasterWorkingCopyArtifact.php',
'HeraldActingUserField' => 'applications/herald/field/HeraldActingUserField.php',
'HeraldAction' => 'applications/herald/action/HeraldAction.php',
'HeraldActionGroup' => 'applications/herald/action/HeraldActionGroup.php',
'HeraldActionRecord' => 'applications/herald/storage/HeraldActionRecord.php',
'HeraldAdapter' => 'applications/herald/adapter/HeraldAdapter.php',
'HeraldAdapterDatasource' => 'applications/herald/typeahead/HeraldAdapterDatasource.php',
'HeraldAlwaysField' => 'applications/herald/field/HeraldAlwaysField.php',
'HeraldAnotherRuleField' => 'applications/herald/field/HeraldAnotherRuleField.php',
'HeraldApplicationActionGroup' => 'applications/herald/action/HeraldApplicationActionGroup.php',
'HeraldApplyTranscript' => 'applications/herald/storage/transcript/HeraldApplyTranscript.php',
'HeraldBasicFieldGroup' => 'applications/herald/field/HeraldBasicFieldGroup.php',
'HeraldBoolFieldValue' => 'applications/herald/value/HeraldBoolFieldValue.php',
'HeraldBuildableState' => 'applications/herald/state/HeraldBuildableState.php',
'HeraldCallWebhookAction' => 'applications/herald/action/HeraldCallWebhookAction.php',
'HeraldCommentAction' => 'applications/herald/action/HeraldCommentAction.php',
'HeraldCommentContentField' => 'applications/herald/field/HeraldCommentContentField.php',
'HeraldCommitAdapter' => 'applications/diffusion/herald/HeraldCommitAdapter.php',
'HeraldCondition' => 'applications/herald/storage/HeraldCondition.php',
'HeraldConditionResult' => 'applications/herald/storage/transcript/HeraldConditionResult.php',
'HeraldConditionTranscript' => 'applications/herald/storage/transcript/HeraldConditionTranscript.php',
'HeraldContentSourceField' => 'applications/herald/field/HeraldContentSourceField.php',
'HeraldController' => 'applications/herald/controller/HeraldController.php',
'HeraldCoreStateReasons' => 'applications/herald/state/HeraldCoreStateReasons.php',
'HeraldCreateWebhooksCapability' => 'applications/herald/capability/HeraldCreateWebhooksCapability.php',
'HeraldDAO' => 'applications/herald/storage/HeraldDAO.php',
'HeraldDeprecatedFieldGroup' => 'applications/herald/field/HeraldDeprecatedFieldGroup.php',
'HeraldDifferentialAdapter' => 'applications/differential/herald/HeraldDifferentialAdapter.php',
'HeraldDifferentialDiffAdapter' => 'applications/differential/herald/HeraldDifferentialDiffAdapter.php',
'HeraldDifferentialRevisionAdapter' => 'applications/differential/herald/HeraldDifferentialRevisionAdapter.php',
'HeraldDisableController' => 'applications/herald/controller/HeraldDisableController.php',
'HeraldDoNothingAction' => 'applications/herald/action/HeraldDoNothingAction.php',
'HeraldEditFieldGroup' => 'applications/herald/field/HeraldEditFieldGroup.php',
'HeraldEffect' => 'applications/herald/engine/HeraldEffect.php',
'HeraldEmptyFieldValue' => 'applications/herald/value/HeraldEmptyFieldValue.php',
'HeraldEngine' => 'applications/herald/engine/HeraldEngine.php',
'HeraldExactProjectsField' => 'applications/project/herald/HeraldExactProjectsField.php',
'HeraldField' => 'applications/herald/field/HeraldField.php',
'HeraldFieldGroup' => 'applications/herald/field/HeraldFieldGroup.php',
'HeraldFieldTestCase' => 'applications/herald/field/__tests__/HeraldFieldTestCase.php',
'HeraldFieldValue' => 'applications/herald/value/HeraldFieldValue.php',
'HeraldGroup' => 'applications/herald/group/HeraldGroup.php',
'HeraldInvalidActionException' => 'applications/herald/engine/exception/HeraldInvalidActionException.php',
'HeraldInvalidConditionException' => 'applications/herald/engine/exception/HeraldInvalidConditionException.php',
'HeraldMailableState' => 'applications/herald/state/HeraldMailableState.php',
'HeraldManageGlobalRulesCapability' => 'applications/herald/capability/HeraldManageGlobalRulesCapability.php',
'HeraldManagementWorkflow' => 'applications/herald/management/HeraldManagementWorkflow.php',
'HeraldManiphestTaskAdapter' => 'applications/maniphest/herald/HeraldManiphestTaskAdapter.php',
'HeraldNewController' => 'applications/herald/controller/HeraldNewController.php',
'HeraldNewObjectField' => 'applications/herald/field/HeraldNewObjectField.php',
'HeraldNotifyActionGroup' => 'applications/herald/action/HeraldNotifyActionGroup.php',
'HeraldObjectTranscript' => 'applications/herald/storage/transcript/HeraldObjectTranscript.php',
'HeraldPhameBlogAdapter' => 'applications/phame/herald/HeraldPhameBlogAdapter.php',
'HeraldPhamePostAdapter' => 'applications/phame/herald/HeraldPhamePostAdapter.php',
'HeraldPholioMockAdapter' => 'applications/pholio/herald/HeraldPholioMockAdapter.php',
'HeraldPonderQuestionAdapter' => 'applications/ponder/herald/HeraldPonderQuestionAdapter.php',
'HeraldPreCommitAdapter' => 'applications/diffusion/herald/HeraldPreCommitAdapter.php',
'HeraldPreCommitContentAdapter' => 'applications/diffusion/herald/HeraldPreCommitContentAdapter.php',
'HeraldPreCommitRefAdapter' => 'applications/diffusion/herald/HeraldPreCommitRefAdapter.php',
'HeraldPreventActionGroup' => 'applications/herald/action/HeraldPreventActionGroup.php',
'HeraldProjectsField' => 'applications/project/herald/HeraldProjectsField.php',
'HeraldRecursiveConditionsException' => 'applications/herald/engine/exception/HeraldRecursiveConditionsException.php',
'HeraldRelatedFieldGroup' => 'applications/herald/field/HeraldRelatedFieldGroup.php',
'HeraldRemarkupFieldValue' => 'applications/herald/value/HeraldRemarkupFieldValue.php',
'HeraldRemarkupRule' => 'applications/herald/remarkup/HeraldRemarkupRule.php',
'HeraldRule' => 'applications/herald/storage/HeraldRule.php',
'HeraldRuleActionAffectsObjectEdgeType' => 'applications/herald/edge/HeraldRuleActionAffectsObjectEdgeType.php',
'HeraldRuleAdapter' => 'applications/herald/adapter/HeraldRuleAdapter.php',
'HeraldRuleAdapterField' => 'applications/herald/field/rule/HeraldRuleAdapterField.php',
'HeraldRuleController' => 'applications/herald/controller/HeraldRuleController.php',
'HeraldRuleDatasource' => 'applications/herald/typeahead/HeraldRuleDatasource.php',
'HeraldRuleDisableTransaction' => 'applications/herald/xaction/HeraldRuleDisableTransaction.php',
'HeraldRuleEditTransaction' => 'applications/herald/xaction/HeraldRuleEditTransaction.php',
'HeraldRuleEditor' => 'applications/herald/editor/HeraldRuleEditor.php',
'HeraldRuleEvaluationException' => 'applications/herald/engine/exception/HeraldRuleEvaluationException.php',
'HeraldRuleField' => 'applications/herald/field/rule/HeraldRuleField.php',
'HeraldRuleFieldGroup' => 'applications/herald/field/rule/HeraldRuleFieldGroup.php',
'HeraldRuleIndexEngineExtension' => 'applications/herald/engineextension/HeraldRuleIndexEngineExtension.php',
'HeraldRuleListController' => 'applications/herald/controller/HeraldRuleListController.php',
'HeraldRuleListView' => 'applications/herald/view/HeraldRuleListView.php',
'HeraldRuleManagementWorkflow' => 'applications/herald/management/HeraldRuleManagementWorkflow.php',
'HeraldRuleNameTransaction' => 'applications/herald/xaction/HeraldRuleNameTransaction.php',
'HeraldRulePHIDType' => 'applications/herald/phid/HeraldRulePHIDType.php',
'HeraldRuleQuery' => 'applications/herald/query/HeraldRuleQuery.php',
'HeraldRuleReplyHandler' => 'applications/herald/mail/HeraldRuleReplyHandler.php',
'HeraldRuleResult' => 'applications/herald/storage/transcript/HeraldRuleResult.php',
'HeraldRuleSearchEngine' => 'applications/herald/query/HeraldRuleSearchEngine.php',
'HeraldRuleSerializer' => 'applications/herald/editor/HeraldRuleSerializer.php',
'HeraldRuleTestCase' => 'applications/herald/storage/__tests__/HeraldRuleTestCase.php',
'HeraldRuleTransaction' => 'applications/herald/storage/HeraldRuleTransaction.php',
'HeraldRuleTransactionType' => 'applications/herald/xaction/HeraldRuleTransactionType.php',
'HeraldRuleTranscript' => 'applications/herald/storage/transcript/HeraldRuleTranscript.php',
'HeraldRuleTypeConfig' => 'applications/herald/config/HeraldRuleTypeConfig.php',
'HeraldRuleTypeDatasource' => 'applications/herald/typeahead/HeraldRuleTypeDatasource.php',
'HeraldRuleTypeField' => 'applications/herald/field/rule/HeraldRuleTypeField.php',
'HeraldRuleViewController' => 'applications/herald/controller/HeraldRuleViewController.php',
'HeraldSchemaSpec' => 'applications/herald/storage/HeraldSchemaSpec.php',
'HeraldSelectFieldValue' => 'applications/herald/value/HeraldSelectFieldValue.php',
'HeraldSpaceField' => 'applications/spaces/herald/HeraldSpaceField.php',
'HeraldState' => 'applications/herald/state/HeraldState.php',
'HeraldStateReasons' => 'applications/herald/state/HeraldStateReasons.php',
'HeraldSubscribersField' => 'applications/subscriptions/herald/HeraldSubscribersField.php',
'HeraldSupportActionGroup' => 'applications/herald/action/HeraldSupportActionGroup.php',
'HeraldSupportFieldGroup' => 'applications/herald/field/HeraldSupportFieldGroup.php',
'HeraldTestConsoleController' => 'applications/herald/controller/HeraldTestConsoleController.php',
'HeraldTestManagementWorkflow' => 'applications/herald/management/HeraldTestManagementWorkflow.php',
'HeraldTextFieldValue' => 'applications/herald/value/HeraldTextFieldValue.php',
'HeraldTokenizerFieldValue' => 'applications/herald/value/HeraldTokenizerFieldValue.php',
'HeraldTransactionQuery' => 'applications/herald/query/HeraldTransactionQuery.php',
'HeraldTransactionsFieldGroup' => 'applications/herald/field/HeraldTransactionsFieldGroup.php',
'HeraldTranscript' => 'applications/herald/storage/transcript/HeraldTranscript.php',
'HeraldTranscriptController' => 'applications/herald/controller/HeraldTranscriptController.php',
'HeraldTranscriptDestructionEngineExtension' => 'applications/herald/engineextension/HeraldTranscriptDestructionEngineExtension.php',
'HeraldTranscriptGarbageCollector' => 'applications/herald/garbagecollector/HeraldTranscriptGarbageCollector.php',
'HeraldTranscriptListController' => 'applications/herald/controller/HeraldTranscriptListController.php',
'HeraldTranscriptPHIDType' => 'applications/herald/phid/HeraldTranscriptPHIDType.php',
'HeraldTranscriptQuery' => 'applications/herald/query/HeraldTranscriptQuery.php',
'HeraldTranscriptResult' => 'applications/herald/storage/transcript/HeraldTranscriptResult.php',
'HeraldTranscriptSearchEngine' => 'applications/herald/query/HeraldTranscriptSearchEngine.php',
'HeraldTranscriptTestCase' => 'applications/herald/storage/__tests__/HeraldTranscriptTestCase.php',
'HeraldUtilityActionGroup' => 'applications/herald/action/HeraldUtilityActionGroup.php',
'HeraldWebhook' => 'applications/herald/storage/HeraldWebhook.php',
'HeraldWebhookCallManagementWorkflow' => 'applications/herald/management/HeraldWebhookCallManagementWorkflow.php',
'HeraldWebhookController' => 'applications/herald/controller/HeraldWebhookController.php',
'HeraldWebhookDatasource' => 'applications/herald/typeahead/HeraldWebhookDatasource.php',
'HeraldWebhookEditController' => 'applications/herald/controller/HeraldWebhookEditController.php',
'HeraldWebhookEditEngine' => 'applications/herald/editor/HeraldWebhookEditEngine.php',
'HeraldWebhookEditor' => 'applications/herald/editor/HeraldWebhookEditor.php',
'HeraldWebhookKeyController' => 'applications/herald/controller/HeraldWebhookKeyController.php',
'HeraldWebhookListController' => 'applications/herald/controller/HeraldWebhookListController.php',
'HeraldWebhookManagementWorkflow' => 'applications/herald/management/HeraldWebhookManagementWorkflow.php',
'HeraldWebhookNameTransaction' => 'applications/herald/xaction/HeraldWebhookNameTransaction.php',
'HeraldWebhookPHIDType' => 'applications/herald/phid/HeraldWebhookPHIDType.php',
'HeraldWebhookQuery' => 'applications/herald/query/HeraldWebhookQuery.php',
'HeraldWebhookRequest' => 'applications/herald/storage/HeraldWebhookRequest.php',
'HeraldWebhookRequestGarbageCollector' => 'applications/herald/garbagecollector/HeraldWebhookRequestGarbageCollector.php',
'HeraldWebhookRequestListView' => 'applications/herald/view/HeraldWebhookRequestListView.php',
'HeraldWebhookRequestPHIDType' => 'applications/herald/phid/HeraldWebhookRequestPHIDType.php',
'HeraldWebhookRequestQuery' => 'applications/herald/query/HeraldWebhookRequestQuery.php',
'HeraldWebhookSearchEngine' => 'applications/herald/query/HeraldWebhookSearchEngine.php',
'HeraldWebhookStatusTransaction' => 'applications/herald/xaction/HeraldWebhookStatusTransaction.php',
'HeraldWebhookTestController' => 'applications/herald/controller/HeraldWebhookTestController.php',
'HeraldWebhookTransaction' => 'applications/herald/storage/HeraldWebhookTransaction.php',
'HeraldWebhookTransactionQuery' => 'applications/herald/query/HeraldWebhookTransactionQuery.php',
'HeraldWebhookTransactionType' => 'applications/herald/xaction/HeraldWebhookTransactionType.php',
'HeraldWebhookURITransaction' => 'applications/herald/xaction/HeraldWebhookURITransaction.php',
'HeraldWebhookViewController' => 'applications/herald/controller/HeraldWebhookViewController.php',
'HeraldWebhookWorker' => 'applications/herald/worker/HeraldWebhookWorker.php',
'Javelin' => 'infrastructure/javelin/Javelin.php',
'LegalpadController' => 'applications/legalpad/controller/LegalpadController.php',
'LegalpadCreateDocumentsCapability' => 'applications/legalpad/capability/LegalpadCreateDocumentsCapability.php',
'LegalpadDAO' => 'applications/legalpad/storage/LegalpadDAO.php',
'LegalpadDefaultEditCapability' => 'applications/legalpad/capability/LegalpadDefaultEditCapability.php',
'LegalpadDefaultViewCapability' => 'applications/legalpad/capability/LegalpadDefaultViewCapability.php',
'LegalpadDocument' => 'applications/legalpad/storage/LegalpadDocument.php',
'LegalpadDocumentBody' => 'applications/legalpad/storage/LegalpadDocumentBody.php',
'LegalpadDocumentDatasource' => 'applications/legalpad/typeahead/LegalpadDocumentDatasource.php',
'LegalpadDocumentDoneController' => 'applications/legalpad/controller/LegalpadDocumentDoneController.php',
'LegalpadDocumentEditController' => 'applications/legalpad/controller/LegalpadDocumentEditController.php',
'LegalpadDocumentEditEngine' => 'applications/legalpad/editor/LegalpadDocumentEditEngine.php',
'LegalpadDocumentEditor' => 'applications/legalpad/editor/LegalpadDocumentEditor.php',
'LegalpadDocumentListController' => 'applications/legalpad/controller/LegalpadDocumentListController.php',
'LegalpadDocumentManageController' => 'applications/legalpad/controller/LegalpadDocumentManageController.php',
'LegalpadDocumentPreambleTransaction' => 'applications/legalpad/xaction/LegalpadDocumentPreambleTransaction.php',
'LegalpadDocumentQuery' => 'applications/legalpad/query/LegalpadDocumentQuery.php',
'LegalpadDocumentRemarkupRule' => 'applications/legalpad/remarkup/LegalpadDocumentRemarkupRule.php',
'LegalpadDocumentRequireSignatureTransaction' => 'applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php',
'LegalpadDocumentSearchEngine' => 'applications/legalpad/query/LegalpadDocumentSearchEngine.php',
'LegalpadDocumentSignController' => 'applications/legalpad/controller/LegalpadDocumentSignController.php',
'LegalpadDocumentSignature' => 'applications/legalpad/storage/LegalpadDocumentSignature.php',
'LegalpadDocumentSignatureAddController' => 'applications/legalpad/controller/LegalpadDocumentSignatureAddController.php',
'LegalpadDocumentSignatureListController' => 'applications/legalpad/controller/LegalpadDocumentSignatureListController.php',
'LegalpadDocumentSignatureQuery' => 'applications/legalpad/query/LegalpadDocumentSignatureQuery.php',
'LegalpadDocumentSignatureSearchEngine' => 'applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php',
'LegalpadDocumentSignatureTypeTransaction' => 'applications/legalpad/xaction/LegalpadDocumentSignatureTypeTransaction.php',
'LegalpadDocumentSignatureVerificationController' => 'applications/legalpad/controller/LegalpadDocumentSignatureVerificationController.php',
'LegalpadDocumentSignatureViewController' => 'applications/legalpad/controller/LegalpadDocumentSignatureViewController.php',
'LegalpadDocumentTextTransaction' => 'applications/legalpad/xaction/LegalpadDocumentTextTransaction.php',
'LegalpadDocumentTitleTransaction' => 'applications/legalpad/xaction/LegalpadDocumentTitleTransaction.php',
'LegalpadDocumentTransactionType' => 'applications/legalpad/xaction/LegalpadDocumentTransactionType.php',
'LegalpadMailReceiver' => 'applications/legalpad/mail/LegalpadMailReceiver.php',
'LegalpadObjectNeedsSignatureEdgeType' => 'applications/legalpad/edge/LegalpadObjectNeedsSignatureEdgeType.php',
'LegalpadReplyHandler' => 'applications/legalpad/mail/LegalpadReplyHandler.php',
'LegalpadRequireSignatureHeraldAction' => 'applications/legalpad/herald/LegalpadRequireSignatureHeraldAction.php',
'LegalpadSchemaSpec' => 'applications/legalpad/storage/LegalpadSchemaSpec.php',
'LegalpadSignatureNeededByObjectEdgeType' => 'applications/legalpad/edge/LegalpadSignatureNeededByObjectEdgeType.php',
'LegalpadTransaction' => 'applications/legalpad/storage/LegalpadTransaction.php',
'LegalpadTransactionComment' => 'applications/legalpad/storage/LegalpadTransactionComment.php',
'LegalpadTransactionQuery' => 'applications/legalpad/query/LegalpadTransactionQuery.php',
'LiskChunkTestCase' => 'infrastructure/storage/lisk/__tests__/LiskChunkTestCase.php',
'LiskDAO' => 'infrastructure/storage/lisk/LiskDAO.php',
'LiskDAOTestCase' => 'infrastructure/storage/lisk/__tests__/LiskDAOTestCase.php',
'LiskEphemeralObjectException' => 'infrastructure/storage/lisk/LiskEphemeralObjectException.php',
'LiskFixtureTestCase' => 'infrastructure/storage/lisk/__tests__/LiskFixtureTestCase.php',
'LiskIsolationTestCase' => 'infrastructure/storage/lisk/__tests__/LiskIsolationTestCase.php',
'LiskIsolationTestDAO' => 'infrastructure/storage/lisk/__tests__/LiskIsolationTestDAO.php',
'LiskIsolationTestDAOException' => 'infrastructure/storage/lisk/__tests__/LiskIsolationTestDAOException.php',
'LiskMigrationIterator' => 'infrastructure/storage/lisk/LiskMigrationIterator.php',
'LiskRawMigrationIterator' => 'infrastructure/storage/lisk/LiskRawMigrationIterator.php',
'MacroConduitAPIMethod' => 'applications/macro/conduit/MacroConduitAPIMethod.php',
'MacroCreateMemeConduitAPIMethod' => 'applications/macro/conduit/MacroCreateMemeConduitAPIMethod.php',
'MacroEditConduitAPIMethod' => 'applications/macro/conduit/MacroEditConduitAPIMethod.php',
'MacroEmojiExample' => 'applications/uiexample/examples/MacroEmojiExample.php',
'MacroQueryConduitAPIMethod' => 'applications/macro/conduit/MacroQueryConduitAPIMethod.php',
'ManiphestAssignEmailCommand' => 'applications/maniphest/command/ManiphestAssignEmailCommand.php',
'ManiphestAssigneeDatasource' => 'applications/maniphest/typeahead/ManiphestAssigneeDatasource.php',
'ManiphestBulkEditCapability' => 'applications/maniphest/capability/ManiphestBulkEditCapability.php',
'ManiphestBulkEditController' => 'applications/maniphest/controller/ManiphestBulkEditController.php',
'ManiphestClaimEmailCommand' => 'applications/maniphest/command/ManiphestClaimEmailCommand.php',
'ManiphestCloseEmailCommand' => 'applications/maniphest/command/ManiphestCloseEmailCommand.php',
'ManiphestConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestConduitAPIMethod.php',
'ManiphestConfiguredCustomField' => 'applications/maniphest/field/ManiphestConfiguredCustomField.php',
'ManiphestConstants' => 'applications/maniphest/constants/ManiphestConstants.php',
'ManiphestController' => 'applications/maniphest/controller/ManiphestController.php',
'ManiphestCreateMailReceiver' => 'applications/maniphest/mail/ManiphestCreateMailReceiver.php',
'ManiphestCreateTaskConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestCreateTaskConduitAPIMethod.php',
'ManiphestCustomField' => 'applications/maniphest/field/ManiphestCustomField.php',
'ManiphestCustomFieldNumericIndex' => 'applications/maniphest/storage/ManiphestCustomFieldNumericIndex.php',
'ManiphestCustomFieldStatusParser' => 'applications/maniphest/field/parser/ManiphestCustomFieldStatusParser.php',
'ManiphestCustomFieldStatusParserTestCase' => 'applications/maniphest/field/parser/__tests__/ManiphestCustomFieldStatusParserTestCase.php',
'ManiphestCustomFieldStorage' => 'applications/maniphest/storage/ManiphestCustomFieldStorage.php',
'ManiphestCustomFieldStringIndex' => 'applications/maniphest/storage/ManiphestCustomFieldStringIndex.php',
'ManiphestDAO' => 'applications/maniphest/storage/ManiphestDAO.php',
'ManiphestDefaultEditCapability' => 'applications/maniphest/capability/ManiphestDefaultEditCapability.php',
'ManiphestDefaultViewCapability' => 'applications/maniphest/capability/ManiphestDefaultViewCapability.php',
'ManiphestEditConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestEditConduitAPIMethod.php',
'ManiphestEditEngine' => 'applications/maniphest/editor/ManiphestEditEngine.php',
'ManiphestEmailCommand' => 'applications/maniphest/command/ManiphestEmailCommand.php',
'ManiphestGetTaskTransactionsConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestGetTaskTransactionsConduitAPIMethod.php',
'ManiphestHovercardEngineExtension' => 'applications/maniphest/engineextension/ManiphestHovercardEngineExtension.php',
'ManiphestInfoConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php',
'ManiphestMailEngineExtension' => 'applications/maniphest/engineextension/ManiphestMailEngineExtension.php',
'ManiphestNameIndex' => 'applications/maniphest/storage/ManiphestNameIndex.php',
'ManiphestPointsConfigType' => 'applications/maniphest/config/ManiphestPointsConfigType.php',
'ManiphestPrioritiesConfigType' => 'applications/maniphest/config/ManiphestPrioritiesConfigType.php',
'ManiphestPriorityEmailCommand' => 'applications/maniphest/command/ManiphestPriorityEmailCommand.php',
'ManiphestPrioritySearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestPrioritySearchConduitAPIMethod.php',
'ManiphestProjectNameFulltextEngineExtension' => 'applications/maniphest/engineextension/ManiphestProjectNameFulltextEngineExtension.php',
'ManiphestQueryConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php',
'ManiphestQueryStatusesConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestQueryStatusesConduitAPIMethod.php',
'ManiphestRemarkupRule' => 'applications/maniphest/remarkup/ManiphestRemarkupRule.php',
'ManiphestReplyHandler' => 'applications/maniphest/mail/ManiphestReplyHandler.php',
'ManiphestReportController' => 'applications/maniphest/controller/ManiphestReportController.php',
'ManiphestSchemaSpec' => 'applications/maniphest/storage/ManiphestSchemaSpec.php',
'ManiphestSearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestSearchConduitAPIMethod.php',
'ManiphestStatusEmailCommand' => 'applications/maniphest/command/ManiphestStatusEmailCommand.php',
'ManiphestStatusSearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestStatusSearchConduitAPIMethod.php',
'ManiphestStatusesConfigType' => 'applications/maniphest/config/ManiphestStatusesConfigType.php',
'ManiphestSubtypesConfigType' => 'applications/maniphest/config/ManiphestSubtypesConfigType.php',
'ManiphestTask' => 'applications/maniphest/storage/ManiphestTask.php',
'ManiphestTaskAssignHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php',
'ManiphestTaskAssignOtherHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignOtherHeraldAction.php',
'ManiphestTaskAssignSelfHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignSelfHeraldAction.php',
'ManiphestTaskAssigneeHeraldField' => 'applications/maniphest/herald/ManiphestTaskAssigneeHeraldField.php',
'ManiphestTaskAttachTransaction' => 'applications/maniphest/xaction/ManiphestTaskAttachTransaction.php',
'ManiphestTaskAuthorHeraldField' => 'applications/maniphest/herald/ManiphestTaskAuthorHeraldField.php',
'ManiphestTaskAuthorPolicyRule' => 'applications/maniphest/policyrule/ManiphestTaskAuthorPolicyRule.php',
'ManiphestTaskBulkEngine' => 'applications/maniphest/bulk/ManiphestTaskBulkEngine.php',
'ManiphestTaskCloseAsDuplicateRelationship' => 'applications/maniphest/relationship/ManiphestTaskCloseAsDuplicateRelationship.php',
'ManiphestTaskClosedStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskClosedStatusDatasource.php',
'ManiphestTaskCoverImageTransaction' => 'applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php',
'ManiphestTaskDependedOnByTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependedOnByTaskEdgeType.php',
'ManiphestTaskDependsOnTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependsOnTaskEdgeType.php',
'ManiphestTaskDescriptionHeraldField' => 'applications/maniphest/herald/ManiphestTaskDescriptionHeraldField.php',
'ManiphestTaskDescriptionTransaction' => 'applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php',
'ManiphestTaskDetailController' => 'applications/maniphest/controller/ManiphestTaskDetailController.php',
'ManiphestTaskEdgeTransaction' => 'applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php',
'ManiphestTaskEditController' => 'applications/maniphest/controller/ManiphestTaskEditController.php',
'ManiphestTaskEditEngineLock' => 'applications/maniphest/editor/ManiphestTaskEditEngineLock.php',
'ManiphestTaskFerretEngine' => 'applications/maniphest/search/ManiphestTaskFerretEngine.php',
'ManiphestTaskFulltextEngine' => 'applications/maniphest/search/ManiphestTaskFulltextEngine.php',
'ManiphestTaskGraph' => 'infrastructure/graph/ManiphestTaskGraph.php',
'ManiphestTaskGraphController' => 'applications/maniphest/controller/ManiphestTaskGraphController.php',
'ManiphestTaskHasCommitEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasCommitEdgeType.php',
'ManiphestTaskHasCommitRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasCommitRelationship.php',
'ManiphestTaskHasDuplicateTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasDuplicateTaskEdgeType.php',
'ManiphestTaskHasMockEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasMockEdgeType.php',
'ManiphestTaskHasMockRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasMockRelationship.php',
'ManiphestTaskHasParentRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasParentRelationship.php',
'ManiphestTaskHasRevisionEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasRevisionEdgeType.php',
'ManiphestTaskHasRevisionRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasRevisionRelationship.php',
'ManiphestTaskHasSubtaskRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasSubtaskRelationship.php',
'ManiphestTaskHeraldField' => 'applications/maniphest/herald/ManiphestTaskHeraldField.php',
'ManiphestTaskHeraldFieldGroup' => 'applications/maniphest/herald/ManiphestTaskHeraldFieldGroup.php',
'ManiphestTaskIsDuplicateOfTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskIsDuplicateOfTaskEdgeType.php',
'ManiphestTaskListController' => 'applications/maniphest/controller/ManiphestTaskListController.php',
'ManiphestTaskListHTTPParameterType' => 'applications/maniphest/httpparametertype/ManiphestTaskListHTTPParameterType.php',
'ManiphestTaskListView' => 'applications/maniphest/view/ManiphestTaskListView.php',
'ManiphestTaskMFAEngine' => 'applications/maniphest/engine/ManiphestTaskMFAEngine.php',
'ManiphestTaskMailReceiver' => 'applications/maniphest/mail/ManiphestTaskMailReceiver.php',
'ManiphestTaskMergeInRelationship' => 'applications/maniphest/relationship/ManiphestTaskMergeInRelationship.php',
'ManiphestTaskMergedFromTransaction' => 'applications/maniphest/xaction/ManiphestTaskMergedFromTransaction.php',
'ManiphestTaskMergedIntoTransaction' => 'applications/maniphest/xaction/ManiphestTaskMergedIntoTransaction.php',
'ManiphestTaskOpenStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskOpenStatusDatasource.php',
'ManiphestTaskOwnerTransaction' => 'applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php',
'ManiphestTaskPHIDResolver' => 'applications/maniphest/httpparametertype/ManiphestTaskPHIDResolver.php',
'ManiphestTaskPHIDType' => 'applications/maniphest/phid/ManiphestTaskPHIDType.php',
'ManiphestTaskParentTransaction' => 'applications/maniphest/xaction/ManiphestTaskParentTransaction.php',
'ManiphestTaskPoints' => 'applications/maniphest/constants/ManiphestTaskPoints.php',
'ManiphestTaskPointsTransaction' => 'applications/maniphest/xaction/ManiphestTaskPointsTransaction.php',
'ManiphestTaskPolicyCodex' => 'applications/maniphest/policy/ManiphestTaskPolicyCodex.php',
'ManiphestTaskPriority' => 'applications/maniphest/constants/ManiphestTaskPriority.php',
'ManiphestTaskPriorityDatasource' => 'applications/maniphest/typeahead/ManiphestTaskPriorityDatasource.php',
'ManiphestTaskPriorityHeraldAction' => 'applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php',
'ManiphestTaskPriorityHeraldField' => 'applications/maniphest/herald/ManiphestTaskPriorityHeraldField.php',
'ManiphestTaskPriorityTransaction' => 'applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php',
'ManiphestTaskQuery' => 'applications/maniphest/query/ManiphestTaskQuery.php',
'ManiphestTaskRelationship' => 'applications/maniphest/relationship/ManiphestTaskRelationship.php',
'ManiphestTaskRelationshipSource' => 'applications/search/relationship/ManiphestTaskRelationshipSource.php',
'ManiphestTaskResultListView' => 'applications/maniphest/view/ManiphestTaskResultListView.php',
'ManiphestTaskSearchEngine' => 'applications/maniphest/query/ManiphestTaskSearchEngine.php',
'ManiphestTaskStatus' => 'applications/maniphest/constants/ManiphestTaskStatus.php',
'ManiphestTaskStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskStatusDatasource.php',
'ManiphestTaskStatusFunctionDatasource' => 'applications/maniphest/typeahead/ManiphestTaskStatusFunctionDatasource.php',
'ManiphestTaskStatusHeraldAction' => 'applications/maniphest/herald/ManiphestTaskStatusHeraldAction.php',
'ManiphestTaskStatusHeraldField' => 'applications/maniphest/herald/ManiphestTaskStatusHeraldField.php',
'ManiphestTaskStatusTestCase' => 'applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php',
'ManiphestTaskStatusTransaction' => 'applications/maniphest/xaction/ManiphestTaskStatusTransaction.php',
'ManiphestTaskSubpriorityTransaction' => 'applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php',
'ManiphestTaskSubtaskController' => 'applications/maniphest/controller/ManiphestTaskSubtaskController.php',
'ManiphestTaskSubtypeDatasource' => 'applications/maniphest/typeahead/ManiphestTaskSubtypeDatasource.php',
'ManiphestTaskTitleHeraldField' => 'applications/maniphest/herald/ManiphestTaskTitleHeraldField.php',
'ManiphestTaskTitleTransaction' => 'applications/maniphest/xaction/ManiphestTaskTitleTransaction.php',
'ManiphestTaskTransactionType' => 'applications/maniphest/xaction/ManiphestTaskTransactionType.php',
'ManiphestTaskUnblockTransaction' => 'applications/maniphest/xaction/ManiphestTaskUnblockTransaction.php',
'ManiphestTaskUnlockEngine' => 'applications/maniphest/engine/ManiphestTaskUnlockEngine.php',
'ManiphestTransaction' => 'applications/maniphest/storage/ManiphestTransaction.php',
'ManiphestTransactionComment' => 'applications/maniphest/storage/ManiphestTransactionComment.php',
'ManiphestTransactionEditor' => 'applications/maniphest/editor/ManiphestTransactionEditor.php',
'ManiphestTransactionQuery' => 'applications/maniphest/query/ManiphestTransactionQuery.php',
'ManiphestUpdateConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestUpdateConduitAPIMethod.php',
'ManiphestView' => 'applications/maniphest/view/ManiphestView.php',
'MetaMTAEmailTransactionCommand' => 'applications/metamta/command/MetaMTAEmailTransactionCommand.php',
'MetaMTAEmailTransactionCommandTestCase' => 'applications/metamta/command/__tests__/MetaMTAEmailTransactionCommandTestCase.php',
'MetaMTAMailReceivedGarbageCollector' => 'applications/metamta/garbagecollector/MetaMTAMailReceivedGarbageCollector.php',
'MetaMTAMailSentGarbageCollector' => 'applications/metamta/garbagecollector/MetaMTAMailSentGarbageCollector.php',
'MetaMTAReceivedMailStatus' => 'applications/metamta/constants/MetaMTAReceivedMailStatus.php',
'MultimeterContext' => 'applications/multimeter/storage/MultimeterContext.php',
'MultimeterControl' => 'applications/multimeter/data/MultimeterControl.php',
'MultimeterController' => 'applications/multimeter/controller/MultimeterController.php',
'MultimeterDAO' => 'applications/multimeter/storage/MultimeterDAO.php',
'MultimeterDimension' => 'applications/multimeter/storage/MultimeterDimension.php',
'MultimeterEvent' => 'applications/multimeter/storage/MultimeterEvent.php',
'MultimeterEventGarbageCollector' => 'applications/multimeter/garbagecollector/MultimeterEventGarbageCollector.php',
'MultimeterHost' => 'applications/multimeter/storage/MultimeterHost.php',
'MultimeterLabel' => 'applications/multimeter/storage/MultimeterLabel.php',
'MultimeterSampleController' => 'applications/multimeter/controller/MultimeterSampleController.php',
'MultimeterViewer' => 'applications/multimeter/storage/MultimeterViewer.php',
'NuanceCommandImplementation' => 'applications/nuance/command/NuanceCommandImplementation.php',
'NuanceConduitAPIMethod' => 'applications/nuance/conduit/NuanceConduitAPIMethod.php',
'NuanceConsoleController' => 'applications/nuance/controller/NuanceConsoleController.php',
'NuanceContentSource' => 'applications/nuance/contentsource/NuanceContentSource.php',
'NuanceController' => 'applications/nuance/controller/NuanceController.php',
'NuanceDAO' => 'applications/nuance/storage/NuanceDAO.php',
'NuanceFormItemType' => 'applications/nuance/item/NuanceFormItemType.php',
'NuanceGitHubEventItemType' => 'applications/nuance/item/NuanceGitHubEventItemType.php',
'NuanceGitHubImportCursor' => 'applications/nuance/cursor/NuanceGitHubImportCursor.php',
'NuanceGitHubIssuesImportCursor' => 'applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php',
'NuanceGitHubRawEvent' => 'applications/nuance/github/NuanceGitHubRawEvent.php',
'NuanceGitHubRawEventTestCase' => 'applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php',
'NuanceGitHubRepositoryImportCursor' => 'applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php',
'NuanceGitHubRepositorySourceDefinition' => 'applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php',
'NuanceImportCursor' => 'applications/nuance/cursor/NuanceImportCursor.php',
'NuanceImportCursorData' => 'applications/nuance/storage/NuanceImportCursorData.php',
'NuanceImportCursorDataQuery' => 'applications/nuance/query/NuanceImportCursorDataQuery.php',
'NuanceImportCursorPHIDType' => 'applications/nuance/phid/NuanceImportCursorPHIDType.php',
'NuanceItem' => 'applications/nuance/storage/NuanceItem.php',
'NuanceItemActionController' => 'applications/nuance/controller/NuanceItemActionController.php',
'NuanceItemCommand' => 'applications/nuance/storage/NuanceItemCommand.php',
'NuanceItemCommandQuery' => 'applications/nuance/query/NuanceItemCommandQuery.php',
'NuanceItemCommandSpec' => 'applications/nuance/command/NuanceItemCommandSpec.php',
'NuanceItemCommandTransaction' => 'applications/nuance/xaction/NuanceItemCommandTransaction.php',
'NuanceItemController' => 'applications/nuance/controller/NuanceItemController.php',
'NuanceItemEditor' => 'applications/nuance/editor/NuanceItemEditor.php',
'NuanceItemListController' => 'applications/nuance/controller/NuanceItemListController.php',
'NuanceItemManageController' => 'applications/nuance/controller/NuanceItemManageController.php',
'NuanceItemOwnerTransaction' => 'applications/nuance/xaction/NuanceItemOwnerTransaction.php',
'NuanceItemPHIDType' => 'applications/nuance/phid/NuanceItemPHIDType.php',
'NuanceItemPropertyTransaction' => 'applications/nuance/xaction/NuanceItemPropertyTransaction.php',
'NuanceItemQuery' => 'applications/nuance/query/NuanceItemQuery.php',
'NuanceItemQueueTransaction' => 'applications/nuance/xaction/NuanceItemQueueTransaction.php',
'NuanceItemRequestorTransaction' => 'applications/nuance/xaction/NuanceItemRequestorTransaction.php',
'NuanceItemSearchEngine' => 'applications/nuance/query/NuanceItemSearchEngine.php',
'NuanceItemSourceTransaction' => 'applications/nuance/xaction/NuanceItemSourceTransaction.php',
'NuanceItemStatusTransaction' => 'applications/nuance/xaction/NuanceItemStatusTransaction.php',
'NuanceItemTransaction' => 'applications/nuance/storage/NuanceItemTransaction.php',
'NuanceItemTransactionComment' => 'applications/nuance/storage/NuanceItemTransactionComment.php',
'NuanceItemTransactionQuery' => 'applications/nuance/query/NuanceItemTransactionQuery.php',
'NuanceItemTransactionType' => 'applications/nuance/xaction/NuanceItemTransactionType.php',
'NuanceItemType' => 'applications/nuance/item/NuanceItemType.php',
'NuanceItemUpdateWorker' => 'applications/nuance/worker/NuanceItemUpdateWorker.php',
'NuanceItemViewController' => 'applications/nuance/controller/NuanceItemViewController.php',
'NuanceManagementImportWorkflow' => 'applications/nuance/management/NuanceManagementImportWorkflow.php',
'NuanceManagementUpdateWorkflow' => 'applications/nuance/management/NuanceManagementUpdateWorkflow.php',
'NuanceManagementWorkflow' => 'applications/nuance/management/NuanceManagementWorkflow.php',
'NuancePhabricatorFormSourceDefinition' => 'applications/nuance/source/NuancePhabricatorFormSourceDefinition.php',
'NuanceQuery' => 'applications/nuance/query/NuanceQuery.php',
'NuanceQueue' => 'applications/nuance/storage/NuanceQueue.php',
'NuanceQueueController' => 'applications/nuance/controller/NuanceQueueController.php',
'NuanceQueueDatasource' => 'applications/nuance/typeahead/NuanceQueueDatasource.php',
'NuanceQueueEditController' => 'applications/nuance/controller/NuanceQueueEditController.php',
'NuanceQueueEditEngine' => 'applications/nuance/editor/NuanceQueueEditEngine.php',
'NuanceQueueEditor' => 'applications/nuance/editor/NuanceQueueEditor.php',
'NuanceQueueListController' => 'applications/nuance/controller/NuanceQueueListController.php',
'NuanceQueueNameTransaction' => 'applications/nuance/xaction/NuanceQueueNameTransaction.php',
'NuanceQueuePHIDType' => 'applications/nuance/phid/NuanceQueuePHIDType.php',
'NuanceQueueQuery' => 'applications/nuance/query/NuanceQueueQuery.php',
'NuanceQueueSearchEngine' => 'applications/nuance/query/NuanceQueueSearchEngine.php',
'NuanceQueueTransaction' => 'applications/nuance/storage/NuanceQueueTransaction.php',
'NuanceQueueTransactionComment' => 'applications/nuance/storage/NuanceQueueTransactionComment.php',
'NuanceQueueTransactionQuery' => 'applications/nuance/query/NuanceQueueTransactionQuery.php',
'NuanceQueueTransactionType' => 'applications/nuance/xaction/NuanceQueueTransactionType.php',
'NuanceQueueViewController' => 'applications/nuance/controller/NuanceQueueViewController.php',
'NuanceQueueWorkController' => 'applications/nuance/controller/NuanceQueueWorkController.php',
'NuanceSchemaSpec' => 'applications/nuance/storage/NuanceSchemaSpec.php',
'NuanceSource' => 'applications/nuance/storage/NuanceSource.php',
'NuanceSourceActionController' => 'applications/nuance/controller/NuanceSourceActionController.php',
'NuanceSourceController' => 'applications/nuance/controller/NuanceSourceController.php',
'NuanceSourceDefaultEditCapability' => 'applications/nuance/capability/NuanceSourceDefaultEditCapability.php',
'NuanceSourceDefaultQueueTransaction' => 'applications/nuance/xaction/NuanceSourceDefaultQueueTransaction.php',
'NuanceSourceDefaultViewCapability' => 'applications/nuance/capability/NuanceSourceDefaultViewCapability.php',
'NuanceSourceDefinition' => 'applications/nuance/source/NuanceSourceDefinition.php',
'NuanceSourceDefinitionTestCase' => 'applications/nuance/source/__tests__/NuanceSourceDefinitionTestCase.php',
'NuanceSourceEditController' => 'applications/nuance/controller/NuanceSourceEditController.php',
'NuanceSourceEditEngine' => 'applications/nuance/editor/NuanceSourceEditEngine.php',
'NuanceSourceEditor' => 'applications/nuance/editor/NuanceSourceEditor.php',
'NuanceSourceListController' => 'applications/nuance/controller/NuanceSourceListController.php',
'NuanceSourceManageCapability' => 'applications/nuance/capability/NuanceSourceManageCapability.php',
'NuanceSourceNameNgrams' => 'applications/nuance/storage/NuanceSourceNameNgrams.php',
'NuanceSourceNameTransaction' => 'applications/nuance/xaction/NuanceSourceNameTransaction.php',
'NuanceSourcePHIDType' => 'applications/nuance/phid/NuanceSourcePHIDType.php',
'NuanceSourceQuery' => 'applications/nuance/query/NuanceSourceQuery.php',
'NuanceSourceSearchEngine' => 'applications/nuance/query/NuanceSourceSearchEngine.php',
'NuanceSourceTransaction' => 'applications/nuance/storage/NuanceSourceTransaction.php',
'NuanceSourceTransactionComment' => 'applications/nuance/storage/NuanceSourceTransactionComment.php',
'NuanceSourceTransactionQuery' => 'applications/nuance/query/NuanceSourceTransactionQuery.php',
'NuanceSourceTransactionType' => 'applications/nuance/xaction/NuanceSourceTransactionType.php',
'NuanceSourceViewController' => 'applications/nuance/controller/NuanceSourceViewController.php',
'NuanceTransaction' => 'applications/nuance/storage/NuanceTransaction.php',
'NuanceTrashCommand' => 'applications/nuance/command/NuanceTrashCommand.php',
'NuanceWorker' => 'applications/nuance/worker/NuanceWorker.php',
'OwnersConduitAPIMethod' => 'applications/owners/conduit/OwnersConduitAPIMethod.php',
'OwnersEditConduitAPIMethod' => 'applications/owners/conduit/OwnersEditConduitAPIMethod.php',
'OwnersPackageReplyHandler' => 'applications/owners/mail/OwnersPackageReplyHandler.php',
'OwnersQueryConduitAPIMethod' => 'applications/owners/conduit/OwnersQueryConduitAPIMethod.php',
'OwnersSearchConduitAPIMethod' => 'applications/owners/conduit/OwnersSearchConduitAPIMethod.php',
'PHIDConduitAPIMethod' => 'applications/phid/conduit/PHIDConduitAPIMethod.php',
'PHIDInfoConduitAPIMethod' => 'applications/phid/conduit/PHIDInfoConduitAPIMethod.php',
'PHIDLookupConduitAPIMethod' => 'applications/phid/conduit/PHIDLookupConduitAPIMethod.php',
'PHIDQueryConduitAPIMethod' => 'applications/phid/conduit/PHIDQueryConduitAPIMethod.php',
'PHUI' => 'view/phui/PHUI.php',
'PHUIActionPanelExample' => 'applications/uiexample/examples/PHUIActionPanelExample.php',
'PHUIActionPanelView' => 'view/phui/PHUIActionPanelView.php',
'PHUIApplicationMenuView' => 'view/layout/PHUIApplicationMenuView.php',
'PHUIBadgeBoxView' => 'view/phui/PHUIBadgeBoxView.php',
'PHUIBadgeExample' => 'applications/uiexample/examples/PHUIBadgeExample.php',
'PHUIBadgeMiniView' => 'view/phui/PHUIBadgeMiniView.php',
'PHUIBadgeView' => 'view/phui/PHUIBadgeView.php',
'PHUIBigInfoExample' => 'applications/uiexample/examples/PHUIBigInfoExample.php',
'PHUIBigInfoView' => 'view/phui/PHUIBigInfoView.php',
'PHUIBoxExample' => 'applications/uiexample/examples/PHUIBoxExample.php',
'PHUIBoxView' => 'view/phui/PHUIBoxView.php',
'PHUIButtonBarExample' => 'applications/uiexample/examples/PHUIButtonBarExample.php',
'PHUIButtonBarView' => 'view/phui/PHUIButtonBarView.php',
'PHUIButtonExample' => 'applications/uiexample/examples/PHUIButtonExample.php',
'PHUIButtonView' => 'view/phui/PHUIButtonView.php',
'PHUICMSView' => 'view/phui/PHUICMSView.php',
'PHUICalendarDayView' => 'view/phui/calendar/PHUICalendarDayView.php',
'PHUICalendarListView' => 'view/phui/calendar/PHUICalendarListView.php',
'PHUICalendarMonthView' => 'view/phui/calendar/PHUICalendarMonthView.php',
'PHUICalendarWeekView' => 'view/phui/calendar/PHUICalendarWeekView.php',
'PHUICalendarWidgetView' => 'view/phui/calendar/PHUICalendarWidgetView.php',
'PHUIColor' => 'view/phui/PHUIColor.php',
'PHUIColorPalletteExample' => 'applications/uiexample/examples/PHUIColorPalletteExample.php',
'PHUICrumbView' => 'view/phui/PHUICrumbView.php',
'PHUICrumbsView' => 'view/phui/PHUICrumbsView.php',
'PHUICurtainExtension' => 'view/extension/PHUICurtainExtension.php',
'PHUICurtainObjectRefListView' => 'view/phui/PHUICurtainObjectRefListView.php',
'PHUICurtainObjectRefView' => 'view/phui/PHUICurtainObjectRefView.php',
'PHUICurtainPanelView' => 'view/layout/PHUICurtainPanelView.php',
'PHUICurtainView' => 'view/layout/PHUICurtainView.php',
'PHUIDiffGraphView' => 'infrastructure/diff/view/PHUIDiffGraphView.php',
'PHUIDiffGraphViewTestCase' => 'infrastructure/diff/view/__tests__/PHUIDiffGraphViewTestCase.php',
'PHUIDiffInlineCommentDetailView' => 'infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php',
'PHUIDiffInlineCommentEditView' => 'infrastructure/diff/view/PHUIDiffInlineCommentEditView.php',
'PHUIDiffInlineCommentPreviewListView' => 'infrastructure/diff/view/PHUIDiffInlineCommentPreviewListView.php',
'PHUIDiffInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php',
'PHUIDiffInlineCommentTableScaffold' => 'infrastructure/diff/view/PHUIDiffInlineCommentTableScaffold.php',
'PHUIDiffInlineCommentUndoView' => 'infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php',
'PHUIDiffInlineCommentView' => 'infrastructure/diff/view/PHUIDiffInlineCommentView.php',
'PHUIDiffInlineThreader' => 'infrastructure/diff/view/PHUIDiffInlineThreader.php',
'PHUIDiffOneUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php',
'PHUIDiffRevealIconView' => 'infrastructure/diff/view/PHUIDiffRevealIconView.php',
'PHUIDiffTableOfContentsItemView' => 'infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php',
'PHUIDiffTableOfContentsListView' => 'infrastructure/diff/view/PHUIDiffTableOfContentsListView.php',
'PHUIDiffTwoUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php',
'PHUIDocumentSummaryView' => 'view/phui/PHUIDocumentSummaryView.php',
'PHUIDocumentView' => 'view/phui/PHUIDocumentView.php',
'PHUIFeedStoryExample' => 'applications/uiexample/examples/PHUIFeedStoryExample.php',
'PHUIFeedStoryView' => 'view/phui/PHUIFeedStoryView.php',
'PHUIFormDividerControl' => 'view/form/control/PHUIFormDividerControl.php',
'PHUIFormFileControl' => 'view/form/control/PHUIFormFileControl.php',
'PHUIFormFreeformDateControl' => 'view/form/control/PHUIFormFreeformDateControl.php',
'PHUIFormIconSetControl' => 'view/form/control/PHUIFormIconSetControl.php',
'PHUIFormInsetView' => 'view/form/PHUIFormInsetView.php',
'PHUIFormLayoutView' => 'view/form/PHUIFormLayoutView.php',
'PHUIFormNumberControl' => 'view/form/control/PHUIFormNumberControl.php',
'PHUIFormTimerControl' => 'view/form/control/PHUIFormTimerControl.php',
'PHUIFormationColumnDynamicView' => 'view/formation/PHUIFormationColumnDynamicView.php',
'PHUIFormationColumnItem' => 'view/formation/PHUIFormationColumnItem.php',
'PHUIFormationColumnView' => 'view/formation/PHUIFormationColumnView.php',
'PHUIFormationContentView' => 'view/formation/PHUIFormationContentView.php',
'PHUIFormationExpanderView' => 'view/formation/PHUIFormationExpanderView.php',
'PHUIFormationFlankView' => 'view/formation/PHUIFormationFlankView.php',
'PHUIFormationResizerView' => 'view/formation/PHUIFormationResizerView.php',
'PHUIFormationView' => 'view/formation/PHUIFormationView.php',
'PHUIHandleListView' => 'applications/phid/view/PHUIHandleListView.php',
'PHUIHandleTagListView' => 'applications/phid/view/PHUIHandleTagListView.php',
'PHUIHandleView' => 'applications/phid/view/PHUIHandleView.php',
'PHUIHeadThingView' => 'view/phui/PHUIHeadThingView.php',
'PHUIHeaderView' => 'view/phui/PHUIHeaderView.php',
'PHUIHomeView' => 'applications/home/view/PHUIHomeView.php',
'PHUIHovercardUIExample' => 'applications/uiexample/examples/PHUIHovercardUIExample.php',
'PHUIHovercardView' => 'view/phui/PHUIHovercardView.php',
'PHUIIconCircleView' => 'view/phui/PHUIIconCircleView.php',
'PHUIIconExample' => 'applications/uiexample/examples/PHUIIconExample.php',
'PHUIIconView' => 'view/phui/PHUIIconView.php',
'PHUIImageMaskExample' => 'applications/uiexample/examples/PHUIImageMaskExample.php',
'PHUIImageMaskView' => 'view/phui/PHUIImageMaskView.php',
'PHUIInfoExample' => 'applications/uiexample/examples/PHUIInfoExample.php',
'PHUIInfoView' => 'view/phui/PHUIInfoView.php',
'PHUIInvisibleCharacterTestCase' => 'view/phui/__tests__/PHUIInvisibleCharacterTestCase.php',
'PHUIInvisibleCharacterView' => 'view/phui/PHUIInvisibleCharacterView.php',
'PHUILauncherView' => 'view/phui/PHUILauncherView.php',
'PHUILeftRightExample' => 'applications/uiexample/examples/PHUILeftRightExample.php',
'PHUILeftRightView' => 'view/phui/PHUILeftRightView.php',
'PHUILinkView' => 'view/phui/PHUILinkView.php',
'PHUIListExample' => 'applications/uiexample/examples/PHUIListExample.php',
'PHUIListItemView' => 'view/phui/PHUIListItemView.php',
'PHUIListView' => 'view/phui/PHUIListView.php',
'PHUIListViewTestCase' => 'view/layout/__tests__/PHUIListViewTestCase.php',
'PHUIObjectBoxView' => 'view/phui/PHUIObjectBoxView.php',
'PHUIObjectItemListExample' => 'applications/uiexample/examples/PHUIObjectItemListExample.php',
'PHUIObjectItemListView' => 'view/phui/PHUIObjectItemListView.php',
'PHUIObjectItemView' => 'view/phui/PHUIObjectItemView.php',
'PHUIPagerView' => 'view/phui/PHUIPagerView.php',
'PHUIPinboardItemView' => 'view/phui/PHUIPinboardItemView.php',
'PHUIPinboardView' => 'view/phui/PHUIPinboardView.php',
'PHUIPolicySectionView' => 'applications/policy/view/PHUIPolicySectionView.php',
'PHUIPropertyGroupView' => 'view/phui/PHUIPropertyGroupView.php',
'PHUIPropertyListExample' => 'applications/uiexample/examples/PHUIPropertyListExample.php',
'PHUIPropertyListView' => 'view/phui/PHUIPropertyListView.php',
'PHUIRemarkupImageView' => 'infrastructure/markup/view/PHUIRemarkupImageView.php',
'PHUIRemarkupPreviewPanel' => 'view/phui/PHUIRemarkupPreviewPanel.php',
'PHUIRemarkupView' => 'infrastructure/markup/view/PHUIRemarkupView.php',
'PHUISegmentBarSegmentView' => 'view/phui/PHUISegmentBarSegmentView.php',
'PHUISegmentBarView' => 'view/phui/PHUISegmentBarView.php',
'PHUISpacesNamespaceContextView' => 'applications/spaces/view/PHUISpacesNamespaceContextView.php',
'PHUIStatusItemView' => 'view/phui/PHUIStatusItemView.php',
'PHUIStatusListView' => 'view/phui/PHUIStatusListView.php',
'PHUITabGroupView' => 'view/phui/PHUITabGroupView.php',
'PHUITabView' => 'view/phui/PHUITabView.php',
'PHUITagExample' => 'applications/uiexample/examples/PHUITagExample.php',
'PHUITagView' => 'view/phui/PHUITagView.php',
'PHUITimelineEventView' => 'view/phui/PHUITimelineEventView.php',
'PHUITimelineExample' => 'applications/uiexample/examples/PHUITimelineExample.php',
'PHUITimelineView' => 'view/phui/PHUITimelineView.php',
'PHUITwoColumnView' => 'view/phui/PHUITwoColumnView.php',
'PHUITypeaheadExample' => 'applications/uiexample/examples/PHUITypeaheadExample.php',
'PHUIUserAvailabilityView' => 'applications/calendar/view/PHUIUserAvailabilityView.php',
'PHUIWorkboardView' => 'view/phui/PHUIWorkboardView.php',
'PHUIWorkpanelView' => 'view/phui/PHUIWorkpanelView.php',
'PHUIXComponentsExample' => 'applications/uiexample/examples/PHUIXComponentsExample.php',
'PassphraseAbstractKey' => 'applications/passphrase/keys/PassphraseAbstractKey.php',
'PassphraseConduitAPIMethod' => 'applications/passphrase/conduit/PassphraseConduitAPIMethod.php',
'PassphraseController' => 'applications/passphrase/controller/PassphraseController.php',
'PassphraseCredential' => 'applications/passphrase/storage/PassphraseCredential.php',
'PassphraseCredentialAuthorPolicyRule' => 'applications/passphrase/policyrule/PassphraseCredentialAuthorPolicyRule.php',
'PassphraseCredentialConduitController' => 'applications/passphrase/controller/PassphraseCredentialConduitController.php',
'PassphraseCredentialConduitTransaction' => 'applications/passphrase/xaction/PassphraseCredentialConduitTransaction.php',
'PassphraseCredentialControl' => 'applications/passphrase/view/PassphraseCredentialControl.php',
'PassphraseCredentialCreateController' => 'applications/passphrase/controller/PassphraseCredentialCreateController.php',
'PassphraseCredentialDescriptionTransaction' => 'applications/passphrase/xaction/PassphraseCredentialDescriptionTransaction.php',
'PassphraseCredentialDestroyController' => 'applications/passphrase/controller/PassphraseCredentialDestroyController.php',
'PassphraseCredentialDestroyTransaction' => 'applications/passphrase/xaction/PassphraseCredentialDestroyTransaction.php',
'PassphraseCredentialEditController' => 'applications/passphrase/controller/PassphraseCredentialEditController.php',
'PassphraseCredentialFerretEngine' => 'applications/passphrase/search/PassphraseCredentialFerretEngine.php',
'PassphraseCredentialFulltextEngine' => 'applications/passphrase/search/PassphraseCredentialFulltextEngine.php',
'PassphraseCredentialListController' => 'applications/passphrase/controller/PassphraseCredentialListController.php',
'PassphraseCredentialLockController' => 'applications/passphrase/controller/PassphraseCredentialLockController.php',
'PassphraseCredentialLockTransaction' => 'applications/passphrase/xaction/PassphraseCredentialLockTransaction.php',
'PassphraseCredentialLookedAtTransaction' => 'applications/passphrase/xaction/PassphraseCredentialLookedAtTransaction.php',
'PassphraseCredentialNameTransaction' => 'applications/passphrase/xaction/PassphraseCredentialNameTransaction.php',
'PassphraseCredentialPHIDType' => 'applications/passphrase/phid/PassphraseCredentialPHIDType.php',
'PassphraseCredentialPublicController' => 'applications/passphrase/controller/PassphraseCredentialPublicController.php',
'PassphraseCredentialQuery' => 'applications/passphrase/query/PassphraseCredentialQuery.php',
'PassphraseCredentialRevealController' => 'applications/passphrase/controller/PassphraseCredentialRevealController.php',
'PassphraseCredentialSearchEngine' => 'applications/passphrase/query/PassphraseCredentialSearchEngine.php',
'PassphraseCredentialSecretIDTransaction' => 'applications/passphrase/xaction/PassphraseCredentialSecretIDTransaction.php',
'PassphraseCredentialTransaction' => 'applications/passphrase/storage/PassphraseCredentialTransaction.php',
'PassphraseCredentialTransactionEditor' => 'applications/passphrase/editor/PassphraseCredentialTransactionEditor.php',
'PassphraseCredentialTransactionQuery' => 'applications/passphrase/query/PassphraseCredentialTransactionQuery.php',
'PassphraseCredentialTransactionType' => 'applications/passphrase/xaction/PassphraseCredentialTransactionType.php',
'PassphraseCredentialType' => 'applications/passphrase/credentialtype/PassphraseCredentialType.php',
'PassphraseCredentialTypeTestCase' => 'applications/passphrase/credentialtype/__tests__/PassphraseCredentialTypeTestCase.php',
'PassphraseCredentialUsernameTransaction' => 'applications/passphrase/xaction/PassphraseCredentialUsernameTransaction.php',
'PassphraseCredentialViewController' => 'applications/passphrase/controller/PassphraseCredentialViewController.php',
'PassphraseDAO' => 'applications/passphrase/storage/PassphraseDAO.php',
'PassphraseDefaultEditCapability' => 'applications/passphrase/capability/PassphraseDefaultEditCapability.php',
'PassphraseDefaultViewCapability' => 'applications/passphrase/capability/PassphraseDefaultViewCapability.php',
'PassphraseNoteCredentialType' => 'applications/passphrase/credentialtype/PassphraseNoteCredentialType.php',
'PassphrasePasswordCredentialType' => 'applications/passphrase/credentialtype/PassphrasePasswordCredentialType.php',
'PassphrasePasswordKey' => 'applications/passphrase/keys/PassphrasePasswordKey.php',
'PassphraseQueryConduitAPIMethod' => 'applications/passphrase/conduit/PassphraseQueryConduitAPIMethod.php',
'PassphraseRemarkupRule' => 'applications/passphrase/remarkup/PassphraseRemarkupRule.php',
'PassphraseSSHGeneratedKeyCredentialType' => 'applications/passphrase/credentialtype/PassphraseSSHGeneratedKeyCredentialType.php',
'PassphraseSSHKey' => 'applications/passphrase/keys/PassphraseSSHKey.php',
'PassphraseSSHPrivateKeyCredentialType' => 'applications/passphrase/credentialtype/PassphraseSSHPrivateKeyCredentialType.php',
'PassphraseSSHPrivateKeyFileCredentialType' => 'applications/passphrase/credentialtype/PassphraseSSHPrivateKeyFileCredentialType.php',
'PassphraseSSHPrivateKeyTextCredentialType' => 'applications/passphrase/credentialtype/PassphraseSSHPrivateKeyTextCredentialType.php',
'PassphraseSchemaSpec' => 'applications/passphrase/storage/PassphraseSchemaSpec.php',
'PassphraseSecret' => 'applications/passphrase/storage/PassphraseSecret.php',
'PassphraseTokenCredentialType' => 'applications/passphrase/credentialtype/PassphraseTokenCredentialType.php',
'PasteConduitAPIMethod' => 'applications/paste/conduit/PasteConduitAPIMethod.php',
'PasteCreateConduitAPIMethod' => 'applications/paste/conduit/PasteCreateConduitAPIMethod.php',
'PasteCreateMailReceiver' => 'applications/paste/mail/PasteCreateMailReceiver.php',
'PasteDefaultEditCapability' => 'applications/paste/capability/PasteDefaultEditCapability.php',
'PasteDefaultViewCapability' => 'applications/paste/capability/PasteDefaultViewCapability.php',
'PasteEditConduitAPIMethod' => 'applications/paste/conduit/PasteEditConduitAPIMethod.php',
'PasteEmbedView' => 'applications/paste/view/PasteEmbedView.php',
'PasteInfoConduitAPIMethod' => 'applications/paste/conduit/PasteInfoConduitAPIMethod.php',
'PasteLanguageSelectDatasource' => 'applications/paste/typeahead/PasteLanguageSelectDatasource.php',
'PasteMailReceiver' => 'applications/paste/mail/PasteMailReceiver.php',
'PasteQueryConduitAPIMethod' => 'applications/paste/conduit/PasteQueryConduitAPIMethod.php',
'PasteReplyHandler' => 'applications/paste/mail/PasteReplyHandler.php',
'PasteSearchConduitAPIMethod' => 'applications/paste/conduit/PasteSearchConduitAPIMethod.php',
'PeopleBrowseUserDirectoryCapability' => 'applications/people/capability/PeopleBrowseUserDirectoryCapability.php',
'PeopleCreateUsersCapability' => 'applications/people/capability/PeopleCreateUsersCapability.php',
'PeopleDisableUsersCapability' => 'applications/people/capability/PeopleDisableUsersCapability.php',
'PeopleHovercardEngineExtension' => 'applications/people/engineextension/PeopleHovercardEngineExtension.php',
'PeopleMainMenuBarExtension' => 'applications/people/engineextension/PeopleMainMenuBarExtension.php',
'PeopleUserLogGarbageCollector' => 'applications/people/garbagecollector/PeopleUserLogGarbageCollector.php',
'Phabricator404Controller' => 'applications/base/controller/Phabricator404Controller.php',
'PhabricatorAWSConfigOptions' => 'applications/config/option/PhabricatorAWSConfigOptions.php',
'PhabricatorAWSSESFuture' => 'applications/metamta/future/PhabricatorAWSSESFuture.php',
'PhabricatorAccessControlTestCase' => 'applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php',
'PhabricatorAccessLog' => 'infrastructure/log/PhabricatorAccessLog.php',
'PhabricatorAccessLogConfigOptions' => 'applications/config/option/PhabricatorAccessLogConfigOptions.php',
'PhabricatorAccessibilitySetting' => 'applications/settings/setting/PhabricatorAccessibilitySetting.php',
'PhabricatorAccumulateChartFunction' => 'applications/fact/chart/PhabricatorAccumulateChartFunction.php',
'PhabricatorActionListView' => 'view/layout/PhabricatorActionListView.php',
'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php',
'PhabricatorActivitySettingsPanel' => 'applications/settings/panel/PhabricatorActivitySettingsPanel.php',
'PhabricatorAddEmailUserLogType' => 'applications/people/userlog/PhabricatorAddEmailUserLogType.php',
'PhabricatorAddMultifactorUserLogType' => 'applications/people/userlog/PhabricatorAddMultifactorUserLogType.php',
'PhabricatorAdministratorsPolicyRule' => 'applications/people/policyrule/PhabricatorAdministratorsPolicyRule.php',
'PhabricatorAjaxRequestExceptionHandler' => 'aphront/handler/PhabricatorAjaxRequestExceptionHandler.php',
'PhabricatorAlmanacApplication' => 'applications/almanac/application/PhabricatorAlmanacApplication.php',
'PhabricatorAmazonAuthProvider' => 'applications/auth/provider/PhabricatorAmazonAuthProvider.php',
'PhabricatorAmazonSNSFuture' => 'applications/metamta/future/PhabricatorAmazonSNSFuture.php',
'PhabricatorAnchorTestCase' => 'infrastructure/markup/__tests__/PhabricatorAnchorTestCase.php',
'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php',
'PhabricatorAphlictManagementDebugWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementDebugWorkflow.php',
'PhabricatorAphlictManagementNotifyWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementNotifyWorkflow.php',
'PhabricatorAphlictManagementRestartWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementRestartWorkflow.php',
'PhabricatorAphlictManagementStartWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementStartWorkflow.php',
'PhabricatorAphlictManagementStatusWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementStatusWorkflow.php',
'PhabricatorAphlictManagementStopWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementStopWorkflow.php',
'PhabricatorAphlictManagementWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementWorkflow.php',
'PhabricatorAphlictSetupCheck' => 'applications/notification/setup/PhabricatorAphlictSetupCheck.php',
'PhabricatorAphrontBarUIExample' => 'applications/uiexample/examples/PhabricatorAphrontBarUIExample.php',
'PhabricatorAphrontViewTestCase' => 'view/__tests__/PhabricatorAphrontViewTestCase.php',
'PhabricatorAppSearchEngine' => 'applications/meta/query/PhabricatorAppSearchEngine.php',
'PhabricatorApplication' => 'applications/base/PhabricatorApplication.php',
'PhabricatorApplicationApplicationPHIDType' => 'applications/meta/phid/PhabricatorApplicationApplicationPHIDType.php',
'PhabricatorApplicationApplicationTransaction' => 'applications/meta/storage/PhabricatorApplicationApplicationTransaction.php',
'PhabricatorApplicationApplicationTransactionQuery' => 'applications/meta/query/PhabricatorApplicationApplicationTransactionQuery.php',
'PhabricatorApplicationConfigOptions' => 'applications/config/option/PhabricatorApplicationConfigOptions.php',
'PhabricatorApplicationConfigurationPanel' => 'applications/meta/panel/PhabricatorApplicationConfigurationPanel.php',
'PhabricatorApplicationConfigurationPanelTestCase' => 'applications/meta/panel/__tests__/PhabricatorApplicationConfigurationPanelTestCase.php',
'PhabricatorApplicationDatasource' => 'applications/meta/typeahead/PhabricatorApplicationDatasource.php',
'PhabricatorApplicationDetailViewController' => 'applications/meta/controller/PhabricatorApplicationDetailViewController.php',
'PhabricatorApplicationEditController' => 'applications/meta/controller/PhabricatorApplicationEditController.php',
'PhabricatorApplicationEditEngine' => 'applications/meta/editor/PhabricatorApplicationEditEngine.php',
'PhabricatorApplicationEditHTTPParameterHelpView' => 'applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php',
'PhabricatorApplicationEditor' => 'applications/meta/editor/PhabricatorApplicationEditor.php',
'PhabricatorApplicationEmailCommandsController' => 'applications/meta/controller/PhabricatorApplicationEmailCommandsController.php',
'PhabricatorApplicationMailReceiver' => 'applications/metamta/receiver/PhabricatorApplicationMailReceiver.php',
'PhabricatorApplicationObjectMailEngineExtension' => 'applications/transactions/engineextension/PhabricatorApplicationObjectMailEngineExtension.php',
'PhabricatorApplicationPanelController' => 'applications/meta/controller/PhabricatorApplicationPanelController.php',
'PhabricatorApplicationPolicyChangeTransaction' => 'applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php',
'PhabricatorApplicationProfileMenuItem' => 'applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php',
'PhabricatorApplicationQuery' => 'applications/meta/query/PhabricatorApplicationQuery.php',
'PhabricatorApplicationSchemaSpec' => 'applications/meta/storage/PhabricatorApplicationSchemaSpec.php',
'PhabricatorApplicationSearchController' => 'applications/search/controller/PhabricatorApplicationSearchController.php',
'PhabricatorApplicationSearchEngine' => 'applications/search/engine/PhabricatorApplicationSearchEngine.php',
'PhabricatorApplicationSearchEngineTestCase' => 'applications/search/engine/__tests__/PhabricatorApplicationSearchEngineTestCase.php',
'PhabricatorApplicationSearchResultView' => 'applications/search/view/PhabricatorApplicationSearchResultView.php',
'PhabricatorApplicationTestCase' => 'applications/base/__tests__/PhabricatorApplicationTestCase.php',
'PhabricatorApplicationTransaction' => 'applications/transactions/storage/PhabricatorApplicationTransaction.php',
'PhabricatorApplicationTransactionComment' => 'applications/transactions/storage/PhabricatorApplicationTransactionComment.php',
'PhabricatorApplicationTransactionCommentEditController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentEditController.php',
'PhabricatorApplicationTransactionCommentEditor' => 'applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php',
'PhabricatorApplicationTransactionCommentHistoryController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentHistoryController.php',
'PhabricatorApplicationTransactionCommentQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php',
'PhabricatorApplicationTransactionCommentQuoteController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentQuoteController.php',
'PhabricatorApplicationTransactionCommentRawController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentRawController.php',
'PhabricatorApplicationTransactionCommentRemoveController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentRemoveController.php',
'PhabricatorApplicationTransactionCommentView' => 'applications/transactions/view/PhabricatorApplicationTransactionCommentView.php',
'PhabricatorApplicationTransactionController' => 'applications/transactions/controller/PhabricatorApplicationTransactionController.php',
'PhabricatorApplicationTransactionDetailController' => 'applications/transactions/controller/PhabricatorApplicationTransactionDetailController.php',
'PhabricatorApplicationTransactionDetailView' => 'applications/transactions/view/PhabricatorApplicationTransactionDetailView.php',
'PhabricatorApplicationTransactionEditor' => 'applications/transactions/editor/PhabricatorApplicationTransactionEditor.php',
'PhabricatorApplicationTransactionFeedStory' => 'applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php',
'PhabricatorApplicationTransactionInterface' => 'applications/transactions/interface/PhabricatorApplicationTransactionInterface.php',
'PhabricatorApplicationTransactionJSONDiffDetailView' => 'applications/transactions/view/PhabricatorApplicationTransactionJSONDiffDetailView.php',
'PhabricatorApplicationTransactionNoEffectException' => 'applications/transactions/exception/PhabricatorApplicationTransactionNoEffectException.php',
'PhabricatorApplicationTransactionNoEffectResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionNoEffectResponse.php',
'PhabricatorApplicationTransactionPublishWorker' => 'applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php',
'PhabricatorApplicationTransactionQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionQuery.php',
'PhabricatorApplicationTransactionRemarkupPreviewController' => 'applications/transactions/controller/PhabricatorApplicationTransactionRemarkupPreviewController.php',
'PhabricatorApplicationTransactionReplyHandler' => 'applications/transactions/replyhandler/PhabricatorApplicationTransactionReplyHandler.php',
'PhabricatorApplicationTransactionResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionResponse.php',
'PhabricatorApplicationTransactionShowOlderController' => 'applications/transactions/controller/PhabricatorApplicationTransactionShowOlderController.php',
'PhabricatorApplicationTransactionStructureException' => 'applications/transactions/exception/PhabricatorApplicationTransactionStructureException.php',
'PhabricatorApplicationTransactionTemplatedCommentQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionTemplatedCommentQuery.php',
'PhabricatorApplicationTransactionTextDiffDetailView' => 'applications/transactions/view/PhabricatorApplicationTransactionTextDiffDetailView.php',
'PhabricatorApplicationTransactionTransactionPHIDType' => 'applications/transactions/phid/PhabricatorApplicationTransactionTransactionPHIDType.php',
'PhabricatorApplicationTransactionType' => 'applications/meta/xactions/PhabricatorApplicationTransactionType.php',
'PhabricatorApplicationTransactionValidationError' => 'applications/transactions/error/PhabricatorApplicationTransactionValidationError.php',
'PhabricatorApplicationTransactionValidationException' => 'applications/transactions/exception/PhabricatorApplicationTransactionValidationException.php',
'PhabricatorApplicationTransactionValidationResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionValidationResponse.php',
'PhabricatorApplicationTransactionValueController' => 'applications/transactions/controller/PhabricatorApplicationTransactionValueController.php',
'PhabricatorApplicationTransactionView' => 'applications/transactions/view/PhabricatorApplicationTransactionView.php',
'PhabricatorApplicationTransactionWarningException' => 'applications/transactions/exception/PhabricatorApplicationTransactionWarningException.php',
'PhabricatorApplicationTransactionWarningResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionWarningResponse.php',
'PhabricatorApplicationUninstallController' => 'applications/meta/controller/PhabricatorApplicationUninstallController.php',
'PhabricatorApplicationUninstallTransaction' => 'applications/meta/xactions/PhabricatorApplicationUninstallTransaction.php',
'PhabricatorApplicationsApplication' => 'applications/meta/application/PhabricatorApplicationsApplication.php',
'PhabricatorApplicationsController' => 'applications/meta/controller/PhabricatorApplicationsController.php',
'PhabricatorApplicationsListController' => 'applications/meta/controller/PhabricatorApplicationsListController.php',
'PhabricatorApplyEditField' => 'applications/transactions/editfield/PhabricatorApplyEditField.php',
'PhabricatorAsanaAuthProvider' => 'applications/auth/provider/PhabricatorAsanaAuthProvider.php',
'PhabricatorAsanaConfigOptions' => 'applications/doorkeeper/option/PhabricatorAsanaConfigOptions.php',
'PhabricatorAsanaSubtaskHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorAsanaSubtaskHasObjectEdgeType.php',
'PhabricatorAsanaTaskHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorAsanaTaskHasObjectEdgeType.php',
'PhabricatorAudioDocumentEngine' => 'applications/files/document/PhabricatorAudioDocumentEngine.php',
'PhabricatorAuditActionConstants' => 'applications/audit/constants/PhabricatorAuditActionConstants.php',
'PhabricatorAuditApplication' => 'applications/audit/application/PhabricatorAuditApplication.php',
'PhabricatorAuditCommentEditor' => 'applications/audit/editor/PhabricatorAuditCommentEditor.php',
'PhabricatorAuditController' => 'applications/audit/controller/PhabricatorAuditController.php',
'PhabricatorAuditEditor' => 'applications/audit/editor/PhabricatorAuditEditor.php',
'PhabricatorAuditInlineComment' => 'applications/audit/storage/PhabricatorAuditInlineComment.php',
'PhabricatorAuditMailReceiver' => 'applications/audit/mail/PhabricatorAuditMailReceiver.php',
'PhabricatorAuditManagementDeleteWorkflow' => 'applications/audit/management/PhabricatorAuditManagementDeleteWorkflow.php',
'PhabricatorAuditManagementWorkflow' => 'applications/audit/management/PhabricatorAuditManagementWorkflow.php',
'PhabricatorAuditReplyHandler' => 'applications/audit/mail/PhabricatorAuditReplyHandler.php',
'PhabricatorAuditRequestStatus' => 'applications/audit/constants/PhabricatorAuditRequestStatus.php',
'PhabricatorAuditSynchronizeManagementWorkflow' => 'applications/audit/management/PhabricatorAuditSynchronizeManagementWorkflow.php',
'PhabricatorAuditTransaction' => 'applications/audit/storage/PhabricatorAuditTransaction.php',
'PhabricatorAuditTransactionComment' => 'applications/audit/storage/PhabricatorAuditTransactionComment.php',
'PhabricatorAuditTransactionQuery' => 'applications/audit/query/PhabricatorAuditTransactionQuery.php',
'PhabricatorAuditTransactionView' => 'applications/audit/view/PhabricatorAuditTransactionView.php',
'PhabricatorAuditUpdateOwnersManagementWorkflow' => 'applications/audit/management/PhabricatorAuditUpdateOwnersManagementWorkflow.php',
'PhabricatorAuthAccountView' => 'applications/auth/view/PhabricatorAuthAccountView.php',
'PhabricatorAuthApplication' => 'applications/auth/application/PhabricatorAuthApplication.php',
'PhabricatorAuthAuthFactorPHIDType' => 'applications/auth/phid/PhabricatorAuthAuthFactorPHIDType.php',
'PhabricatorAuthAuthFactorProviderPHIDType' => 'applications/auth/phid/PhabricatorAuthAuthFactorProviderPHIDType.php',
'PhabricatorAuthAuthProviderPHIDType' => 'applications/auth/phid/PhabricatorAuthAuthProviderPHIDType.php',
'PhabricatorAuthCSRFEngine' => 'applications/auth/engine/PhabricatorAuthCSRFEngine.php',
'PhabricatorAuthChallenge' => 'applications/auth/storage/PhabricatorAuthChallenge.php',
'PhabricatorAuthChallengeGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthChallengeGarbageCollector.php',
'PhabricatorAuthChallengePHIDType' => 'applications/auth/phid/PhabricatorAuthChallengePHIDType.php',
'PhabricatorAuthChallengeQuery' => 'applications/auth/query/PhabricatorAuthChallengeQuery.php',
'PhabricatorAuthChallengeStatusController' => 'applications/auth/controller/mfa/PhabricatorAuthChallengeStatusController.php',
'PhabricatorAuthChallengeUpdate' => 'applications/auth/view/PhabricatorAuthChallengeUpdate.php',
'PhabricatorAuthChangePasswordAction' => 'applications/auth/action/PhabricatorAuthChangePasswordAction.php',
'PhabricatorAuthChangeUsernameMessageType' => 'applications/auth/message/PhabricatorAuthChangeUsernameMessageType.php',
'PhabricatorAuthConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthConduitAPIMethod.php',
'PhabricatorAuthConduitTokenRevoker' => 'applications/auth/revoker/PhabricatorAuthConduitTokenRevoker.php',
'PhabricatorAuthConfirmLinkController' => 'applications/auth/controller/PhabricatorAuthConfirmLinkController.php',
'PhabricatorAuthContactNumber' => 'applications/auth/storage/PhabricatorAuthContactNumber.php',
'PhabricatorAuthContactNumberController' => 'applications/auth/controller/contact/PhabricatorAuthContactNumberController.php',
'PhabricatorAuthContactNumberDisableController' => 'applications/auth/controller/contact/PhabricatorAuthContactNumberDisableController.php',
'PhabricatorAuthContactNumberEditController' => 'applications/auth/controller/contact/PhabricatorAuthContactNumberEditController.php',
'PhabricatorAuthContactNumberEditEngine' => 'applications/auth/editor/PhabricatorAuthContactNumberEditEngine.php',
'PhabricatorAuthContactNumberEditor' => 'applications/auth/editor/PhabricatorAuthContactNumberEditor.php',
'PhabricatorAuthContactNumberMFAEngine' => 'applications/auth/engine/PhabricatorAuthContactNumberMFAEngine.php',
'PhabricatorAuthContactNumberNumberTransaction' => 'applications/auth/xaction/PhabricatorAuthContactNumberNumberTransaction.php',
'PhabricatorAuthContactNumberPHIDType' => 'applications/auth/phid/PhabricatorAuthContactNumberPHIDType.php',
'PhabricatorAuthContactNumberPrimaryController' => 'applications/auth/controller/contact/PhabricatorAuthContactNumberPrimaryController.php',
'PhabricatorAuthContactNumberPrimaryTransaction' => 'applications/auth/xaction/PhabricatorAuthContactNumberPrimaryTransaction.php',
'PhabricatorAuthContactNumberQuery' => 'applications/auth/query/PhabricatorAuthContactNumberQuery.php',
'PhabricatorAuthContactNumberStatusTransaction' => 'applications/auth/xaction/PhabricatorAuthContactNumberStatusTransaction.php',
'PhabricatorAuthContactNumberTestController' => 'applications/auth/controller/contact/PhabricatorAuthContactNumberTestController.php',
'PhabricatorAuthContactNumberTransaction' => 'applications/auth/storage/PhabricatorAuthContactNumberTransaction.php',
'PhabricatorAuthContactNumberTransactionQuery' => 'applications/auth/query/PhabricatorAuthContactNumberTransactionQuery.php',
'PhabricatorAuthContactNumberTransactionType' => 'applications/auth/xaction/PhabricatorAuthContactNumberTransactionType.php',
'PhabricatorAuthContactNumberViewController' => 'applications/auth/controller/contact/PhabricatorAuthContactNumberViewController.php',
'PhabricatorAuthController' => 'applications/auth/controller/PhabricatorAuthController.php',
'PhabricatorAuthDAO' => 'applications/auth/storage/PhabricatorAuthDAO.php',
'PhabricatorAuthDisableController' => 'applications/auth/controller/config/PhabricatorAuthDisableController.php',
'PhabricatorAuthDowngradeSessionController' => 'applications/auth/controller/PhabricatorAuthDowngradeSessionController.php',
'PhabricatorAuthEditController' => 'applications/auth/controller/config/PhabricatorAuthEditController.php',
'PhabricatorAuthEmailLoginAction' => 'applications/auth/action/PhabricatorAuthEmailLoginAction.php',
'PhabricatorAuthEmailLoginMessageType' => 'applications/auth/message/PhabricatorAuthEmailLoginMessageType.php',
'PhabricatorAuthEmailSetPasswordMessageType' => 'applications/auth/message/PhabricatorAuthEmailSetPasswordMessageType.php',
'PhabricatorAuthFactor' => 'applications/auth/factor/PhabricatorAuthFactor.php',
'PhabricatorAuthFactorConfig' => 'applications/auth/storage/PhabricatorAuthFactorConfig.php',
'PhabricatorAuthFactorConfigQuery' => 'applications/auth/query/PhabricatorAuthFactorConfigQuery.php',
'PhabricatorAuthFactorProvider' => 'applications/auth/storage/PhabricatorAuthFactorProvider.php',
'PhabricatorAuthFactorProviderController' => 'applications/auth/controller/mfa/PhabricatorAuthFactorProviderController.php',
'PhabricatorAuthFactorProviderDuoCredentialTransaction' => 'applications/auth/xaction/PhabricatorAuthFactorProviderDuoCredentialTransaction.php',
'PhabricatorAuthFactorProviderDuoEnrollTransaction' => 'applications/auth/xaction/PhabricatorAuthFactorProviderDuoEnrollTransaction.php',
'PhabricatorAuthFactorProviderDuoHostnameTransaction' => 'applications/auth/xaction/PhabricatorAuthFactorProviderDuoHostnameTransaction.php',
'PhabricatorAuthFactorProviderDuoUsernamesTransaction' => 'applications/auth/xaction/PhabricatorAuthFactorProviderDuoUsernamesTransaction.php',
'PhabricatorAuthFactorProviderEditController' => 'applications/auth/controller/mfa/PhabricatorAuthFactorProviderEditController.php',
'PhabricatorAuthFactorProviderEditEngine' => 'applications/auth/editor/PhabricatorAuthFactorProviderEditEngine.php',
'PhabricatorAuthFactorProviderEditor' => 'applications/auth/editor/PhabricatorAuthFactorProviderEditor.php',
'PhabricatorAuthFactorProviderEnrollMessageTransaction' => 'applications/auth/xaction/PhabricatorAuthFactorProviderEnrollMessageTransaction.php',
'PhabricatorAuthFactorProviderListController' => 'applications/auth/controller/mfa/PhabricatorAuthFactorProviderListController.php',
'PhabricatorAuthFactorProviderMFAEngine' => 'applications/auth/engine/PhabricatorAuthFactorProviderMFAEngine.php',
'PhabricatorAuthFactorProviderMessageController' => 'applications/auth/controller/mfa/PhabricatorAuthFactorProviderMessageController.php',
'PhabricatorAuthFactorProviderNameTransaction' => 'applications/auth/xaction/PhabricatorAuthFactorProviderNameTransaction.php',
'PhabricatorAuthFactorProviderQuery' => 'applications/auth/query/PhabricatorAuthFactorProviderQuery.php',
'PhabricatorAuthFactorProviderStatus' => 'applications/auth/constants/PhabricatorAuthFactorProviderStatus.php',
'PhabricatorAuthFactorProviderStatusTransaction' => 'applications/auth/xaction/PhabricatorAuthFactorProviderStatusTransaction.php',
'PhabricatorAuthFactorProviderTransaction' => 'applications/auth/storage/PhabricatorAuthFactorProviderTransaction.php',
'PhabricatorAuthFactorProviderTransactionQuery' => 'applications/auth/query/PhabricatorAuthFactorProviderTransactionQuery.php',
'PhabricatorAuthFactorProviderTransactionType' => 'applications/auth/xaction/PhabricatorAuthFactorProviderTransactionType.php',
'PhabricatorAuthFactorProviderViewController' => 'applications/auth/controller/mfa/PhabricatorAuthFactorProviderViewController.php',
'PhabricatorAuthFactorResult' => 'applications/auth/factor/PhabricatorAuthFactorResult.php',
'PhabricatorAuthFactorTestCase' => 'applications/auth/factor/__tests__/PhabricatorAuthFactorTestCase.php',
'PhabricatorAuthFinishController' => 'applications/auth/controller/PhabricatorAuthFinishController.php',
'PhabricatorAuthHMACKey' => 'applications/auth/storage/PhabricatorAuthHMACKey.php',
'PhabricatorAuthHighSecurityRequiredException' => 'applications/auth/exception/PhabricatorAuthHighSecurityRequiredException.php',
'PhabricatorAuthHighSecurityToken' => 'applications/auth/data/PhabricatorAuthHighSecurityToken.php',
'PhabricatorAuthInvite' => 'applications/auth/storage/PhabricatorAuthInvite.php',
'PhabricatorAuthInviteAccountException' => 'applications/auth/exception/PhabricatorAuthInviteAccountException.php',
'PhabricatorAuthInviteAction' => 'applications/auth/data/PhabricatorAuthInviteAction.php',
'PhabricatorAuthInviteActionTableView' => 'applications/auth/view/PhabricatorAuthInviteActionTableView.php',
'PhabricatorAuthInviteController' => 'applications/auth/controller/PhabricatorAuthInviteController.php',
'PhabricatorAuthInviteDialogException' => 'applications/auth/exception/PhabricatorAuthInviteDialogException.php',
'PhabricatorAuthInviteEngine' => 'applications/auth/engine/PhabricatorAuthInviteEngine.php',
'PhabricatorAuthInviteException' => 'applications/auth/exception/PhabricatorAuthInviteException.php',
'PhabricatorAuthInviteInvalidException' => 'applications/auth/exception/PhabricatorAuthInviteInvalidException.php',
'PhabricatorAuthInviteLoginException' => 'applications/auth/exception/PhabricatorAuthInviteLoginException.php',
'PhabricatorAuthInvitePHIDType' => 'applications/auth/phid/PhabricatorAuthInvitePHIDType.php',
'PhabricatorAuthInviteQuery' => 'applications/auth/query/PhabricatorAuthInviteQuery.php',
'PhabricatorAuthInviteRegisteredException' => 'applications/auth/exception/PhabricatorAuthInviteRegisteredException.php',
'PhabricatorAuthInviteSearchEngine' => 'applications/auth/query/PhabricatorAuthInviteSearchEngine.php',
'PhabricatorAuthInviteTestCase' => 'applications/auth/factor/__tests__/PhabricatorAuthInviteTestCase.php',
'PhabricatorAuthInviteVerifyException' => 'applications/auth/exception/PhabricatorAuthInviteVerifyException.php',
'PhabricatorAuthInviteWorker' => 'applications/auth/worker/PhabricatorAuthInviteWorker.php',
'PhabricatorAuthLinkController' => 'applications/auth/controller/PhabricatorAuthLinkController.php',
'PhabricatorAuthLinkMessageType' => 'applications/auth/message/PhabricatorAuthLinkMessageType.php',
'PhabricatorAuthListController' => 'applications/auth/controller/config/PhabricatorAuthListController.php',
'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php',
'PhabricatorAuthLoginMessageType' => 'applications/auth/message/PhabricatorAuthLoginMessageType.php',
'PhabricatorAuthLogoutConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthLogoutConduitAPIMethod.php',
'PhabricatorAuthMFAEditEngineExtension' => 'applications/auth/engineextension/PhabricatorAuthMFAEditEngineExtension.php',
'PhabricatorAuthMFASyncTemporaryTokenType' => 'applications/auth/factor/PhabricatorAuthMFASyncTemporaryTokenType.php',
'PhabricatorAuthMainMenuBarExtension' => 'applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php',
'PhabricatorAuthManagementCachePKCS8Workflow' => 'applications/auth/management/PhabricatorAuthManagementCachePKCS8Workflow.php',
'PhabricatorAuthManagementLDAPWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLDAPWorkflow.php',
'PhabricatorAuthManagementListFactorsWorkflow' => 'applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php',
'PhabricatorAuthManagementListMFAProvidersWorkflow' => 'applications/auth/management/PhabricatorAuthManagementListMFAProvidersWorkflow.php',
'PhabricatorAuthManagementLockWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLockWorkflow.php',
'PhabricatorAuthManagementRecoverWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php',
'PhabricatorAuthManagementRefreshWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRefreshWorkflow.php',
'PhabricatorAuthManagementRevokeWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRevokeWorkflow.php',
'PhabricatorAuthManagementStripWorkflow' => 'applications/auth/management/PhabricatorAuthManagementStripWorkflow.php',
'PhabricatorAuthManagementTrustOAuthClientWorkflow' => 'applications/auth/management/PhabricatorAuthManagementTrustOAuthClientWorkflow.php',
'PhabricatorAuthManagementUnlimitWorkflow' => 'applications/auth/management/PhabricatorAuthManagementUnlimitWorkflow.php',
'PhabricatorAuthManagementUnlockWorkflow' => 'applications/auth/management/PhabricatorAuthManagementUnlockWorkflow.php',
'PhabricatorAuthManagementUntrustOAuthClientWorkflow' => 'applications/auth/management/PhabricatorAuthManagementUntrustOAuthClientWorkflow.php',
'PhabricatorAuthManagementVerifyWorkflow' => 'applications/auth/management/PhabricatorAuthManagementVerifyWorkflow.php',
'PhabricatorAuthManagementWorkflow' => 'applications/auth/management/PhabricatorAuthManagementWorkflow.php',
'PhabricatorAuthMessage' => 'applications/auth/storage/PhabricatorAuthMessage.php',
'PhabricatorAuthMessageController' => 'applications/auth/controller/message/PhabricatorAuthMessageController.php',
'PhabricatorAuthMessageEditController' => 'applications/auth/controller/message/PhabricatorAuthMessageEditController.php',
'PhabricatorAuthMessageEditEngine' => 'applications/auth/editor/PhabricatorAuthMessageEditEngine.php',
'PhabricatorAuthMessageEditor' => 'applications/auth/editor/PhabricatorAuthMessageEditor.php',
'PhabricatorAuthMessageListController' => 'applications/auth/controller/message/PhabricatorAuthMessageListController.php',
'PhabricatorAuthMessagePHIDType' => 'applications/auth/phid/PhabricatorAuthMessagePHIDType.php',
'PhabricatorAuthMessageQuery' => 'applications/auth/query/PhabricatorAuthMessageQuery.php',
'PhabricatorAuthMessageTextTransaction' => 'applications/auth/xaction/PhabricatorAuthMessageTextTransaction.php',
'PhabricatorAuthMessageTransaction' => 'applications/auth/storage/PhabricatorAuthMessageTransaction.php',
'PhabricatorAuthMessageTransactionQuery' => 'applications/auth/query/PhabricatorAuthMessageTransactionQuery.php',
'PhabricatorAuthMessageTransactionType' => 'applications/auth/xaction/PhabricatorAuthMessageTransactionType.php',
'PhabricatorAuthMessageType' => 'applications/auth/message/PhabricatorAuthMessageType.php',
'PhabricatorAuthMessageViewController' => 'applications/auth/controller/message/PhabricatorAuthMessageViewController.php',
'PhabricatorAuthNeedsApprovalController' => 'applications/auth/controller/PhabricatorAuthNeedsApprovalController.php',
'PhabricatorAuthNeedsMultiFactorController' => 'applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php',
'PhabricatorAuthNewController' => 'applications/auth/controller/config/PhabricatorAuthNewController.php',
'PhabricatorAuthNewFactorAction' => 'applications/auth/action/PhabricatorAuthNewFactorAction.php',
'PhabricatorAuthOldOAuthRedirectController' => 'applications/auth/controller/PhabricatorAuthOldOAuthRedirectController.php',
'PhabricatorAuthOneTimeLoginController' => 'applications/auth/controller/PhabricatorAuthOneTimeLoginController.php',
'PhabricatorAuthOneTimeLoginTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php',
'PhabricatorAuthPassword' => 'applications/auth/storage/PhabricatorAuthPassword.php',
'PhabricatorAuthPasswordEditor' => 'applications/auth/editor/PhabricatorAuthPasswordEditor.php',
'PhabricatorAuthPasswordEngine' => 'applications/auth/engine/PhabricatorAuthPasswordEngine.php',
'PhabricatorAuthPasswordException' => 'applications/auth/password/PhabricatorAuthPasswordException.php',
'PhabricatorAuthPasswordHashInterface' => 'applications/auth/password/PhabricatorAuthPasswordHashInterface.php',
'PhabricatorAuthPasswordPHIDType' => 'applications/auth/phid/PhabricatorAuthPasswordPHIDType.php',
'PhabricatorAuthPasswordQuery' => 'applications/auth/query/PhabricatorAuthPasswordQuery.php',
'PhabricatorAuthPasswordResetTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthPasswordResetTemporaryTokenType.php',
'PhabricatorAuthPasswordRevokeTransaction' => 'applications/auth/xaction/PhabricatorAuthPasswordRevokeTransaction.php',
'PhabricatorAuthPasswordRevoker' => 'applications/auth/revoker/PhabricatorAuthPasswordRevoker.php',
'PhabricatorAuthPasswordTestCase' => 'applications/auth/__tests__/PhabricatorAuthPasswordTestCase.php',
'PhabricatorAuthPasswordTransaction' => 'applications/auth/storage/PhabricatorAuthPasswordTransaction.php',
'PhabricatorAuthPasswordTransactionQuery' => 'applications/auth/query/PhabricatorAuthPasswordTransactionQuery.php',
'PhabricatorAuthPasswordTransactionType' => 'applications/auth/xaction/PhabricatorAuthPasswordTransactionType.php',
'PhabricatorAuthPasswordUpgradeTransaction' => 'applications/auth/xaction/PhabricatorAuthPasswordUpgradeTransaction.php',
'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php',
'PhabricatorAuthProviderConfig' => 'applications/auth/storage/PhabricatorAuthProviderConfig.php',
'PhabricatorAuthProviderConfigController' => 'applications/auth/controller/config/PhabricatorAuthProviderConfigController.php',
'PhabricatorAuthProviderConfigEditor' => 'applications/auth/editor/PhabricatorAuthProviderConfigEditor.php',
'PhabricatorAuthProviderConfigQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigQuery.php',
'PhabricatorAuthProviderConfigTransaction' => 'applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php',
'PhabricatorAuthProviderConfigTransactionQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigTransactionQuery.php',
'PhabricatorAuthProviderController' => 'applications/auth/controller/config/PhabricatorAuthProviderController.php',
'PhabricatorAuthProviderViewController' => 'applications/auth/controller/config/PhabricatorAuthProviderViewController.php',
'PhabricatorAuthProvidersGuidanceContext' => 'applications/auth/guidance/PhabricatorAuthProvidersGuidanceContext.php',
'PhabricatorAuthProvidersGuidanceEngineExtension' => 'applications/auth/guidance/PhabricatorAuthProvidersGuidanceEngineExtension.php',
'PhabricatorAuthQueryPublicKeysConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthQueryPublicKeysConduitAPIMethod.php',
'PhabricatorAuthRegisterController' => 'applications/auth/controller/PhabricatorAuthRegisterController.php',
'PhabricatorAuthRevokeTokenController' => 'applications/auth/controller/PhabricatorAuthRevokeTokenController.php',
'PhabricatorAuthRevoker' => 'applications/auth/revoker/PhabricatorAuthRevoker.php',
'PhabricatorAuthSSHKey' => 'applications/auth/storage/PhabricatorAuthSSHKey.php',
'PhabricatorAuthSSHKeyController' => 'applications/auth/controller/PhabricatorAuthSSHKeyController.php',
'PhabricatorAuthSSHKeyEditController' => 'applications/auth/controller/PhabricatorAuthSSHKeyEditController.php',
'PhabricatorAuthSSHKeyEditor' => 'applications/auth/editor/PhabricatorAuthSSHKeyEditor.php',
'PhabricatorAuthSSHKeyGenerateController' => 'applications/auth/controller/PhabricatorAuthSSHKeyGenerateController.php',
'PhabricatorAuthSSHKeyListController' => 'applications/auth/controller/PhabricatorAuthSSHKeyListController.php',
'PhabricatorAuthSSHKeyPHIDType' => 'applications/auth/phid/PhabricatorAuthSSHKeyPHIDType.php',
'PhabricatorAuthSSHKeyQuery' => 'applications/auth/query/PhabricatorAuthSSHKeyQuery.php',
'PhabricatorAuthSSHKeyReplyHandler' => 'applications/auth/mail/PhabricatorAuthSSHKeyReplyHandler.php',
'PhabricatorAuthSSHKeyRevokeController' => 'applications/auth/controller/PhabricatorAuthSSHKeyRevokeController.php',
'PhabricatorAuthSSHKeySearchEngine' => 'applications/auth/query/PhabricatorAuthSSHKeySearchEngine.php',
'PhabricatorAuthSSHKeyTableView' => 'applications/auth/view/PhabricatorAuthSSHKeyTableView.php',
'PhabricatorAuthSSHKeyTestCase' => 'applications/auth/__tests__/PhabricatorAuthSSHKeyTestCase.php',
'PhabricatorAuthSSHKeyTransaction' => 'applications/auth/storage/PhabricatorAuthSSHKeyTransaction.php',
'PhabricatorAuthSSHKeyTransactionQuery' => 'applications/auth/query/PhabricatorAuthSSHKeyTransactionQuery.php',
'PhabricatorAuthSSHKeyViewController' => 'applications/auth/controller/PhabricatorAuthSSHKeyViewController.php',
'PhabricatorAuthSSHPrivateKey' => 'applications/auth/sshkey/PhabricatorAuthSSHPrivateKey.php',
'PhabricatorAuthSSHPrivateKeyException' => 'applications/auth/exception/privatekey/PhabricatorAuthSSHPrivateKeyException.php',
'PhabricatorAuthSSHPrivateKeyFormatException' => 'applications/auth/exception/privatekey/PhabricatorAuthSSHPrivateKeyFormatException.php',
'PhabricatorAuthSSHPrivateKeyIncorrectPassphraseException' => 'applications/auth/exception/privatekey/PhabricatorAuthSSHPrivateKeyIncorrectPassphraseException.php',
'PhabricatorAuthSSHPrivateKeyMissingPassphraseException' => 'applications/auth/exception/privatekey/PhabricatorAuthSSHPrivateKeyMissingPassphraseException.php',
'PhabricatorAuthSSHPrivateKeyPassphraseException' => 'applications/auth/exception/privatekey/PhabricatorAuthSSHPrivateKeyPassphraseException.php',
'PhabricatorAuthSSHPrivateKeySurplusPassphraseException' => 'applications/auth/exception/privatekey/PhabricatorAuthSSHPrivateKeySurplusPassphraseException.php',
'PhabricatorAuthSSHPrivateKeyUnknownException' => 'applications/auth/exception/privatekey/PhabricatorAuthSSHPrivateKeyUnknownException.php',
'PhabricatorAuthSSHPublicKey' => 'applications/auth/sshkey/PhabricatorAuthSSHPublicKey.php',
'PhabricatorAuthSSHRevoker' => 'applications/auth/revoker/PhabricatorAuthSSHRevoker.php',
'PhabricatorAuthSession' => 'applications/auth/storage/PhabricatorAuthSession.php',
'PhabricatorAuthSessionEngine' => 'applications/auth/engine/PhabricatorAuthSessionEngine.php',
'PhabricatorAuthSessionEngineExtension' => 'applications/auth/engine/PhabricatorAuthSessionEngineExtension.php',
'PhabricatorAuthSessionEngineExtensionModule' => 'applications/auth/engine/PhabricatorAuthSessionEngineExtensionModule.php',
'PhabricatorAuthSessionGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php',
'PhabricatorAuthSessionInfo' => 'applications/auth/data/PhabricatorAuthSessionInfo.php',
'PhabricatorAuthSessionPHIDType' => 'applications/auth/phid/PhabricatorAuthSessionPHIDType.php',
'PhabricatorAuthSessionQuery' => 'applications/auth/query/PhabricatorAuthSessionQuery.php',
'PhabricatorAuthSessionRevoker' => 'applications/auth/revoker/PhabricatorAuthSessionRevoker.php',
'PhabricatorAuthSetExternalController' => 'applications/auth/controller/PhabricatorAuthSetExternalController.php',
'PhabricatorAuthSetPasswordController' => 'applications/auth/controller/PhabricatorAuthSetPasswordController.php',
'PhabricatorAuthSetupCheck' => 'applications/config/check/PhabricatorAuthSetupCheck.php',
'PhabricatorAuthStartController' => 'applications/auth/controller/PhabricatorAuthStartController.php',
'PhabricatorAuthTemporaryToken' => 'applications/auth/storage/PhabricatorAuthTemporaryToken.php',
'PhabricatorAuthTemporaryTokenGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthTemporaryTokenGarbageCollector.php',
'PhabricatorAuthTemporaryTokenQuery' => 'applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php',
'PhabricatorAuthTemporaryTokenRevoker' => 'applications/auth/revoker/PhabricatorAuthTemporaryTokenRevoker.php',
'PhabricatorAuthTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthTemporaryTokenType.php',
'PhabricatorAuthTemporaryTokenTypeModule' => 'applications/auth/tokentype/PhabricatorAuthTemporaryTokenTypeModule.php',
'PhabricatorAuthTerminateSessionController' => 'applications/auth/controller/PhabricatorAuthTerminateSessionController.php',
'PhabricatorAuthTestSMSAction' => 'applications/auth/action/PhabricatorAuthTestSMSAction.php',
'PhabricatorAuthTryEmailLoginAction' => 'applications/auth/action/PhabricatorAuthTryEmailLoginAction.php',
'PhabricatorAuthTryFactorAction' => 'applications/auth/action/PhabricatorAuthTryFactorAction.php',
'PhabricatorAuthTryPasswordAction' => 'applications/auth/action/PhabricatorAuthTryPasswordAction.php',
'PhabricatorAuthTryPasswordWithoutCAPTCHAAction' => 'applications/auth/action/PhabricatorAuthTryPasswordWithoutCAPTCHAAction.php',
'PhabricatorAuthUnlinkController' => 'applications/auth/controller/PhabricatorAuthUnlinkController.php',
'PhabricatorAuthValidateController' => 'applications/auth/controller/PhabricatorAuthValidateController.php',
'PhabricatorAuthWaitForApprovalMessageType' => 'applications/auth/message/PhabricatorAuthWaitForApprovalMessageType.php',
'PhabricatorAuthWelcomeMailMessageType' => 'applications/auth/message/PhabricatorAuthWelcomeMailMessageType.php',
'PhabricatorAuthenticationConfigOptions' => 'applications/config/option/PhabricatorAuthenticationConfigOptions.php',
'PhabricatorAutoEventListener' => 'infrastructure/events/PhabricatorAutoEventListener.php',
'PhabricatorBadgesApplication' => 'applications/badges/application/PhabricatorBadgesApplication.php',
'PhabricatorBadgesArchiveController' => 'applications/badges/controller/PhabricatorBadgesArchiveController.php',
'PhabricatorBadgesAward' => 'applications/badges/storage/PhabricatorBadgesAward.php',
'PhabricatorBadgesAwardController' => 'applications/badges/controller/PhabricatorBadgesAwardController.php',
'PhabricatorBadgesAwardQuery' => 'applications/badges/query/PhabricatorBadgesAwardQuery.php',
'PhabricatorBadgesAwardTestDataGenerator' => 'applications/badges/lipsum/PhabricatorBadgesAwardTestDataGenerator.php',
'PhabricatorBadgesBadge' => 'applications/badges/storage/PhabricatorBadgesBadge.php',
'PhabricatorBadgesBadgeAwardTransaction' => 'applications/badges/xaction/PhabricatorBadgesBadgeAwardTransaction.php',
'PhabricatorBadgesBadgeDescriptionTransaction' => 'applications/badges/xaction/PhabricatorBadgesBadgeDescriptionTransaction.php',
'PhabricatorBadgesBadgeFlavorTransaction' => 'applications/badges/xaction/PhabricatorBadgesBadgeFlavorTransaction.php',
'PhabricatorBadgesBadgeIconTransaction' => 'applications/badges/xaction/PhabricatorBadgesBadgeIconTransaction.php',
'PhabricatorBadgesBadgeNameNgrams' => 'applications/badges/storage/PhabricatorBadgesBadgeNameNgrams.php',
'PhabricatorBadgesBadgeNameTransaction' => 'applications/badges/xaction/PhabricatorBadgesBadgeNameTransaction.php',
'PhabricatorBadgesBadgeQualityTransaction' => 'applications/badges/xaction/PhabricatorBadgesBadgeQualityTransaction.php',
'PhabricatorBadgesBadgeRevokeTransaction' => 'applications/badges/xaction/PhabricatorBadgesBadgeRevokeTransaction.php',
'PhabricatorBadgesBadgeStatusTransaction' => 'applications/badges/xaction/PhabricatorBadgesBadgeStatusTransaction.php',
'PhabricatorBadgesBadgeTestDataGenerator' => 'applications/badges/lipsum/PhabricatorBadgesBadgeTestDataGenerator.php',
'PhabricatorBadgesBadgeTransactionType' => 'applications/badges/xaction/PhabricatorBadgesBadgeTransactionType.php',
'PhabricatorBadgesCommentController' => 'applications/badges/controller/PhabricatorBadgesCommentController.php',
'PhabricatorBadgesController' => 'applications/badges/controller/PhabricatorBadgesController.php',
'PhabricatorBadgesCreateCapability' => 'applications/badges/capability/PhabricatorBadgesCreateCapability.php',
'PhabricatorBadgesDAO' => 'applications/badges/storage/PhabricatorBadgesDAO.php',
'PhabricatorBadgesDatasource' => 'applications/badges/typeahead/PhabricatorBadgesDatasource.php',
'PhabricatorBadgesDefaultEditCapability' => 'applications/badges/capability/PhabricatorBadgesDefaultEditCapability.php',
'PhabricatorBadgesEditConduitAPIMethod' => 'applications/badges/conduit/PhabricatorBadgesEditConduitAPIMethod.php',
'PhabricatorBadgesEditController' => 'applications/badges/controller/PhabricatorBadgesEditController.php',
'PhabricatorBadgesEditEngine' => 'applications/badges/editor/PhabricatorBadgesEditEngine.php',
'PhabricatorBadgesEditRecipientsController' => 'applications/badges/controller/PhabricatorBadgesEditRecipientsController.php',
'PhabricatorBadgesEditor' => 'applications/badges/editor/PhabricatorBadgesEditor.php',
'PhabricatorBadgesIconSet' => 'applications/badges/icon/PhabricatorBadgesIconSet.php',
'PhabricatorBadgesListController' => 'applications/badges/controller/PhabricatorBadgesListController.php',
'PhabricatorBadgesLootContextFreeGrammar' => 'applications/badges/lipsum/PhabricatorBadgesLootContextFreeGrammar.php',
'PhabricatorBadgesMailReceiver' => 'applications/badges/mail/PhabricatorBadgesMailReceiver.php',
'PhabricatorBadgesPHIDType' => 'applications/badges/phid/PhabricatorBadgesPHIDType.php',
'PhabricatorBadgesProfileController' => 'applications/badges/controller/PhabricatorBadgesProfileController.php',
'PhabricatorBadgesQuality' => 'applications/badges/constants/PhabricatorBadgesQuality.php',
'PhabricatorBadgesQuery' => 'applications/badges/query/PhabricatorBadgesQuery.php',
'PhabricatorBadgesRecipientsController' => 'applications/badges/controller/PhabricatorBadgesRecipientsController.php',
'PhabricatorBadgesRecipientsListView' => 'applications/badges/view/PhabricatorBadgesRecipientsListView.php',
'PhabricatorBadgesRemoveRecipientsController' => 'applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php',
'PhabricatorBadgesReplyHandler' => 'applications/badges/mail/PhabricatorBadgesReplyHandler.php',
'PhabricatorBadgesSchemaSpec' => 'applications/badges/storage/PhabricatorBadgesSchemaSpec.php',
'PhabricatorBadgesSearchConduitAPIMethod' => 'applications/badges/conduit/PhabricatorBadgesSearchConduitAPIMethod.php',
'PhabricatorBadgesSearchEngine' => 'applications/badges/query/PhabricatorBadgesSearchEngine.php',
'PhabricatorBadgesTransaction' => 'applications/badges/storage/PhabricatorBadgesTransaction.php',
'PhabricatorBadgesTransactionComment' => 'applications/badges/storage/PhabricatorBadgesTransactionComment.php',
'PhabricatorBadgesTransactionQuery' => 'applications/badges/query/PhabricatorBadgesTransactionQuery.php',
'PhabricatorBadgesViewController' => 'applications/badges/controller/PhabricatorBadgesViewController.php',
'PhabricatorBarePageView' => 'view/page/PhabricatorBarePageView.php',
'PhabricatorBaseURISetupCheck' => 'applications/config/check/PhabricatorBaseURISetupCheck.php',
'PhabricatorBcryptPasswordHasher' => 'infrastructure/util/password/PhabricatorBcryptPasswordHasher.php',
'PhabricatorBinariesSetupCheck' => 'applications/config/check/PhabricatorBinariesSetupCheck.php',
'PhabricatorBitbucketAuthProvider' => 'applications/auth/provider/PhabricatorBitbucketAuthProvider.php',
'PhabricatorBoardColumnsSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorBoardColumnsSearchEngineAttachment.php',
'PhabricatorBoardLayoutEngine' => 'applications/project/engine/PhabricatorBoardLayoutEngine.php',
'PhabricatorBoardRenderingEngine' => 'applications/project/engine/PhabricatorBoardRenderingEngine.php',
'PhabricatorBoardResponseEngine' => 'applications/project/engine/PhabricatorBoardResponseEngine.php',
'PhabricatorBoolConfigType' => 'applications/config/type/PhabricatorBoolConfigType.php',
'PhabricatorBoolEditField' => 'applications/transactions/editfield/PhabricatorBoolEditField.php',
'PhabricatorBoolMailStamp' => 'applications/metamta/stamp/PhabricatorBoolMailStamp.php',
'PhabricatorBritishEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorBritishEnglishTranslation.php',
'PhabricatorBuiltinDraftEngine' => 'applications/transactions/draft/PhabricatorBuiltinDraftEngine.php',
'PhabricatorBuiltinFileCachePurger' => 'applications/cache/purger/PhabricatorBuiltinFileCachePurger.php',
'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php',
'PhabricatorBulkContentSource' => 'infrastructure/daemon/contentsource/PhabricatorBulkContentSource.php',
'PhabricatorBulkEditGroup' => 'applications/transactions/bulk/PhabricatorBulkEditGroup.php',
'PhabricatorBulkEngine' => 'applications/transactions/bulk/PhabricatorBulkEngine.php',
'PhabricatorBulkManagementExportWorkflow' => 'applications/transactions/bulk/management/PhabricatorBulkManagementExportWorkflow.php',
'PhabricatorBulkManagementMakeSilentWorkflow' => 'applications/transactions/bulk/management/PhabricatorBulkManagementMakeSilentWorkflow.php',
'PhabricatorBulkManagementWorkflow' => 'applications/transactions/bulk/management/PhabricatorBulkManagementWorkflow.php',
'PhabricatorCSVExportFormat' => 'infrastructure/export/format/PhabricatorCSVExportFormat.php',
'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php',
'PhabricatorCacheEngine' => 'applications/system/engine/PhabricatorCacheEngine.php',
'PhabricatorCacheEngineExtension' => 'applications/system/engine/PhabricatorCacheEngineExtension.php',
'PhabricatorCacheGeneralGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheGeneralGarbageCollector.php',
'PhabricatorCacheManagementPurgeWorkflow' => 'applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php',
'PhabricatorCacheManagementWorkflow' => 'applications/cache/management/PhabricatorCacheManagementWorkflow.php',
'PhabricatorCacheMarkupGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheMarkupGarbageCollector.php',
'PhabricatorCachePurger' => 'applications/cache/purger/PhabricatorCachePurger.php',
'PhabricatorCacheSchemaSpec' => 'applications/cache/storage/PhabricatorCacheSchemaSpec.php',
'PhabricatorCacheSetupCheck' => 'applications/config/check/PhabricatorCacheSetupCheck.php',
'PhabricatorCacheSpec' => 'applications/cache/spec/PhabricatorCacheSpec.php',
'PhabricatorCacheTTLGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheTTLGarbageCollector.php',
'PhabricatorCachedClassMapQuery' => 'applications/cache/PhabricatorCachedClassMapQuery.php',
'PhabricatorCaches' => 'applications/cache/PhabricatorCaches.php',
'PhabricatorCachesTestCase' => 'applications/cache/__tests__/PhabricatorCachesTestCase.php',
'PhabricatorCalendarApplication' => 'applications/calendar/application/PhabricatorCalendarApplication.php',
'PhabricatorCalendarController' => 'applications/calendar/controller/PhabricatorCalendarController.php',
'PhabricatorCalendarDAO' => 'applications/calendar/storage/PhabricatorCalendarDAO.php',
'PhabricatorCalendarEvent' => 'applications/calendar/storage/PhabricatorCalendarEvent.php',
'PhabricatorCalendarEventAcceptTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventAcceptTransaction.php',
'PhabricatorCalendarEventAllDayTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php',
'PhabricatorCalendarEventAvailabilityController' => 'applications/calendar/controller/PhabricatorCalendarEventAvailabilityController.php',
'PhabricatorCalendarEventCancelController' => 'applications/calendar/controller/PhabricatorCalendarEventCancelController.php',
'PhabricatorCalendarEventCancelTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventCancelTransaction.php',
'PhabricatorCalendarEventDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventDateTransaction.php',
'PhabricatorCalendarEventDeclineTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventDeclineTransaction.php',
'PhabricatorCalendarEventDefaultEditCapability' => 'applications/calendar/capability/PhabricatorCalendarEventDefaultEditCapability.php',
'PhabricatorCalendarEventDefaultViewCapability' => 'applications/calendar/capability/PhabricatorCalendarEventDefaultViewCapability.php',
'PhabricatorCalendarEventDescriptionTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventDescriptionTransaction.php',
'PhabricatorCalendarEventDragController' => 'applications/calendar/controller/PhabricatorCalendarEventDragController.php',
'PhabricatorCalendarEventEditConduitAPIMethod' => 'applications/calendar/conduit/PhabricatorCalendarEventEditConduitAPIMethod.php',
'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php',
'PhabricatorCalendarEventEditEngine' => 'applications/calendar/editor/PhabricatorCalendarEventEditEngine.php',
'PhabricatorCalendarEventEditor' => 'applications/calendar/editor/PhabricatorCalendarEventEditor.php',
'PhabricatorCalendarEventEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventEmailCommand.php',
'PhabricatorCalendarEventEndDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php',
'PhabricatorCalendarEventExportController' => 'applications/calendar/controller/PhabricatorCalendarEventExportController.php',
'PhabricatorCalendarEventFerretEngine' => 'applications/calendar/search/PhabricatorCalendarEventFerretEngine.php',
'PhabricatorCalendarEventForkTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventForkTransaction.php',
'PhabricatorCalendarEventFrequencyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php',
'PhabricatorCalendarEventFulltextEngine' => 'applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php',
'PhabricatorCalendarEventHeraldAdapter' => 'applications/calendar/herald/PhabricatorCalendarEventHeraldAdapter.php',
'PhabricatorCalendarEventHeraldField' => 'applications/calendar/herald/PhabricatorCalendarEventHeraldField.php',
'PhabricatorCalendarEventHeraldFieldGroup' => 'applications/calendar/herald/PhabricatorCalendarEventHeraldFieldGroup.php',
'PhabricatorCalendarEventHostPolicyRule' => 'applications/calendar/policyrule/PhabricatorCalendarEventHostPolicyRule.php',
'PhabricatorCalendarEventHostTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventHostTransaction.php',
'PhabricatorCalendarEventIconTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventIconTransaction.php',
'PhabricatorCalendarEventInviteTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php',
'PhabricatorCalendarEventInvitee' => 'applications/calendar/storage/PhabricatorCalendarEventInvitee.php',
'PhabricatorCalendarEventInviteeQuery' => 'applications/calendar/query/PhabricatorCalendarEventInviteeQuery.php',
'PhabricatorCalendarEventInviteesPolicyRule' => 'applications/calendar/policyrule/PhabricatorCalendarEventInviteesPolicyRule.php',
'PhabricatorCalendarEventJoinController' => 'applications/calendar/controller/PhabricatorCalendarEventJoinController.php',
'PhabricatorCalendarEventListController' => 'applications/calendar/controller/PhabricatorCalendarEventListController.php',
'PhabricatorCalendarEventMailReceiver' => 'applications/calendar/mail/PhabricatorCalendarEventMailReceiver.php',
'PhabricatorCalendarEventNameHeraldField' => 'applications/calendar/herald/PhabricatorCalendarEventNameHeraldField.php',
'PhabricatorCalendarEventNameTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventNameTransaction.php',
'PhabricatorCalendarEventNotificationView' => 'applications/calendar/notifications/PhabricatorCalendarEventNotificationView.php',
'PhabricatorCalendarEventPHIDType' => 'applications/calendar/phid/PhabricatorCalendarEventPHIDType.php',
'PhabricatorCalendarEventPolicyCodex' => 'applications/calendar/codex/PhabricatorCalendarEventPolicyCodex.php',
'PhabricatorCalendarEventQuery' => 'applications/calendar/query/PhabricatorCalendarEventQuery.php',
'PhabricatorCalendarEventRSVPEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventRSVPEmailCommand.php',
'PhabricatorCalendarEventRecurringTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventRecurringTransaction.php',
'PhabricatorCalendarEventReplyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventReplyTransaction.php',
'PhabricatorCalendarEventSearchConduitAPIMethod' => 'applications/calendar/conduit/PhabricatorCalendarEventSearchConduitAPIMethod.php',
'PhabricatorCalendarEventSearchEngine' => 'applications/calendar/query/PhabricatorCalendarEventSearchEngine.php',
'PhabricatorCalendarEventStartDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php',
'PhabricatorCalendarEventTransaction' => 'applications/calendar/storage/PhabricatorCalendarEventTransaction.php',
'PhabricatorCalendarEventTransactionComment' => 'applications/calendar/storage/PhabricatorCalendarEventTransactionComment.php',
'PhabricatorCalendarEventTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarEventTransactionQuery.php',
'PhabricatorCalendarEventTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarEventTransactionType.php',
'PhabricatorCalendarEventUntilDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php',
'PhabricatorCalendarEventViewController' => 'applications/calendar/controller/PhabricatorCalendarEventViewController.php',
'PhabricatorCalendarExport' => 'applications/calendar/storage/PhabricatorCalendarExport.php',
'PhabricatorCalendarExportDisableController' => 'applications/calendar/controller/PhabricatorCalendarExportDisableController.php',
'PhabricatorCalendarExportDisableTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportDisableTransaction.php',
'PhabricatorCalendarExportEditController' => 'applications/calendar/controller/PhabricatorCalendarExportEditController.php',
'PhabricatorCalendarExportEditEngine' => 'applications/calendar/editor/PhabricatorCalendarExportEditEngine.php',
'PhabricatorCalendarExportEditor' => 'applications/calendar/editor/PhabricatorCalendarExportEditor.php',
'PhabricatorCalendarExportICSController' => 'applications/calendar/controller/PhabricatorCalendarExportICSController.php',
'PhabricatorCalendarExportListController' => 'applications/calendar/controller/PhabricatorCalendarExportListController.php',
'PhabricatorCalendarExportModeTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportModeTransaction.php',
'PhabricatorCalendarExportNameTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportNameTransaction.php',
'PhabricatorCalendarExportPHIDType' => 'applications/calendar/phid/PhabricatorCalendarExportPHIDType.php',
'PhabricatorCalendarExportQuery' => 'applications/calendar/query/PhabricatorCalendarExportQuery.php',
'PhabricatorCalendarExportQueryKeyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportQueryKeyTransaction.php',
'PhabricatorCalendarExportSearchEngine' => 'applications/calendar/query/PhabricatorCalendarExportSearchEngine.php',
'PhabricatorCalendarExportTransaction' => 'applications/calendar/storage/PhabricatorCalendarExportTransaction.php',
'PhabricatorCalendarExportTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarExportTransactionQuery.php',
'PhabricatorCalendarExportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarExportTransactionType.php',
'PhabricatorCalendarExportViewController' => 'applications/calendar/controller/PhabricatorCalendarExportViewController.php',
'PhabricatorCalendarExternalInvitee' => 'applications/calendar/storage/PhabricatorCalendarExternalInvitee.php',
'PhabricatorCalendarExternalInviteePHIDType' => 'applications/calendar/phid/PhabricatorCalendarExternalInviteePHIDType.php',
'PhabricatorCalendarExternalInviteeQuery' => 'applications/calendar/query/PhabricatorCalendarExternalInviteeQuery.php',
'PhabricatorCalendarICSFileImportEngine' => 'applications/calendar/import/PhabricatorCalendarICSFileImportEngine.php',
'PhabricatorCalendarICSImportEngine' => 'applications/calendar/import/PhabricatorCalendarICSImportEngine.php',
'PhabricatorCalendarICSURIImportEngine' => 'applications/calendar/import/PhabricatorCalendarICSURIImportEngine.php',
'PhabricatorCalendarICSWriter' => 'applications/calendar/util/PhabricatorCalendarICSWriter.php',
'PhabricatorCalendarIconSet' => 'applications/calendar/icon/PhabricatorCalendarIconSet.php',
'PhabricatorCalendarImport' => 'applications/calendar/storage/PhabricatorCalendarImport.php',
'PhabricatorCalendarImportDefaultLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportDefaultLogType.php',
'PhabricatorCalendarImportDeleteController' => 'applications/calendar/controller/PhabricatorCalendarImportDeleteController.php',
'PhabricatorCalendarImportDeleteLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportDeleteLogType.php',
'PhabricatorCalendarImportDeleteTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportDeleteTransaction.php',
'PhabricatorCalendarImportDisableController' => 'applications/calendar/controller/PhabricatorCalendarImportDisableController.php',
'PhabricatorCalendarImportDisableTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportDisableTransaction.php',
'PhabricatorCalendarImportDropController' => 'applications/calendar/controller/PhabricatorCalendarImportDropController.php',
'PhabricatorCalendarImportDuplicateLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportDuplicateLogType.php',
'PhabricatorCalendarImportEditController' => 'applications/calendar/controller/PhabricatorCalendarImportEditController.php',
'PhabricatorCalendarImportEditEngine' => 'applications/calendar/editor/PhabricatorCalendarImportEditEngine.php',
'PhabricatorCalendarImportEditor' => 'applications/calendar/editor/PhabricatorCalendarImportEditor.php',
'PhabricatorCalendarImportEmptyLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportEmptyLogType.php',
'PhabricatorCalendarImportEngine' => 'applications/calendar/import/PhabricatorCalendarImportEngine.php',
'PhabricatorCalendarImportEpochLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportEpochLogType.php',
'PhabricatorCalendarImportFetchLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportFetchLogType.php',
'PhabricatorCalendarImportFrequencyLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportFrequencyLogType.php',
'PhabricatorCalendarImportFrequencyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportFrequencyTransaction.php',
'PhabricatorCalendarImportICSFileTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php',
'PhabricatorCalendarImportICSLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportICSLogType.php',
'PhabricatorCalendarImportICSURITransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportICSURITransaction.php',
'PhabricatorCalendarImportICSWarningLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportICSWarningLogType.php',
'PhabricatorCalendarImportIgnoredNodeLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportIgnoredNodeLogType.php',
'PhabricatorCalendarImportListController' => 'applications/calendar/controller/PhabricatorCalendarImportListController.php',
'PhabricatorCalendarImportLog' => 'applications/calendar/storage/PhabricatorCalendarImportLog.php',
'PhabricatorCalendarImportLogListController' => 'applications/calendar/controller/PhabricatorCalendarImportLogListController.php',
'PhabricatorCalendarImportLogQuery' => 'applications/calendar/query/PhabricatorCalendarImportLogQuery.php',
'PhabricatorCalendarImportLogSearchEngine' => 'applications/calendar/query/PhabricatorCalendarImportLogSearchEngine.php',
'PhabricatorCalendarImportLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportLogType.php',
'PhabricatorCalendarImportLogView' => 'applications/calendar/view/PhabricatorCalendarImportLogView.php',
'PhabricatorCalendarImportNameTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportNameTransaction.php',
'PhabricatorCalendarImportOriginalLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportOriginalLogType.php',
'PhabricatorCalendarImportOrphanLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportOrphanLogType.php',
'PhabricatorCalendarImportPHIDType' => 'applications/calendar/phid/PhabricatorCalendarImportPHIDType.php',
'PhabricatorCalendarImportQuery' => 'applications/calendar/query/PhabricatorCalendarImportQuery.php',
'PhabricatorCalendarImportQueueLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportQueueLogType.php',
'PhabricatorCalendarImportReloadController' => 'applications/calendar/controller/PhabricatorCalendarImportReloadController.php',
'PhabricatorCalendarImportReloadTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportReloadTransaction.php',
'PhabricatorCalendarImportReloadWorker' => 'applications/calendar/worker/PhabricatorCalendarImportReloadWorker.php',
'PhabricatorCalendarImportSearchEngine' => 'applications/calendar/query/PhabricatorCalendarImportSearchEngine.php',
'PhabricatorCalendarImportTransaction' => 'applications/calendar/storage/PhabricatorCalendarImportTransaction.php',
'PhabricatorCalendarImportTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarImportTransactionQuery.php',
'PhabricatorCalendarImportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarImportTransactionType.php',
'PhabricatorCalendarImportTriggerLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportTriggerLogType.php',
'PhabricatorCalendarImportUpdateLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportUpdateLogType.php',
'PhabricatorCalendarImportViewController' => 'applications/calendar/controller/PhabricatorCalendarImportViewController.php',
'PhabricatorCalendarInviteeDatasource' => 'applications/calendar/typeahead/PhabricatorCalendarInviteeDatasource.php',
'PhabricatorCalendarInviteeUserDatasource' => 'applications/calendar/typeahead/PhabricatorCalendarInviteeUserDatasource.php',
'PhabricatorCalendarInviteeViewerFunctionDatasource' => 'applications/calendar/typeahead/PhabricatorCalendarInviteeViewerFunctionDatasource.php',
'PhabricatorCalendarManagementNotifyWorkflow' => 'applications/calendar/management/PhabricatorCalendarManagementNotifyWorkflow.php',
'PhabricatorCalendarManagementReloadWorkflow' => 'applications/calendar/management/PhabricatorCalendarManagementReloadWorkflow.php',
'PhabricatorCalendarManagementWorkflow' => 'applications/calendar/management/PhabricatorCalendarManagementWorkflow.php',
'PhabricatorCalendarNotification' => 'applications/calendar/storage/PhabricatorCalendarNotification.php',
'PhabricatorCalendarNotificationEngine' => 'applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php',
'PhabricatorCalendarRemarkupRule' => 'applications/calendar/remarkup/PhabricatorCalendarRemarkupRule.php',
'PhabricatorCalendarReplyHandler' => 'applications/calendar/mail/PhabricatorCalendarReplyHandler.php',
'PhabricatorCalendarSchemaSpec' => 'applications/calendar/storage/PhabricatorCalendarSchemaSpec.php',
'PhabricatorCelerityApplication' => 'applications/celerity/application/PhabricatorCelerityApplication.php',
'PhabricatorCelerityTestCase' => '__tests__/PhabricatorCelerityTestCase.php',
'PhabricatorChangeParserTestCase' => 'applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php',
'PhabricatorChangePasswordUserLogType' => 'applications/people/userlog/PhabricatorChangePasswordUserLogType.php',
'PhabricatorChangesetCachePurger' => 'applications/cache/purger/PhabricatorChangesetCachePurger.php',
'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php',
'PhabricatorChangesetViewState' => 'infrastructure/diff/viewstate/PhabricatorChangesetViewState.php',
'PhabricatorChangesetViewStateEngine' => 'infrastructure/diff/viewstate/PhabricatorChangesetViewStateEngine.php',
'PhabricatorChartAxis' => 'applications/fact/chart/PhabricatorChartAxis.php',
'PhabricatorChartDataQuery' => 'applications/fact/chart/PhabricatorChartDataQuery.php',
'PhabricatorChartDataset' => 'applications/fact/chart/PhabricatorChartDataset.php',
'PhabricatorChartDisplayData' => 'applications/fact/chart/PhabricatorChartDisplayData.php',
'PhabricatorChartEngine' => 'applications/fact/engine/PhabricatorChartEngine.php',
'PhabricatorChartFunction' => 'applications/fact/chart/PhabricatorChartFunction.php',
'PhabricatorChartFunctionArgument' => 'applications/fact/chart/PhabricatorChartFunctionArgument.php',
'PhabricatorChartFunctionArgumentParser' => 'applications/fact/chart/PhabricatorChartFunctionArgumentParser.php',
'PhabricatorChartFunctionLabel' => 'applications/fact/chart/PhabricatorChartFunctionLabel.php',
'PhabricatorChartInterval' => 'applications/fact/chart/PhabricatorChartInterval.php',
'PhabricatorChartRenderingEngine' => 'applications/fact/engine/PhabricatorChartRenderingEngine.php',
'PhabricatorChartStackedAreaDataset' => 'applications/fact/chart/PhabricatorChartStackedAreaDataset.php',
'PhabricatorChatLogApplication' => 'applications/chatlog/application/PhabricatorChatLogApplication.php',
'PhabricatorChatLogChannel' => 'applications/chatlog/storage/PhabricatorChatLogChannel.php',
'PhabricatorChatLogChannelListController' => 'applications/chatlog/controller/PhabricatorChatLogChannelListController.php',
'PhabricatorChatLogChannelLogController' => 'applications/chatlog/controller/PhabricatorChatLogChannelLogController.php',
'PhabricatorChatLogChannelQuery' => 'applications/chatlog/query/PhabricatorChatLogChannelQuery.php',
'PhabricatorChatLogController' => 'applications/chatlog/controller/PhabricatorChatLogController.php',
'PhabricatorChatLogDAO' => 'applications/chatlog/storage/PhabricatorChatLogDAO.php',
'PhabricatorChatLogEvent' => 'applications/chatlog/storage/PhabricatorChatLogEvent.php',
'PhabricatorChatLogQuery' => 'applications/chatlog/query/PhabricatorChatLogQuery.php',
'PhabricatorCheckboxesEditField' => 'applications/transactions/editfield/PhabricatorCheckboxesEditField.php',
'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php',
'PhabricatorClassConfigType' => 'applications/config/type/PhabricatorClassConfigType.php',
'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php',
'PhabricatorClusterDatabasesConfigType' => 'infrastructure/cluster/config/PhabricatorClusterDatabasesConfigType.php',
'PhabricatorClusterException' => 'infrastructure/cluster/exception/PhabricatorClusterException.php',
'PhabricatorClusterExceptionHandler' => 'infrastructure/cluster/exception/PhabricatorClusterExceptionHandler.php',
'PhabricatorClusterImpossibleWriteException' => 'infrastructure/cluster/exception/PhabricatorClusterImpossibleWriteException.php',
'PhabricatorClusterImproperWriteException' => 'infrastructure/cluster/exception/PhabricatorClusterImproperWriteException.php',
'PhabricatorClusterMailersConfigType' => 'infrastructure/cluster/config/PhabricatorClusterMailersConfigType.php',
'PhabricatorClusterNoHostForRoleException' => 'infrastructure/cluster/exception/PhabricatorClusterNoHostForRoleException.php',
'PhabricatorClusterSearchConfigType' => 'infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php',
'PhabricatorClusterServiceHealthRecord' => 'infrastructure/cluster/PhabricatorClusterServiceHealthRecord.php',
'PhabricatorClusterStrandedException' => 'infrastructure/cluster/exception/PhabricatorClusterStrandedException.php',
'PhabricatorColumnProxyInterface' => 'applications/project/interface/PhabricatorColumnProxyInterface.php',
'PhabricatorColumnsEditField' => 'applications/transactions/editfield/PhabricatorColumnsEditField.php',
'PhabricatorCommentEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorCommentEditEngineExtension.php',
'PhabricatorCommentEditField' => 'applications/transactions/editfield/PhabricatorCommentEditField.php',
'PhabricatorCommentEditType' => 'applications/transactions/edittype/PhabricatorCommentEditType.php',
'PhabricatorCommitBranchesField' => 'applications/repository/customfield/PhabricatorCommitBranchesField.php',
'PhabricatorCommitCustomField' => 'applications/repository/customfield/PhabricatorCommitCustomField.php',
'PhabricatorCommitMergedCommitsField' => 'applications/repository/customfield/PhabricatorCommitMergedCommitsField.php',
'PhabricatorCommitRepositoryField' => 'applications/repository/customfield/PhabricatorCommitRepositoryField.php',
'PhabricatorCommitSearchEngine' => 'applications/audit/query/PhabricatorCommitSearchEngine.php',
'PhabricatorCommitTagsField' => 'applications/repository/customfield/PhabricatorCommitTagsField.php',
'PhabricatorCommonPasswords' => 'applications/auth/constants/PhabricatorCommonPasswords.php',
'PhabricatorComposeChartFunction' => 'applications/fact/chart/PhabricatorComposeChartFunction.php',
'PhabricatorConduitAPIController' => 'applications/conduit/controller/PhabricatorConduitAPIController.php',
'PhabricatorConduitApplication' => 'applications/conduit/application/PhabricatorConduitApplication.php',
'PhabricatorConduitCallManagementWorkflow' => 'applications/conduit/management/PhabricatorConduitCallManagementWorkflow.php',
'PhabricatorConduitCertificateFailureUserLogType' => 'applications/people/userlog/PhabricatorConduitCertificateFailureUserLogType.php',
'PhabricatorConduitCertificateToken' => 'applications/conduit/storage/PhabricatorConduitCertificateToken.php',
'PhabricatorConduitCertificateUserLogType' => 'applications/people/userlog/PhabricatorConduitCertificateUserLogType.php',
'PhabricatorConduitConsoleController' => 'applications/conduit/controller/PhabricatorConduitConsoleController.php',
'PhabricatorConduitContentSource' => 'infrastructure/contentsource/PhabricatorConduitContentSource.php',
'PhabricatorConduitController' => 'applications/conduit/controller/PhabricatorConduitController.php',
'PhabricatorConduitDAO' => 'applications/conduit/storage/PhabricatorConduitDAO.php',
'PhabricatorConduitEditField' => 'applications/transactions/editfield/PhabricatorConduitEditField.php',
'PhabricatorConduitListController' => 'applications/conduit/controller/PhabricatorConduitListController.php',
'PhabricatorConduitLogController' => 'applications/conduit/controller/PhabricatorConduitLogController.php',
'PhabricatorConduitLogQuery' => 'applications/conduit/query/PhabricatorConduitLogQuery.php',
'PhabricatorConduitLogSearchEngine' => 'applications/conduit/query/PhabricatorConduitLogSearchEngine.php',
'PhabricatorConduitManagementWorkflow' => 'applications/conduit/management/PhabricatorConduitManagementWorkflow.php',
'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/PhabricatorConduitMethodCallLog.php',
'PhabricatorConduitMethodQuery' => 'applications/conduit/query/PhabricatorConduitMethodQuery.php',
'PhabricatorConduitRequestExceptionHandler' => 'aphront/handler/PhabricatorConduitRequestExceptionHandler.php',
'PhabricatorConduitResultInterface' => 'applications/conduit/interface/PhabricatorConduitResultInterface.php',
'PhabricatorConduitSearchEngine' => 'applications/conduit/query/PhabricatorConduitSearchEngine.php',
'PhabricatorConduitSearchFieldSpecification' => 'applications/conduit/interface/PhabricatorConduitSearchFieldSpecification.php',
'PhabricatorConduitTestCase' => '__tests__/PhabricatorConduitTestCase.php',
'PhabricatorConduitToken' => 'applications/conduit/storage/PhabricatorConduitToken.php',
'PhabricatorConduitTokenController' => 'applications/conduit/controller/PhabricatorConduitTokenController.php',
'PhabricatorConduitTokenEditController' => 'applications/conduit/controller/PhabricatorConduitTokenEditController.php',
'PhabricatorConduitTokenHandshakeController' => 'applications/conduit/controller/PhabricatorConduitTokenHandshakeController.php',
'PhabricatorConduitTokenQuery' => 'applications/conduit/query/PhabricatorConduitTokenQuery.php',
'PhabricatorConduitTokenTerminateController' => 'applications/conduit/controller/PhabricatorConduitTokenTerminateController.php',
'PhabricatorConduitTokensSettingsPanel' => 'applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php',
'PhabricatorConfigApplication' => 'applications/config/application/PhabricatorConfigApplication.php',
'PhabricatorConfigCacheController' => 'applications/config/controller/services/PhabricatorConfigCacheController.php',
'PhabricatorConfigClusterDatabasesController' => 'applications/config/controller/services/PhabricatorConfigClusterDatabasesController.php',
'PhabricatorConfigClusterNotificationsController' => 'applications/config/controller/services/PhabricatorConfigClusterNotificationsController.php',
'PhabricatorConfigClusterRepositoriesController' => 'applications/config/controller/services/PhabricatorConfigClusterRepositoriesController.php',
'PhabricatorConfigClusterSearchController' => 'applications/config/controller/services/PhabricatorConfigClusterSearchController.php',
'PhabricatorConfigCollectorsModule' => 'applications/config/module/PhabricatorConfigCollectorsModule.php',
'PhabricatorConfigColumnSchema' => 'applications/config/schema/PhabricatorConfigColumnSchema.php',
'PhabricatorConfigConfigPHIDType' => 'applications/config/phid/PhabricatorConfigConfigPHIDType.php',
'PhabricatorConfigConsoleController' => 'applications/config/controller/PhabricatorConfigConsoleController.php',
'PhabricatorConfigConstants' => 'applications/config/constants/PhabricatorConfigConstants.php',
'PhabricatorConfigController' => 'applications/config/controller/PhabricatorConfigController.php',
'PhabricatorConfigCoreSchemaSpec' => 'applications/config/schema/PhabricatorConfigCoreSchemaSpec.php',
'PhabricatorConfigDatabaseController' => 'applications/config/controller/services/PhabricatorConfigDatabaseController.php',
'PhabricatorConfigDatabaseIssueController' => 'applications/config/controller/services/PhabricatorConfigDatabaseIssueController.php',
'PhabricatorConfigDatabaseSchema' => 'applications/config/schema/PhabricatorConfigDatabaseSchema.php',
'PhabricatorConfigDatabaseSource' => 'infrastructure/env/PhabricatorConfigDatabaseSource.php',
'PhabricatorConfigDatabaseStatusController' => 'applications/config/controller/services/PhabricatorConfigDatabaseStatusController.php',
'PhabricatorConfigDefaultSource' => 'infrastructure/env/PhabricatorConfigDefaultSource.php',
'PhabricatorConfigDictionarySource' => 'infrastructure/env/PhabricatorConfigDictionarySource.php',
'PhabricatorConfigEdgeModule' => 'applications/config/module/PhabricatorConfigEdgeModule.php',
'PhabricatorConfigEditController' => 'applications/config/controller/settings/PhabricatorConfigEditController.php',
'PhabricatorConfigEditor' => 'applications/config/editor/PhabricatorConfigEditor.php',
'PhabricatorConfigEntry' => 'applications/config/storage/PhabricatorConfigEntry.php',
'PhabricatorConfigEntryDAO' => 'applications/config/storage/PhabricatorConfigEntryDAO.php',
'PhabricatorConfigEntryQuery' => 'applications/config/query/PhabricatorConfigEntryQuery.php',
'PhabricatorConfigFileSource' => 'infrastructure/env/PhabricatorConfigFileSource.php',
'PhabricatorConfigGroupConstants' => 'applications/config/constants/PhabricatorConfigGroupConstants.php',
'PhabricatorConfigHTTPParameterTypesModule' => 'applications/config/module/PhabricatorConfigHTTPParameterTypesModule.php',
'PhabricatorConfigIgnoreController' => 'applications/config/controller/issue/PhabricatorConfigIgnoreController.php',
'PhabricatorConfigIssueListController' => 'applications/config/controller/issue/PhabricatorConfigIssueListController.php',
'PhabricatorConfigIssuePanelController' => 'applications/config/controller/issue/PhabricatorConfigIssuePanelController.php',
'PhabricatorConfigIssueViewController' => 'applications/config/controller/issue/PhabricatorConfigIssueViewController.php',
'PhabricatorConfigJSON' => 'applications/config/json/PhabricatorConfigJSON.php',
'PhabricatorConfigJSONOptionType' => 'applications/config/custom/PhabricatorConfigJSONOptionType.php',
'PhabricatorConfigKeySchema' => 'applications/config/schema/PhabricatorConfigKeySchema.php',
'PhabricatorConfigLocalSource' => 'infrastructure/env/PhabricatorConfigLocalSource.php',
'PhabricatorConfigManagementDeleteWorkflow' => 'applications/config/management/PhabricatorConfigManagementDeleteWorkflow.php',
'PhabricatorConfigManagementDoneWorkflow' => 'applications/config/management/PhabricatorConfigManagementDoneWorkflow.php',
'PhabricatorConfigManagementGetWorkflow' => 'applications/config/management/PhabricatorConfigManagementGetWorkflow.php',
'PhabricatorConfigManagementListWorkflow' => 'applications/config/management/PhabricatorConfigManagementListWorkflow.php',
'PhabricatorConfigManagementMigrateWorkflow' => 'applications/config/management/PhabricatorConfigManagementMigrateWorkflow.php',
'PhabricatorConfigManagementSetWorkflow' => 'applications/config/management/PhabricatorConfigManagementSetWorkflow.php',
'PhabricatorConfigManagementWorkflow' => 'applications/config/management/PhabricatorConfigManagementWorkflow.php',
'PhabricatorConfigManualActivity' => 'applications/config/storage/PhabricatorConfigManualActivity.php',
'PhabricatorConfigModule' => 'applications/config/module/PhabricatorConfigModule.php',
'PhabricatorConfigModuleController' => 'applications/config/controller/module/PhabricatorConfigModuleController.php',
'PhabricatorConfigOption' => 'applications/config/option/PhabricatorConfigOption.php',
'PhabricatorConfigOptionType' => 'applications/config/custom/PhabricatorConfigOptionType.php',
'PhabricatorConfigPHIDModule' => 'applications/config/module/PhabricatorConfigPHIDModule.php',
'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php',
'PhabricatorConfigPurgeCacheController' => 'applications/config/controller/services/PhabricatorConfigPurgeCacheController.php',
'PhabricatorConfigRegexOptionType' => 'applications/config/custom/PhabricatorConfigRegexOptionType.php',
'PhabricatorConfigRemarkupRule' => 'infrastructure/markup/rule/PhabricatorConfigRemarkupRule.php',
'PhabricatorConfigRequestExceptionHandlerModule' => 'applications/config/module/PhabricatorConfigRequestExceptionHandlerModule.php',
'PhabricatorConfigResponse' => 'applications/config/response/PhabricatorConfigResponse.php',
'PhabricatorConfigSchemaQuery' => 'applications/config/schema/PhabricatorConfigSchemaQuery.php',
'PhabricatorConfigSchemaSpec' => 'applications/config/schema/PhabricatorConfigSchemaSpec.php',
'PhabricatorConfigServerSchema' => 'applications/config/schema/PhabricatorConfigServerSchema.php',
'PhabricatorConfigServicesController' => 'applications/config/controller/services/PhabricatorConfigServicesController.php',
'PhabricatorConfigSettingsController' => 'applications/config/controller/settings/PhabricatorConfigSettingsController.php',
'PhabricatorConfigSettingsHistoryController' => 'applications/config/controller/settings/PhabricatorConfigSettingsHistoryController.php',
'PhabricatorConfigSettingsListController' => 'applications/config/controller/settings/PhabricatorConfigSettingsListController.php',
'PhabricatorConfigSetupCheckModule' => 'applications/config/module/PhabricatorConfigSetupCheckModule.php',
'PhabricatorConfigSiteModule' => 'applications/config/module/PhabricatorConfigSiteModule.php',
'PhabricatorConfigSiteSource' => 'infrastructure/env/PhabricatorConfigSiteSource.php',
'PhabricatorConfigSource' => 'infrastructure/env/PhabricatorConfigSource.php',
'PhabricatorConfigStackSource' => 'infrastructure/env/PhabricatorConfigStackSource.php',
'PhabricatorConfigStorageSchema' => 'applications/config/schema/PhabricatorConfigStorageSchema.php',
'PhabricatorConfigTableSchema' => 'applications/config/schema/PhabricatorConfigTableSchema.php',
'PhabricatorConfigTransaction' => 'applications/config/storage/PhabricatorConfigTransaction.php',
'PhabricatorConfigTransactionQuery' => 'applications/config/query/PhabricatorConfigTransactionQuery.php',
'PhabricatorConfigType' => 'applications/config/type/PhabricatorConfigType.php',
'PhabricatorConfigValidationException' => 'applications/config/exception/PhabricatorConfigValidationException.php',
'PhabricatorConpherenceApplication' => 'applications/conpherence/application/PhabricatorConpherenceApplication.php',
'PhabricatorConpherenceColumnMinimizeSetting' => 'applications/settings/setting/PhabricatorConpherenceColumnMinimizeSetting.php',
'PhabricatorConpherenceColumnVisibleSetting' => 'applications/settings/setting/PhabricatorConpherenceColumnVisibleSetting.php',
'PhabricatorConpherenceNotificationsSetting' => 'applications/settings/setting/PhabricatorConpherenceNotificationsSetting.php',
'PhabricatorConpherencePreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorConpherencePreferencesSettingsPanel.php',
'PhabricatorConpherenceProfileMenuItem' => 'applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php',
'PhabricatorConpherenceRoomContextFreeGrammar' => 'applications/conpherence/lipsum/PhabricatorConpherenceRoomContextFreeGrammar.php',
'PhabricatorConpherenceRoomTestDataGenerator' => 'applications/conpherence/lipsum/PhabricatorConpherenceRoomTestDataGenerator.php',
'PhabricatorConpherenceSoundSetting' => 'applications/settings/setting/PhabricatorConpherenceSoundSetting.php',
'PhabricatorConpherenceThreadPHIDType' => 'applications/conpherence/phid/PhabricatorConpherenceThreadPHIDType.php',
'PhabricatorConpherenceWidgetVisibleSetting' => 'applications/settings/setting/PhabricatorConpherenceWidgetVisibleSetting.php',
'PhabricatorConsoleApplication' => 'applications/console/application/PhabricatorConsoleApplication.php',
'PhabricatorConsoleContentSource' => 'infrastructure/contentsource/PhabricatorConsoleContentSource.php',
'PhabricatorConstantChartFunction' => 'applications/fact/chart/PhabricatorConstantChartFunction.php',
'PhabricatorContactNumbersSettingsPanel' => 'applications/settings/panel/PhabricatorContactNumbersSettingsPanel.php',
'PhabricatorContentSource' => 'infrastructure/contentsource/PhabricatorContentSource.php',
'PhabricatorContentSourceModule' => 'infrastructure/contentsource/PhabricatorContentSourceModule.php',
'PhabricatorContentSourceView' => 'infrastructure/contentsource/PhabricatorContentSourceView.php',
'PhabricatorContributedToObjectEdgeType' => 'applications/transactions/edges/PhabricatorContributedToObjectEdgeType.php',
'PhabricatorController' => 'applications/base/controller/PhabricatorController.php',
'PhabricatorCookies' => 'applications/auth/constants/PhabricatorCookies.php',
'PhabricatorCoreConfigOptions' => 'applications/config/option/PhabricatorCoreConfigOptions.php',
'PhabricatorCoreCreateTransaction' => 'applications/transactions/xaction/PhabricatorCoreCreateTransaction.php',
'PhabricatorCoreTransactionType' => 'applications/transactions/xaction/PhabricatorCoreTransactionType.php',
'PhabricatorCoreVoidTransaction' => 'applications/transactions/xaction/PhabricatorCoreVoidTransaction.php',
'PhabricatorCosChartFunction' => 'applications/fact/chart/PhabricatorCosChartFunction.php',
'PhabricatorCountFact' => 'applications/fact/fact/PhabricatorCountFact.php',
'PhabricatorCountdown' => 'applications/countdown/storage/PhabricatorCountdown.php',
'PhabricatorCountdownApplication' => 'applications/countdown/application/PhabricatorCountdownApplication.php',
'PhabricatorCountdownController' => 'applications/countdown/controller/PhabricatorCountdownController.php',
'PhabricatorCountdownCountdownPHIDType' => 'applications/countdown/phid/PhabricatorCountdownCountdownPHIDType.php',
'PhabricatorCountdownDAO' => 'applications/countdown/storage/PhabricatorCountdownDAO.php',
'PhabricatorCountdownDefaultEditCapability' => 'applications/countdown/capability/PhabricatorCountdownDefaultEditCapability.php',
'PhabricatorCountdownDefaultViewCapability' => 'applications/countdown/capability/PhabricatorCountdownDefaultViewCapability.php',
'PhabricatorCountdownDescriptionTransaction' => 'applications/countdown/xaction/PhabricatorCountdownDescriptionTransaction.php',
'PhabricatorCountdownEditController' => 'applications/countdown/controller/PhabricatorCountdownEditController.php',
'PhabricatorCountdownEditEngine' => 'applications/countdown/editor/PhabricatorCountdownEditEngine.php',
'PhabricatorCountdownEditor' => 'applications/countdown/editor/PhabricatorCountdownEditor.php',
'PhabricatorCountdownEpochTransaction' => 'applications/countdown/xaction/PhabricatorCountdownEpochTransaction.php',
'PhabricatorCountdownListController' => 'applications/countdown/controller/PhabricatorCountdownListController.php',
'PhabricatorCountdownMailReceiver' => 'applications/countdown/mail/PhabricatorCountdownMailReceiver.php',
'PhabricatorCountdownQuery' => 'applications/countdown/query/PhabricatorCountdownQuery.php',
'PhabricatorCountdownRemarkupRule' => 'applications/countdown/remarkup/PhabricatorCountdownRemarkupRule.php',
'PhabricatorCountdownReplyHandler' => 'applications/countdown/mail/PhabricatorCountdownReplyHandler.php',
'PhabricatorCountdownSchemaSpec' => 'applications/countdown/storage/PhabricatorCountdownSchemaSpec.php',
'PhabricatorCountdownSearchEngine' => 'applications/countdown/query/PhabricatorCountdownSearchEngine.php',
'PhabricatorCountdownTitleTransaction' => 'applications/countdown/xaction/PhabricatorCountdownTitleTransaction.php',
'PhabricatorCountdownTransaction' => 'applications/countdown/storage/PhabricatorCountdownTransaction.php',
'PhabricatorCountdownTransactionComment' => 'applications/countdown/storage/PhabricatorCountdownTransactionComment.php',
'PhabricatorCountdownTransactionQuery' => 'applications/countdown/query/PhabricatorCountdownTransactionQuery.php',
'PhabricatorCountdownTransactionType' => 'applications/countdown/xaction/PhabricatorCountdownTransactionType.php',
'PhabricatorCountdownView' => 'applications/countdown/view/PhabricatorCountdownView.php',
'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php',
'PhabricatorCredentialEditField' => 'applications/transactions/editfield/PhabricatorCredentialEditField.php',
'PhabricatorCursorPagedPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php',
'PhabricatorCustomField' => 'infrastructure/customfield/field/PhabricatorCustomField.php',
'PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource' => 'infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource.php',
'PhabricatorCustomFieldApplicationSearchDatasource' => 'infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchDatasource.php',
'PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource' => 'infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource.php',
'PhabricatorCustomFieldAttachment' => 'infrastructure/customfield/field/PhabricatorCustomFieldAttachment.php',
'PhabricatorCustomFieldConfigOptionType' => 'infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php',
'PhabricatorCustomFieldDataNotAvailableException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldDataNotAvailableException.php',
'PhabricatorCustomFieldEditEngineExtension' => 'infrastructure/customfield/engineextension/PhabricatorCustomFieldEditEngineExtension.php',
'PhabricatorCustomFieldEditField' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php',
'PhabricatorCustomFieldEditType' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditType.php',
'PhabricatorCustomFieldExportEngineExtension' => 'infrastructure/export/engine/PhabricatorCustomFieldExportEngineExtension.php',
'PhabricatorCustomFieldFulltextEngineExtension' => 'infrastructure/customfield/engineextension/PhabricatorCustomFieldFulltextEngineExtension.php',
'PhabricatorCustomFieldHeraldAction' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldAction.php',
'PhabricatorCustomFieldHeraldActionGroup' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldActionGroup.php',
'PhabricatorCustomFieldHeraldField' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldField.php',
'PhabricatorCustomFieldHeraldFieldGroup' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldFieldGroup.php',
'PhabricatorCustomFieldImplementationIncompleteException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldImplementationIncompleteException.php',
'PhabricatorCustomFieldIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldIndexStorage.php',
'PhabricatorCustomFieldInterface' => 'infrastructure/customfield/interface/PhabricatorCustomFieldInterface.php',
'PhabricatorCustomFieldList' => 'infrastructure/customfield/field/PhabricatorCustomFieldList.php',
'PhabricatorCustomFieldMonogramParser' => 'infrastructure/customfield/parser/PhabricatorCustomFieldMonogramParser.php',
'PhabricatorCustomFieldNotAttachedException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldNotAttachedException.php',
'PhabricatorCustomFieldNotProxyException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldNotProxyException.php',
'PhabricatorCustomFieldNumericIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldNumericIndexStorage.php',
'PhabricatorCustomFieldSearchEngineExtension' => 'infrastructure/customfield/engineextension/PhabricatorCustomFieldSearchEngineExtension.php',
'PhabricatorCustomFieldStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldStorage.php',
'PhabricatorCustomFieldStorageQuery' => 'infrastructure/customfield/query/PhabricatorCustomFieldStorageQuery.php',
'PhabricatorCustomFieldStringIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldStringIndexStorage.php',
'PhabricatorCustomLogoConfigType' => 'applications/config/custom/PhabricatorCustomLogoConfigType.php',
'PhabricatorCustomUIFooterConfigType' => 'applications/config/custom/PhabricatorCustomUIFooterConfigType.php',
'PhabricatorDaemon' => 'infrastructure/daemon/PhabricatorDaemon.php',
'PhabricatorDaemonBulkJobController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobController.php',
'PhabricatorDaemonBulkJobListController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobListController.php',
'PhabricatorDaemonBulkJobMonitorController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobMonitorController.php',
'PhabricatorDaemonBulkJobViewController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobViewController.php',
'PhabricatorDaemonConsoleController' => 'applications/daemon/controller/PhabricatorDaemonConsoleController.php',
'PhabricatorDaemonContentSource' => 'infrastructure/daemon/contentsource/PhabricatorDaemonContentSource.php',
'PhabricatorDaemonController' => 'applications/daemon/controller/PhabricatorDaemonController.php',
'PhabricatorDaemonDAO' => 'applications/daemon/storage/PhabricatorDaemonDAO.php',
'PhabricatorDaemonEventListener' => 'applications/daemon/event/PhabricatorDaemonEventListener.php',
'PhabricatorDaemonLockLog' => 'applications/daemon/storage/PhabricatorDaemonLockLog.php',
'PhabricatorDaemonLockLogGarbageCollector' => 'applications/daemon/garbagecollector/PhabricatorDaemonLockLogGarbageCollector.php',
'PhabricatorDaemonLog' => 'applications/daemon/storage/PhabricatorDaemonLog.php',
'PhabricatorDaemonLogEvent' => 'applications/daemon/storage/PhabricatorDaemonLogEvent.php',
'PhabricatorDaemonLogEventGarbageCollector' => 'applications/daemon/garbagecollector/PhabricatorDaemonLogEventGarbageCollector.php',
'PhabricatorDaemonLogGarbageCollector' => 'applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php',
'PhabricatorDaemonLogListController' => 'applications/daemon/controller/PhabricatorDaemonLogListController.php',
'PhabricatorDaemonLogListView' => 'applications/daemon/view/PhabricatorDaemonLogListView.php',
'PhabricatorDaemonLogQuery' => 'applications/daemon/query/PhabricatorDaemonLogQuery.php',
'PhabricatorDaemonLogViewController' => 'applications/daemon/controller/PhabricatorDaemonLogViewController.php',
'PhabricatorDaemonManagementDebugWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementDebugWorkflow.php',
'PhabricatorDaemonManagementLaunchWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementLaunchWorkflow.php',
'PhabricatorDaemonManagementListWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementListWorkflow.php',
'PhabricatorDaemonManagementLogWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementLogWorkflow.php',
'PhabricatorDaemonManagementReloadWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementReloadWorkflow.php',
'PhabricatorDaemonManagementRestartWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementRestartWorkflow.php',
'PhabricatorDaemonManagementStartWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementStartWorkflow.php',
'PhabricatorDaemonManagementStatusWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementStatusWorkflow.php',
'PhabricatorDaemonManagementStopWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementStopWorkflow.php',
'PhabricatorDaemonManagementWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementWorkflow.php',
'PhabricatorDaemonOverseerModule' => 'infrastructure/daemon/overseer/PhabricatorDaemonOverseerModule.php',
'PhabricatorDaemonReference' => 'infrastructure/daemon/control/PhabricatorDaemonReference.php',
'PhabricatorDaemonTaskGarbageCollector' => 'applications/daemon/garbagecollector/PhabricatorDaemonTaskGarbageCollector.php',
'PhabricatorDaemonTasksTableView' => 'applications/daemon/view/PhabricatorDaemonTasksTableView.php',
'PhabricatorDaemonsApplication' => 'applications/daemon/application/PhabricatorDaemonsApplication.php',
'PhabricatorDaemonsSetupCheck' => 'applications/config/check/PhabricatorDaemonsSetupCheck.php',
'PhabricatorDailyRoutineTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorDailyRoutineTriggerClock.php',
'PhabricatorDarkConsoleSetting' => 'applications/settings/setting/PhabricatorDarkConsoleSetting.php',
'PhabricatorDarkConsoleTabSetting' => 'applications/settings/setting/PhabricatorDarkConsoleTabSetting.php',
'PhabricatorDarkConsoleVisibleSetting' => 'applications/settings/setting/PhabricatorDarkConsoleVisibleSetting.php',
'PhabricatorDashboard' => 'applications/dashboard/storage/PhabricatorDashboard.php',
'PhabricatorDashboardAdjustController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardAdjustController.php',
'PhabricatorDashboardApplication' => 'applications/dashboard/application/PhabricatorDashboardApplication.php',
'PhabricatorDashboardApplicationInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardApplicationInstallWorkflow.php',
'PhabricatorDashboardArchiveController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardArchiveController.php',
'PhabricatorDashboardChartPanelChartTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardChartPanelChartTransaction.php',
'PhabricatorDashboardChartPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardChartPanelType.php',
'PhabricatorDashboardColumn' => 'applications/dashboard/layoutconfig/PhabricatorDashboardColumn.php',
'PhabricatorDashboardConsoleController' => 'applications/dashboard/controller/PhabricatorDashboardConsoleController.php',
'PhabricatorDashboardController' => 'applications/dashboard/controller/PhabricatorDashboardController.php',
'PhabricatorDashboardDAO' => 'applications/dashboard/storage/PhabricatorDashboardDAO.php',
'PhabricatorDashboardDashboardPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardDashboardPHIDType.php',
'PhabricatorDashboardDatasource' => 'applications/dashboard/typeahead/PhabricatorDashboardDatasource.php',
'PhabricatorDashboardEditController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardEditController.php',
'PhabricatorDashboardEditEngine' => 'applications/dashboard/editor/PhabricatorDashboardEditEngine.php',
'PhabricatorDashboardFavoritesInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardFavoritesInstallWorkflow.php',
'PhabricatorDashboardFerretEngine' => 'applications/dashboard/engine/PhabricatorDashboardFerretEngine.php',
'PhabricatorDashboardFullLayoutMode' => 'applications/dashboard/layoutconfig/PhabricatorDashboardFullLayoutMode.php',
'PhabricatorDashboardFulltextEngine' => 'applications/dashboard/engine/PhabricatorDashboardFulltextEngine.php',
'PhabricatorDashboardHalfLayoutMode' => 'applications/dashboard/layoutconfig/PhabricatorDashboardHalfLayoutMode.php',
'PhabricatorDashboardHomeInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardHomeInstallWorkflow.php',
'PhabricatorDashboardIconSet' => 'applications/dashboard/icon/PhabricatorDashboardIconSet.php',
'PhabricatorDashboardIconTransaction' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardIconTransaction.php',
'PhabricatorDashboardInstallController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardInstallController.php',
'PhabricatorDashboardInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardInstallWorkflow.php',
'PhabricatorDashboardLayoutMode' => 'applications/dashboard/layoutconfig/PhabricatorDashboardLayoutMode.php',
'PhabricatorDashboardLayoutTransaction' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardLayoutTransaction.php',
'PhabricatorDashboardListController' => 'applications/dashboard/controller/PhabricatorDashboardListController.php',
'PhabricatorDashboardNameTransaction' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardNameTransaction.php',
'PhabricatorDashboardObjectInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardObjectInstallWorkflow.php',
'PhabricatorDashboardOneThirdLayoutMode' => 'applications/dashboard/layoutconfig/PhabricatorDashboardOneThirdLayoutMode.php',
'PhabricatorDashboardPanel' => 'applications/dashboard/storage/PhabricatorDashboardPanel.php',
'PhabricatorDashboardPanelArchiveController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelArchiveController.php',
'PhabricatorDashboardPanelContainerIndexEngineExtension' => 'applications/dashboard/engineextension/PhabricatorDashboardPanelContainerIndexEngineExtension.php',
'PhabricatorDashboardPanelContainerInterface' => 'applications/dashboard/interface/PhabricatorDashboardPanelContainerInterface.php',
'PhabricatorDashboardPanelDatasource' => 'applications/dashboard/typeahead/PhabricatorDashboardPanelDatasource.php',
'PhabricatorDashboardPanelEditConduitAPIMethod' => 'applications/dashboard/conduit/PhabricatorDashboardPanelEditConduitAPIMethod.php',
'PhabricatorDashboardPanelEditController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelEditController.php',
'PhabricatorDashboardPanelEditEngine' => 'applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php',
'PhabricatorDashboardPanelFerretEngine' => 'applications/dashboard/engine/PhabricatorDashboardPanelFerretEngine.php',
'PhabricatorDashboardPanelFulltextEngine' => 'applications/dashboard/engine/PhabricatorDashboardPanelFulltextEngine.php',
'PhabricatorDashboardPanelListController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelListController.php',
'PhabricatorDashboardPanelNameTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardPanelNameTransaction.php',
'PhabricatorDashboardPanelPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardPanelPHIDType.php',
'PhabricatorDashboardPanelPropertyTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardPanelPropertyTransaction.php',
'PhabricatorDashboardPanelQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelQuery.php',
'PhabricatorDashboardPanelRef' => 'applications/dashboard/layoutconfig/PhabricatorDashboardPanelRef.php',
'PhabricatorDashboardPanelRefList' => 'applications/dashboard/layoutconfig/PhabricatorDashboardPanelRefList.php',
'PhabricatorDashboardPanelRenderController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelRenderController.php',
'PhabricatorDashboardPanelRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php',
'PhabricatorDashboardPanelSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php',
'PhabricatorDashboardPanelStatusTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardPanelStatusTransaction.php',
'PhabricatorDashboardPanelTabsController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelTabsController.php',
'PhabricatorDashboardPanelTransaction' => 'applications/dashboard/storage/PhabricatorDashboardPanelTransaction.php',
'PhabricatorDashboardPanelTransactionEditor' => 'applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php',
'PhabricatorDashboardPanelTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelTransactionQuery.php',
'PhabricatorDashboardPanelTransactionType' => 'applications/dashboard/xaction/panel/PhabricatorDashboardPanelTransactionType.php',
'PhabricatorDashboardPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardPanelType.php',
'PhabricatorDashboardPanelUsedByObjectEdgeType' => 'applications/search/edge/PhabricatorDashboardPanelUsedByObjectEdgeType.php',
'PhabricatorDashboardPanelViewController' => 'applications/dashboard/controller/panel/PhabricatorDashboardPanelViewController.php',
'PhabricatorDashboardPanelsTransaction' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardPanelsTransaction.php',
'PhabricatorDashboardPortal' => 'applications/dashboard/storage/PhabricatorDashboardPortal.php',
'PhabricatorDashboardPortalController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalController.php',
'PhabricatorDashboardPortalDatasource' => 'applications/dashboard/typeahead/PhabricatorDashboardPortalDatasource.php',
'PhabricatorDashboardPortalEditConduitAPIMethod' => 'applications/dashboard/conduit/PhabricatorDashboardPortalEditConduitAPIMethod.php',
'PhabricatorDashboardPortalEditController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalEditController.php',
'PhabricatorDashboardPortalEditEngine' => 'applications/dashboard/editor/PhabricatorDashboardPortalEditEngine.php',
'PhabricatorDashboardPortalEditor' => 'applications/dashboard/editor/PhabricatorDashboardPortalEditor.php',
'PhabricatorDashboardPortalFerretEngine' => 'applications/dashboard/engine/PhabricatorDashboardPortalFerretEngine.php',
'PhabricatorDashboardPortalFulltextEngine' => 'applications/dashboard/engine/PhabricatorDashboardPortalFulltextEngine.php',
'PhabricatorDashboardPortalInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardPortalInstallWorkflow.php',
'PhabricatorDashboardPortalListController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalListController.php',
'PhabricatorDashboardPortalMenuItem' => 'applications/dashboard/menuitem/PhabricatorDashboardPortalMenuItem.php',
'PhabricatorDashboardPortalNameTransaction' => 'applications/dashboard/xaction/portal/PhabricatorDashboardPortalNameTransaction.php',
'PhabricatorDashboardPortalPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardPortalPHIDType.php',
'PhabricatorDashboardPortalProfileMenuEngine' => 'applications/dashboard/engine/PhabricatorDashboardPortalProfileMenuEngine.php',
'PhabricatorDashboardPortalQuery' => 'applications/dashboard/query/PhabricatorDashboardPortalQuery.php',
'PhabricatorDashboardPortalSearchConduitAPIMethod' => 'applications/dashboard/conduit/PhabricatorDashboardPortalSearchConduitAPIMethod.php',
'PhabricatorDashboardPortalSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardPortalSearchEngine.php',
'PhabricatorDashboardPortalStatus' => 'applications/dashboard/constants/PhabricatorDashboardPortalStatus.php',
'PhabricatorDashboardPortalTransaction' => 'applications/dashboard/storage/PhabricatorDashboardPortalTransaction.php',
'PhabricatorDashboardPortalTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardPortalTransactionQuery.php',
'PhabricatorDashboardPortalTransactionType' => 'applications/dashboard/xaction/portal/PhabricatorDashboardPortalTransactionType.php',
'PhabricatorDashboardPortalViewController' => 'applications/dashboard/controller/portal/PhabricatorDashboardPortalViewController.php',
'PhabricatorDashboardProfileController' => 'applications/dashboard/controller/PhabricatorDashboardProfileController.php',
'PhabricatorDashboardProfileMenuItem' => 'applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php',
'PhabricatorDashboardProjectInstallWorkflow' => 'applications/dashboard/install/PhabricatorDashboardProjectInstallWorkflow.php',
'PhabricatorDashboardQuery' => 'applications/dashboard/query/PhabricatorDashboardQuery.php',
'PhabricatorDashboardQueryPanelApplicationEditField' => 'applications/dashboard/editfield/PhabricatorDashboardQueryPanelApplicationEditField.php',
'PhabricatorDashboardQueryPanelApplicationTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardQueryPanelApplicationTransaction.php',
'PhabricatorDashboardQueryPanelInstallController' => 'applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php',
'PhabricatorDashboardQueryPanelLimitTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardQueryPanelLimitTransaction.php',
'PhabricatorDashboardQueryPanelQueryEditField' => 'applications/dashboard/editfield/PhabricatorDashboardQueryPanelQueryEditField.php',
'PhabricatorDashboardQueryPanelQueryTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardQueryPanelQueryTransaction.php',
'PhabricatorDashboardQueryPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php',
'PhabricatorDashboardRemarkupRule' => 'applications/dashboard/remarkup/PhabricatorDashboardRemarkupRule.php',
'PhabricatorDashboardRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php',
'PhabricatorDashboardSchemaSpec' => 'applications/dashboard/storage/PhabricatorDashboardSchemaSpec.php',
'PhabricatorDashboardSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardSearchEngine.php',
'PhabricatorDashboardStatusTransaction' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardStatusTransaction.php',
'PhabricatorDashboardTabsPanelTabsTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardTabsPanelTabsTransaction.php',
'PhabricatorDashboardTabsPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php',
'PhabricatorDashboardTextPanelTextTransaction' => 'applications/dashboard/xaction/panel/PhabricatorDashboardTextPanelTextTransaction.php',
'PhabricatorDashboardTextPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardTextPanelType.php',
'PhabricatorDashboardTransaction' => 'applications/dashboard/storage/PhabricatorDashboardTransaction.php',
'PhabricatorDashboardTransactionEditor' => 'applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php',
'PhabricatorDashboardTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardTransactionQuery.php',
'PhabricatorDashboardTransactionType' => 'applications/dashboard/xaction/dashboard/PhabricatorDashboardTransactionType.php',
'PhabricatorDashboardTwoThirdsLayoutMode' => 'applications/dashboard/layoutconfig/PhabricatorDashboardTwoThirdsLayoutMode.php',
'PhabricatorDashboardViewController' => 'applications/dashboard/controller/dashboard/PhabricatorDashboardViewController.php',
'PhabricatorDataCacheSpec' => 'applications/cache/spec/PhabricatorDataCacheSpec.php',
'PhabricatorDataNotAttachedException' => 'infrastructure/storage/lisk/PhabricatorDataNotAttachedException.php',
'PhabricatorDatabaseRef' => 'infrastructure/cluster/PhabricatorDatabaseRef.php',
'PhabricatorDatabaseRefParser' => 'infrastructure/cluster/PhabricatorDatabaseRefParser.php',
'PhabricatorDatabaseSetupCheck' => 'applications/config/check/PhabricatorDatabaseSetupCheck.php',
'PhabricatorDatasourceApplicationEngineExtension' => 'applications/meta/engineextension/PhabricatorDatasourceApplicationEngineExtension.php',
'PhabricatorDatasourceEditField' => 'applications/transactions/editfield/PhabricatorDatasourceEditField.php',
'PhabricatorDatasourceEditType' => 'applications/transactions/edittype/PhabricatorDatasourceEditType.php',
'PhabricatorDatasourceEngine' => 'applications/search/engine/PhabricatorDatasourceEngine.php',
'PhabricatorDatasourceEngineExtension' => 'applications/search/engineextension/PhabricatorDatasourceEngineExtension.php',
'PhabricatorDatasourceURIEngineExtension' => 'applications/meta/engineextension/PhabricatorDatasourceURIEngineExtension.php',
'PhabricatorDateFormatSetting' => 'applications/settings/setting/PhabricatorDateFormatSetting.php',
'PhabricatorDateTimeSettingsPanel' => 'applications/settings/panel/PhabricatorDateTimeSettingsPanel.php',
'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php',
'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php',
'PhabricatorDefaultSyntaxStyle' => 'infrastructure/syntax/PhabricatorDefaultSyntaxStyle.php',
'PhabricatorDefaultUnlockEngine' => 'applications/system/engine/PhabricatorDefaultUnlockEngine.php',
'PhabricatorDemoChartEngine' => 'applications/fact/engine/PhabricatorDemoChartEngine.php',
'PhabricatorDestructibleCodex' => 'applications/system/codex/PhabricatorDestructibleCodex.php',
'PhabricatorDestructibleCodexInterface' => 'applications/system/interface/PhabricatorDestructibleCodexInterface.php',
'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php',
'PhabricatorDestructionEngine' => 'applications/system/engine/PhabricatorDestructionEngine.php',
'PhabricatorDestructionEngineExtension' => 'applications/system/engine/PhabricatorDestructionEngineExtension.php',
'PhabricatorDestructionEngineExtensionModule' => 'applications/system/engine/PhabricatorDestructionEngineExtensionModule.php',
'PhabricatorDeveloperConfigOptions' => 'applications/config/option/PhabricatorDeveloperConfigOptions.php',
'PhabricatorDeveloperPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorDeveloperPreferencesSettingsPanel.php',
'PhabricatorDiffInlineCommentContentState' => 'infrastructure/diff/inline/PhabricatorDiffInlineCommentContentState.php',
'PhabricatorDiffInlineCommentContext' => 'infrastructure/diff/inline/PhabricatorDiffInlineCommentContext.php',
'PhabricatorDiffInlineCommentQuery' => 'infrastructure/diff/query/PhabricatorDiffInlineCommentQuery.php',
'PhabricatorDiffPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorDiffPreferencesSettingsPanel.php',
'PhabricatorDiffScopeEngine' => 'infrastructure/diff/PhabricatorDiffScopeEngine.php',
'PhabricatorDiffScopeEngineTestCase' => 'infrastructure/diff/__tests__/PhabricatorDiffScopeEngineTestCase.php',
'PhabricatorDifferenceEngine' => 'infrastructure/diff/PhabricatorDifferenceEngine.php',
'PhabricatorDifferentialApplication' => 'applications/differential/application/PhabricatorDifferentialApplication.php',
'PhabricatorDifferentialAttachCommitWorkflow' => 'applications/differential/management/PhabricatorDifferentialAttachCommitWorkflow.php',
'PhabricatorDifferentialConfigOptions' => 'applications/differential/config/PhabricatorDifferentialConfigOptions.php',
'PhabricatorDifferentialExtractWorkflow' => 'applications/differential/management/PhabricatorDifferentialExtractWorkflow.php',
'PhabricatorDifferentialManagementWorkflow' => 'applications/differential/management/PhabricatorDifferentialManagementWorkflow.php',
'PhabricatorDifferentialMigrateHunkWorkflow' => 'applications/differential/management/PhabricatorDifferentialMigrateHunkWorkflow.php',
'PhabricatorDifferentialRebuildChangesetsWorkflow' => 'applications/differential/management/PhabricatorDifferentialRebuildChangesetsWorkflow.php',
'PhabricatorDifferentialRevisionTestDataGenerator' => 'applications/differential/lipsum/PhabricatorDifferentialRevisionTestDataGenerator.php',
'PhabricatorDiffusionApplication' => 'applications/diffusion/application/PhabricatorDiffusionApplication.php',
'PhabricatorDiffusionBlameSetting' => 'applications/settings/setting/PhabricatorDiffusionBlameSetting.php',
'PhabricatorDiffusionConfigOptions' => 'applications/diffusion/config/PhabricatorDiffusionConfigOptions.php',
'PhabricatorDisabledUserController' => 'applications/auth/controller/PhabricatorDisabledUserController.php',
'PhabricatorDisplayPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorDisplayPreferencesSettingsPanel.php',
'PhabricatorDisqusAuthProvider' => 'applications/auth/provider/PhabricatorDisqusAuthProvider.php',
'PhabricatorDividerEditField' => 'applications/transactions/editfield/PhabricatorDividerEditField.php',
'PhabricatorDividerProfileMenuItem' => 'applications/search/menuitem/PhabricatorDividerProfileMenuItem.php',
'PhabricatorDivinerApplication' => 'applications/diviner/application/PhabricatorDivinerApplication.php',
'PhabricatorDocumentEngine' => 'applications/files/document/PhabricatorDocumentEngine.php',
'PhabricatorDocumentEngineBlock' => 'applications/files/diff/PhabricatorDocumentEngineBlock.php',
'PhabricatorDocumentEngineBlockDiff' => 'applications/files/diff/PhabricatorDocumentEngineBlockDiff.php',
'PhabricatorDocumentEngineBlocks' => 'applications/files/diff/PhabricatorDocumentEngineBlocks.php',
'PhabricatorDocumentEngineParserException' => 'applications/files/document/exception/PhabricatorDocumentEngineParserException.php',
'PhabricatorDocumentRef' => 'applications/files/document/PhabricatorDocumentRef.php',
'PhabricatorDocumentRenderingEngine' => 'applications/files/document/render/PhabricatorDocumentRenderingEngine.php',
'PhabricatorDoorkeeperApplication' => 'applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php',
'PhabricatorDoubleExportField' => 'infrastructure/export/field/PhabricatorDoubleExportField.php',
'PhabricatorDraft' => 'applications/draft/storage/PhabricatorDraft.php',
'PhabricatorDraftDAO' => 'applications/draft/storage/PhabricatorDraftDAO.php',
'PhabricatorDraftEngine' => 'applications/transactions/draft/PhabricatorDraftEngine.php',
'PhabricatorDraftInterface' => 'applications/transactions/draft/PhabricatorDraftInterface.php',
'PhabricatorDrydockApplication' => 'applications/drydock/application/PhabricatorDrydockApplication.php',
'PhabricatorDuoAuthFactor' => 'applications/auth/factor/PhabricatorDuoAuthFactor.php',
'PhabricatorDuoFuture' => 'applications/auth/future/PhabricatorDuoFuture.php',
'PhabricatorEdgeChangeRecord' => 'infrastructure/edges/util/PhabricatorEdgeChangeRecord.php',
'PhabricatorEdgeChangeRecordTestCase' => 'infrastructure/edges/__tests__/PhabricatorEdgeChangeRecordTestCase.php',
'PhabricatorEdgeConfig' => 'infrastructure/edges/constants/PhabricatorEdgeConfig.php',
'PhabricatorEdgeConstants' => 'infrastructure/edges/constants/PhabricatorEdgeConstants.php',
'PhabricatorEdgeCycleException' => 'infrastructure/edges/exception/PhabricatorEdgeCycleException.php',
'PhabricatorEdgeEditType' => 'applications/transactions/edittype/PhabricatorEdgeEditType.php',
'PhabricatorEdgeEditor' => 'infrastructure/edges/editor/PhabricatorEdgeEditor.php',
'PhabricatorEdgeGraph' => 'infrastructure/edges/util/PhabricatorEdgeGraph.php',
'PhabricatorEdgeIndexEngineExtension' => 'applications/search/engineextension/PhabricatorEdgeIndexEngineExtension.php',
'PhabricatorEdgeObject' => 'infrastructure/edges/conduit/PhabricatorEdgeObject.php',
'PhabricatorEdgeObjectQuery' => 'infrastructure/edges/query/PhabricatorEdgeObjectQuery.php',
'PhabricatorEdgeQuery' => 'infrastructure/edges/query/PhabricatorEdgeQuery.php',
'PhabricatorEdgeTestCase' => 'infrastructure/edges/__tests__/PhabricatorEdgeTestCase.php',
'PhabricatorEdgeType' => 'infrastructure/edges/type/PhabricatorEdgeType.php',
'PhabricatorEdgeTypeTestCase' => 'infrastructure/edges/type/__tests__/PhabricatorEdgeTypeTestCase.php',
'PhabricatorEdgesDestructionEngineExtension' => 'infrastructure/edges/engineextension/PhabricatorEdgesDestructionEngineExtension.php',
'PhabricatorEditEngine' => 'applications/transactions/editengine/PhabricatorEditEngine.php',
'PhabricatorEditEngineAPIMethod' => 'applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php',
'PhabricatorEditEngineBulkJobType' => 'applications/transactions/bulk/PhabricatorEditEngineBulkJobType.php',
'PhabricatorEditEngineCheckboxesCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineCheckboxesCommentAction.php',
'PhabricatorEditEngineColumnsCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineColumnsCommentAction.php',
'PhabricatorEditEngineCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php',
'PhabricatorEditEngineCommentActionGroup' => 'applications/transactions/commentaction/PhabricatorEditEngineCommentActionGroup.php',
'PhabricatorEditEngineConfiguration' => 'applications/transactions/storage/PhabricatorEditEngineConfiguration.php',
'PhabricatorEditEngineConfigurationDefaultCreateController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultCreateController.php',
'PhabricatorEditEngineConfigurationDefaultsController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultsController.php',
'PhabricatorEditEngineConfigurationDisableController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationDisableController.php',
'PhabricatorEditEngineConfigurationEditController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationEditController.php',
'PhabricatorEditEngineConfigurationEditEngine' => 'applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php',
'PhabricatorEditEngineConfigurationEditor' => 'applications/transactions/editor/PhabricatorEditEngineConfigurationEditor.php',
'PhabricatorEditEngineConfigurationIsEditController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationIsEditController.php',
'PhabricatorEditEngineConfigurationListController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationListController.php',
'PhabricatorEditEngineConfigurationLockController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationLockController.php',
'PhabricatorEditEngineConfigurationPHIDType' => 'applications/transactions/phid/PhabricatorEditEngineConfigurationPHIDType.php',
'PhabricatorEditEngineConfigurationQuery' => 'applications/transactions/query/PhabricatorEditEngineConfigurationQuery.php',
'PhabricatorEditEngineConfigurationReorderController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationReorderController.php',
'PhabricatorEditEngineConfigurationSaveController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationSaveController.php',
'PhabricatorEditEngineConfigurationSearchEngine' => 'applications/transactions/query/PhabricatorEditEngineConfigurationSearchEngine.php',
'PhabricatorEditEngineConfigurationSortController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationSortController.php',
'PhabricatorEditEngineConfigurationSubtypeController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationSubtypeController.php',
'PhabricatorEditEngineConfigurationTransaction' => 'applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php',
'PhabricatorEditEngineConfigurationTransactionQuery' => 'applications/transactions/query/PhabricatorEditEngineConfigurationTransactionQuery.php',
'PhabricatorEditEngineConfigurationViewController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php',
'PhabricatorEditEngineController' => 'applications/transactions/controller/PhabricatorEditEngineController.php',
'PhabricatorEditEngineCreateOrderTransaction' => 'applications/transactions/xaction/PhabricatorEditEngineCreateOrderTransaction.php',
'PhabricatorEditEngineDatasource' => 'applications/transactions/typeahead/PhabricatorEditEngineDatasource.php',
'PhabricatorEditEngineDefaultCreateTransaction' => 'applications/transactions/xaction/PhabricatorEditEngineDefaultCreateTransaction.php',
'PhabricatorEditEngineDefaultLock' => 'applications/transactions/editengine/PhabricatorEditEngineDefaultLock.php',
'PhabricatorEditEngineDefaultTransaction' => 'applications/transactions/xaction/PhabricatorEditEngineDefaultTransaction.php',
'PhabricatorEditEngineDisableTransaction' => 'applications/transactions/xaction/PhabricatorEditEngineDisableTransaction.php',
'PhabricatorEditEngineEditOrderTransaction' => 'applications/transactions/xaction/PhabricatorEditEngineEditOrderTransaction.php',
'PhabricatorEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorEditEngineExtension.php',
'PhabricatorEditEngineExtensionModule' => 'applications/transactions/engineextension/PhabricatorEditEngineExtensionModule.php',
'PhabricatorEditEngineIsEditTransaction' => 'applications/transactions/xaction/PhabricatorEditEngineIsEditTransaction.php',
'PhabricatorEditEngineListController' => 'applications/transactions/controller/PhabricatorEditEngineListController.php',
'PhabricatorEditEngineLock' => 'applications/transactions/editengine/PhabricatorEditEngineLock.php',
'PhabricatorEditEngineLockableInterface' => 'applications/transactions/editengine/PhabricatorEditEngineLockableInterface.php',
'PhabricatorEditEngineLocksTransaction' => 'applications/transactions/xaction/PhabricatorEditEngineLocksTransaction.php',
'PhabricatorEditEngineMFAEngine' => 'applications/transactions/editengine/PhabricatorEditEngineMFAEngine.php',
'PhabricatorEditEngineMFAInterface' => 'applications/transactions/editengine/PhabricatorEditEngineMFAInterface.php',
'PhabricatorEditEngineNameTransaction' => 'applications/transactions/xaction/PhabricatorEditEngineNameTransaction.php',
'PhabricatorEditEngineOrderTransaction' => 'applications/transactions/xaction/PhabricatorEditEngineOrderTransaction.php',
'PhabricatorEditEnginePageState' => 'applications/transactions/editengine/PhabricatorEditEnginePageState.php',
'PhabricatorEditEnginePointsCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEnginePointsCommentAction.php',
'PhabricatorEditEnginePreambleTransaction' => 'applications/transactions/xaction/PhabricatorEditEnginePreambleTransaction.php',
'PhabricatorEditEngineProfileMenuItem' => 'applications/search/menuitem/PhabricatorEditEngineProfileMenuItem.php',
'PhabricatorEditEngineQuery' => 'applications/transactions/query/PhabricatorEditEngineQuery.php',
'PhabricatorEditEngineSearchEngine' => 'applications/transactions/query/PhabricatorEditEngineSearchEngine.php',
'PhabricatorEditEngineSelectCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineSelectCommentAction.php',
'PhabricatorEditEngineSettingsPanel' => 'applications/settings/panel/PhabricatorEditEngineSettingsPanel.php',
'PhabricatorEditEngineStaticCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineStaticCommentAction.php',
'PhabricatorEditEngineSubtype' => 'applications/transactions/editengine/PhabricatorEditEngineSubtype.php',
'PhabricatorEditEngineSubtypeHeraldField' => 'applications/transactions/herald/PhabricatorEditEngineSubtypeHeraldField.php',
'PhabricatorEditEngineSubtypeInterface' => 'applications/transactions/editengine/PhabricatorEditEngineSubtypeInterface.php',
'PhabricatorEditEngineSubtypeMap' => 'applications/transactions/editengine/PhabricatorEditEngineSubtypeMap.php',
'PhabricatorEditEngineSubtypeTestCase' => 'applications/transactions/editengine/__tests__/PhabricatorEditEngineSubtypeTestCase.php',
'PhabricatorEditEngineSubtypeTransaction' => 'applications/transactions/xaction/PhabricatorEditEngineSubtypeTransaction.php',
'PhabricatorEditEngineTokenizerCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineTokenizerCommentAction.php',
'PhabricatorEditEngineTransactionType' => 'applications/transactions/xaction/PhabricatorEditEngineTransactionType.php',
'PhabricatorEditField' => 'applications/transactions/editfield/PhabricatorEditField.php',
'PhabricatorEditPage' => 'applications/transactions/editengine/PhabricatorEditPage.php',
'PhabricatorEditType' => 'applications/transactions/edittype/PhabricatorEditType.php',
'PhabricatorEditor' => 'infrastructure/PhabricatorEditor.php',
'PhabricatorEditorExtension' => 'applications/transactions/engineextension/PhabricatorEditorExtension.php',
'PhabricatorEditorExtensionModule' => 'applications/transactions/engineextension/PhabricatorEditorExtensionModule.php',
'PhabricatorEditorMailEngineExtension' => 'applications/transactions/engineextension/PhabricatorEditorMailEngineExtension.php',
'PhabricatorEditorSetting' => 'applications/settings/setting/PhabricatorEditorSetting.php',
'PhabricatorEditorURIEngine' => 'infrastructure/editor/PhabricatorEditorURIEngine.php',
'PhabricatorEditorURIEngineTestCase' => 'infrastructure/editor/__tests__/PhabricatorEditorURIEngineTestCase.php',
'PhabricatorEditorURIParserException' => 'infrastructure/editor/PhabricatorEditorURIParserException.php',
'PhabricatorElasticFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php',
'PhabricatorElasticsearchHost' => 'infrastructure/cluster/search/PhabricatorElasticsearchHost.php',
'PhabricatorElasticsearchQueryBuilder' => 'applications/search/fulltextstorage/PhabricatorElasticsearchQueryBuilder.php',
'PhabricatorElasticsearchSetupCheck' => 'applications/config/check/PhabricatorElasticsearchSetupCheck.php',
'PhabricatorEmailAddressesSettingsPanel' => 'applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php',
'PhabricatorEmailContentSource' => 'applications/metamta/contentsource/PhabricatorEmailContentSource.php',
'PhabricatorEmailDeliverySettingsPanel' => 'applications/settings/panel/PhabricatorEmailDeliverySettingsPanel.php',
'PhabricatorEmailFormatSetting' => 'applications/settings/setting/PhabricatorEmailFormatSetting.php',
'PhabricatorEmailFormatSettingsPanel' => 'applications/settings/panel/PhabricatorEmailFormatSettingsPanel.php',
'PhabricatorEmailLoginController' => 'applications/auth/controller/PhabricatorEmailLoginController.php',
'PhabricatorEmailLoginUserLogType' => 'applications/people/userlog/PhabricatorEmailLoginUserLogType.php',
'PhabricatorEmailNotificationsSetting' => 'applications/settings/setting/PhabricatorEmailNotificationsSetting.php',
'PhabricatorEmailPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php',
'PhabricatorEmailRePrefixSetting' => 'applications/settings/setting/PhabricatorEmailRePrefixSetting.php',
'PhabricatorEmailSelfActionsSetting' => 'applications/settings/setting/PhabricatorEmailSelfActionsSetting.php',
'PhabricatorEmailStampsSetting' => 'applications/settings/setting/PhabricatorEmailStampsSetting.php',
'PhabricatorEmailTagsSetting' => 'applications/settings/setting/PhabricatorEmailTagsSetting.php',
'PhabricatorEmailVarySubjectsSetting' => 'applications/settings/setting/PhabricatorEmailVarySubjectsSetting.php',
'PhabricatorEmailVerificationController' => 'applications/auth/controller/PhabricatorEmailVerificationController.php',
'PhabricatorEmbedFileRemarkupRule' => 'applications/files/markup/PhabricatorEmbedFileRemarkupRule.php',
'PhabricatorEmojiDatasource' => 'applications/macro/typeahead/PhabricatorEmojiDatasource.php',
'PhabricatorEmojiRemarkupRule' => 'applications/macro/markup/PhabricatorEmojiRemarkupRule.php',
'PhabricatorEmojiTranslation' => 'infrastructure/internationalization/translation/PhabricatorEmojiTranslation.php',
'PhabricatorEmptyQueryException' => 'infrastructure/query/exception/PhabricatorEmptyQueryException.php',
'PhabricatorEnterHisecUserLogType' => 'applications/people/userlog/PhabricatorEnterHisecUserLogType.php',
'PhabricatorEnumConfigType' => 'applications/config/type/PhabricatorEnumConfigType.php',
'PhabricatorEnv' => 'infrastructure/env/PhabricatorEnv.php',
'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__/PhabricatorEnvTestCase.php',
'PhabricatorEpochEditField' => 'applications/transactions/editfield/PhabricatorEpochEditField.php',
'PhabricatorEpochExportField' => 'infrastructure/export/field/PhabricatorEpochExportField.php',
'PhabricatorEvent' => 'infrastructure/events/PhabricatorEvent.php',
'PhabricatorEventEngine' => 'infrastructure/events/PhabricatorEventEngine.php',
'PhabricatorEventListener' => 'infrastructure/events/PhabricatorEventListener.php',
'PhabricatorEventType' => 'infrastructure/events/constant/PhabricatorEventType.php',
'PhabricatorExampleEventListener' => 'infrastructure/events/PhabricatorExampleEventListener.php',
'PhabricatorExcelExportFormat' => 'infrastructure/export/format/PhabricatorExcelExportFormat.php',
'PhabricatorExecFutureFileUploadSource' => 'applications/files/uploadsource/PhabricatorExecFutureFileUploadSource.php',
'PhabricatorExitHisecUserLogType' => 'applications/people/userlog/PhabricatorExitHisecUserLogType.php',
'PhabricatorExportEngine' => 'infrastructure/export/engine/PhabricatorExportEngine.php',
'PhabricatorExportEngineBulkJobType' => 'infrastructure/export/engine/PhabricatorExportEngineBulkJobType.php',
'PhabricatorExportEngineExtension' => 'infrastructure/export/engine/PhabricatorExportEngineExtension.php',
'PhabricatorExportField' => 'infrastructure/export/field/PhabricatorExportField.php',
'PhabricatorExportFormat' => 'infrastructure/export/format/PhabricatorExportFormat.php',
'PhabricatorExportFormatSetting' => 'infrastructure/export/engine/PhabricatorExportFormatSetting.php',
'PhabricatorExtendedPolicyInterface' => 'applications/policy/interface/PhabricatorExtendedPolicyInterface.php',
'PhabricatorExtendingPhabricatorConfigOptions' => 'applications/config/option/PhabricatorExtendingPhabricatorConfigOptions.php',
'PhabricatorExtensionsSetupCheck' => 'applications/config/check/PhabricatorExtensionsSetupCheck.php',
'PhabricatorExternalAccount' => 'applications/people/storage/PhabricatorExternalAccount.php',
'PhabricatorExternalAccountIdentifier' => 'applications/people/storage/PhabricatorExternalAccountIdentifier.php',
'PhabricatorExternalAccountIdentifierQuery' => 'applications/auth/query/PhabricatorExternalAccountIdentifierQuery.php',
'PhabricatorExternalAccountQuery' => 'applications/auth/query/PhabricatorExternalAccountQuery.php',
'PhabricatorExternalAccountsSettingsPanel' => 'applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php',
'PhabricatorExternalEditorSettingsPanel' => 'applications/settings/panel/PhabricatorExternalEditorSettingsPanel.php',
'PhabricatorExtraConfigSetupCheck' => 'applications/config/check/PhabricatorExtraConfigSetupCheck.php',
'PhabricatorFacebookAuthProvider' => 'applications/auth/provider/PhabricatorFacebookAuthProvider.php',
'PhabricatorFact' => 'applications/fact/fact/PhabricatorFact.php',
'PhabricatorFactAggregate' => 'applications/fact/storage/PhabricatorFactAggregate.php',
'PhabricatorFactApplication' => 'applications/fact/application/PhabricatorFactApplication.php',
'PhabricatorFactChart' => 'applications/fact/storage/PhabricatorFactChart.php',
'PhabricatorFactChartController' => 'applications/fact/controller/PhabricatorFactChartController.php',
'PhabricatorFactChartFunction' => 'applications/fact/chart/PhabricatorFactChartFunction.php',
'PhabricatorFactController' => 'applications/fact/controller/PhabricatorFactController.php',
'PhabricatorFactCursor' => 'applications/fact/storage/PhabricatorFactCursor.php',
'PhabricatorFactDAO' => 'applications/fact/storage/PhabricatorFactDAO.php',
'PhabricatorFactDaemon' => 'applications/fact/daemon/PhabricatorFactDaemon.php',
'PhabricatorFactDatapointQuery' => 'applications/fact/query/PhabricatorFactDatapointQuery.php',
'PhabricatorFactDimension' => 'applications/fact/storage/PhabricatorFactDimension.php',
'PhabricatorFactEngine' => 'applications/fact/engine/PhabricatorFactEngine.php',
'PhabricatorFactEngineTestCase' => 'applications/fact/engine/__tests__/PhabricatorFactEngineTestCase.php',
'PhabricatorFactHomeController' => 'applications/fact/controller/PhabricatorFactHomeController.php',
'PhabricatorFactIntDatapoint' => 'applications/fact/storage/PhabricatorFactIntDatapoint.php',
'PhabricatorFactKeyDimension' => 'applications/fact/storage/PhabricatorFactKeyDimension.php',
'PhabricatorFactManagementAnalyzeWorkflow' => 'applications/fact/management/PhabricatorFactManagementAnalyzeWorkflow.php',
'PhabricatorFactManagementCursorsWorkflow' => 'applications/fact/management/PhabricatorFactManagementCursorsWorkflow.php',
'PhabricatorFactManagementDestroyWorkflow' => 'applications/fact/management/PhabricatorFactManagementDestroyWorkflow.php',
'PhabricatorFactManagementListWorkflow' => 'applications/fact/management/PhabricatorFactManagementListWorkflow.php',
'PhabricatorFactManagementWorkflow' => 'applications/fact/management/PhabricatorFactManagementWorkflow.php',
'PhabricatorFactObjectController' => 'applications/fact/controller/PhabricatorFactObjectController.php',
'PhabricatorFactObjectDimension' => 'applications/fact/storage/PhabricatorFactObjectDimension.php',
'PhabricatorFactRaw' => 'applications/fact/storage/PhabricatorFactRaw.php',
'PhabricatorFactUpdateIterator' => 'applications/fact/extract/PhabricatorFactUpdateIterator.php',
'PhabricatorFailHisecUserLogType' => 'applications/people/userlog/PhabricatorFailHisecUserLogType.php',
'PhabricatorFaviconController' => 'applications/system/controller/PhabricatorFaviconController.php',
'PhabricatorFaviconRef' => 'applications/files/favicon/PhabricatorFaviconRef.php',
'PhabricatorFaviconRefQuery' => 'applications/files/favicon/PhabricatorFaviconRefQuery.php',
'PhabricatorFavoritesApplication' => 'applications/favorites/application/PhabricatorFavoritesApplication.php',
'PhabricatorFavoritesController' => 'applications/favorites/controller/PhabricatorFavoritesController.php',
'PhabricatorFavoritesMainMenuBarExtension' => 'applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php',
'PhabricatorFavoritesMenuItemController' => 'applications/favorites/controller/PhabricatorFavoritesMenuItemController.php',
'PhabricatorFavoritesProfileMenuEngine' => 'applications/favorites/engine/PhabricatorFavoritesProfileMenuEngine.php',
'PhabricatorFaxContentSource' => 'infrastructure/contentsource/PhabricatorFaxContentSource.php',
'PhabricatorFeedApplication' => 'applications/feed/application/PhabricatorFeedApplication.php',
'PhabricatorFeedBuilder' => 'applications/feed/builder/PhabricatorFeedBuilder.php',
'PhabricatorFeedConfigOptions' => 'applications/feed/config/PhabricatorFeedConfigOptions.php',
'PhabricatorFeedController' => 'applications/feed/controller/PhabricatorFeedController.php',
'PhabricatorFeedDAO' => 'applications/feed/storage/PhabricatorFeedDAO.php',
'PhabricatorFeedDetailController' => 'applications/feed/controller/PhabricatorFeedDetailController.php',
'PhabricatorFeedListController' => 'applications/feed/controller/PhabricatorFeedListController.php',
'PhabricatorFeedManagementRepublishWorkflow' => 'applications/feed/management/PhabricatorFeedManagementRepublishWorkflow.php',
'PhabricatorFeedManagementWorkflow' => 'applications/feed/management/PhabricatorFeedManagementWorkflow.php',
'PhabricatorFeedQuery' => 'applications/feed/query/PhabricatorFeedQuery.php',
'PhabricatorFeedSearchEngine' => 'applications/feed/query/PhabricatorFeedSearchEngine.php',
'PhabricatorFeedStory' => 'applications/feed/story/PhabricatorFeedStory.php',
'PhabricatorFeedStoryData' => 'applications/feed/storage/PhabricatorFeedStoryData.php',
'PhabricatorFeedStoryNotification' => 'applications/notification/storage/PhabricatorFeedStoryNotification.php',
'PhabricatorFeedStoryPublisher' => 'applications/feed/PhabricatorFeedStoryPublisher.php',
'PhabricatorFeedStoryReference' => 'applications/feed/storage/PhabricatorFeedStoryReference.php',
'PhabricatorFeedTransactionListController' => 'applications/feed/controller/PhabricatorFeedTransactionListController.php',
'PhabricatorFeedTransactionQuery' => 'applications/feed/query/PhabricatorFeedTransactionQuery.php',
'PhabricatorFeedTransactionSearchEngine' => 'applications/feed/query/PhabricatorFeedTransactionSearchEngine.php',
'PhabricatorFerretEngine' => 'applications/search/ferret/PhabricatorFerretEngine.php',
'PhabricatorFerretEngineTestCase' => 'applications/search/ferret/__tests__/PhabricatorFerretEngineTestCase.php',
'PhabricatorFerretFulltextEngineExtension' => 'applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php',
'PhabricatorFerretFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php',
'PhabricatorFerretInterface' => 'applications/search/ferret/PhabricatorFerretInterface.php',
'PhabricatorFerretMetadata' => 'applications/search/ferret/PhabricatorFerretMetadata.php',
'PhabricatorFerretSearchEngineExtension' => 'applications/search/engineextension/PhabricatorFerretSearchEngineExtension.php',
'PhabricatorFile' => 'applications/files/storage/PhabricatorFile.php',
'PhabricatorFileAES256StorageFormat' => 'applications/files/format/PhabricatorFileAES256StorageFormat.php',
'PhabricatorFileAltTextTransaction' => 'applications/files/xaction/PhabricatorFileAltTextTransaction.php',
+ 'PhabricatorFileAttachment' => 'applications/files/storage/PhabricatorFileAttachment.php',
+ 'PhabricatorFileAttachmentQuery' => 'applications/files/query/PhabricatorFileAttachmentQuery.php',
'PhabricatorFileBundleLoader' => 'applications/files/query/PhabricatorFileBundleLoader.php',
'PhabricatorFileChunk' => 'applications/files/storage/PhabricatorFileChunk.php',
'PhabricatorFileChunkIterator' => 'applications/files/engine/PhabricatorFileChunkIterator.php',
'PhabricatorFileChunkQuery' => 'applications/files/query/PhabricatorFileChunkQuery.php',
'PhabricatorFileComposeController' => 'applications/files/controller/PhabricatorFileComposeController.php',
'PhabricatorFileController' => 'applications/files/controller/PhabricatorFileController.php',
'PhabricatorFileDAO' => 'applications/files/storage/PhabricatorFileDAO.php',
'PhabricatorFileDataController' => 'applications/files/controller/PhabricatorFileDataController.php',
'PhabricatorFileDeleteController' => 'applications/files/controller/PhabricatorFileDeleteController.php',
'PhabricatorFileDeleteTransaction' => 'applications/files/xaction/PhabricatorFileDeleteTransaction.php',
+ 'PhabricatorFileDetachController' => 'applications/files/controller/PhabricatorFileDetachController.php',
'PhabricatorFileDocumentController' => 'applications/files/controller/PhabricatorFileDocumentController.php',
'PhabricatorFileDocumentRenderingEngine' => 'applications/files/document/render/PhabricatorFileDocumentRenderingEngine.php',
'PhabricatorFileDropUploadController' => 'applications/files/controller/PhabricatorFileDropUploadController.php',
'PhabricatorFileEditController' => 'applications/files/controller/PhabricatorFileEditController.php',
'PhabricatorFileEditEngine' => 'applications/files/editor/PhabricatorFileEditEngine.php',
'PhabricatorFileEditField' => 'applications/transactions/editfield/PhabricatorFileEditField.php',
'PhabricatorFileEditor' => 'applications/files/editor/PhabricatorFileEditor.php',
'PhabricatorFileExternalRequest' => 'applications/files/storage/PhabricatorFileExternalRequest.php',
'PhabricatorFileExternalRequestGarbageCollector' => 'applications/files/garbagecollector/PhabricatorFileExternalRequestGarbageCollector.php',
'PhabricatorFileFilePHIDType' => 'applications/files/phid/PhabricatorFileFilePHIDType.php',
- 'PhabricatorFileHasObjectEdgeType' => 'applications/files/edge/PhabricatorFileHasObjectEdgeType.php',
'PhabricatorFileIconSetSelectController' => 'applications/files/controller/PhabricatorFileIconSetSelectController.php',
'PhabricatorFileImageMacro' => 'applications/macro/storage/PhabricatorFileImageMacro.php',
'PhabricatorFileImageProxyController' => 'applications/files/controller/PhabricatorFileImageProxyController.php',
'PhabricatorFileImageTransform' => 'applications/files/transform/PhabricatorFileImageTransform.php',
'PhabricatorFileIntegrityException' => 'applications/files/exception/PhabricatorFileIntegrityException.php',
'PhabricatorFileLightboxController' => 'applications/files/controller/PhabricatorFileLightboxController.php',
'PhabricatorFileLinkView' => 'view/layout/PhabricatorFileLinkView.php',
'PhabricatorFileListController' => 'applications/files/controller/PhabricatorFileListController.php',
'PhabricatorFileNameNgrams' => 'applications/files/storage/PhabricatorFileNameNgrams.php',
'PhabricatorFileNameTransaction' => 'applications/files/xaction/PhabricatorFileNameTransaction.php',
'PhabricatorFileQuery' => 'applications/files/query/PhabricatorFileQuery.php',
'PhabricatorFileROT13StorageFormat' => 'applications/files/format/PhabricatorFileROT13StorageFormat.php',
'PhabricatorFileRawStorageFormat' => 'applications/files/format/PhabricatorFileRawStorageFormat.php',
'PhabricatorFileSchemaSpec' => 'applications/files/storage/PhabricatorFileSchemaSpec.php',
'PhabricatorFileSearchConduitAPIMethod' => 'applications/files/conduit/PhabricatorFileSearchConduitAPIMethod.php',
'PhabricatorFileSearchEngine' => 'applications/files/query/PhabricatorFileSearchEngine.php',
'PhabricatorFileStorageBlob' => 'applications/files/storage/PhabricatorFileStorageBlob.php',
'PhabricatorFileStorageConfigurationException' => 'applications/files/exception/PhabricatorFileStorageConfigurationException.php',
'PhabricatorFileStorageEngine' => 'applications/files/engine/PhabricatorFileStorageEngine.php',
'PhabricatorFileStorageEngineTestCase' => 'applications/files/engine/__tests__/PhabricatorFileStorageEngineTestCase.php',
'PhabricatorFileStorageFormat' => 'applications/files/format/PhabricatorFileStorageFormat.php',
'PhabricatorFileStorageFormatTestCase' => 'applications/files/format/__tests__/PhabricatorFileStorageFormatTestCase.php',
'PhabricatorFileTemporaryGarbageCollector' => 'applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php',
'PhabricatorFileTestCase' => 'applications/files/storage/__tests__/PhabricatorFileTestCase.php',
'PhabricatorFileTestDataGenerator' => 'applications/files/lipsum/PhabricatorFileTestDataGenerator.php',
'PhabricatorFileThumbnailTransform' => 'applications/files/transform/PhabricatorFileThumbnailTransform.php',
'PhabricatorFileTransaction' => 'applications/files/storage/PhabricatorFileTransaction.php',
'PhabricatorFileTransactionComment' => 'applications/files/storage/PhabricatorFileTransactionComment.php',
'PhabricatorFileTransactionQuery' => 'applications/files/query/PhabricatorFileTransactionQuery.php',
'PhabricatorFileTransactionType' => 'applications/files/xaction/PhabricatorFileTransactionType.php',
'PhabricatorFileTransform' => 'applications/files/transform/PhabricatorFileTransform.php',
'PhabricatorFileTransformController' => 'applications/files/controller/PhabricatorFileTransformController.php',
'PhabricatorFileTransformListController' => 'applications/files/controller/PhabricatorFileTransformListController.php',
'PhabricatorFileTransformTestCase' => 'applications/files/transform/__tests__/PhabricatorFileTransformTestCase.php',
+ 'PhabricatorFileUICurtainAttachController' => 'applications/files/controller/PhabricatorFileUICurtainAttachController.php',
+ 'PhabricatorFileUICurtainListController' => 'applications/files/controller/PhabricatorFileUICurtainListController.php',
'PhabricatorFileUploadController' => 'applications/files/controller/PhabricatorFileUploadController.php',
'PhabricatorFileUploadDialogController' => 'applications/files/controller/PhabricatorFileUploadDialogController.php',
'PhabricatorFileUploadException' => 'applications/files/exception/PhabricatorFileUploadException.php',
'PhabricatorFileUploadSource' => 'applications/files/uploadsource/PhabricatorFileUploadSource.php',
'PhabricatorFileUploadSourceByteLimitException' => 'applications/files/uploadsource/PhabricatorFileUploadSourceByteLimitException.php',
'PhabricatorFileViewController' => 'applications/files/controller/PhabricatorFileViewController.php',
'PhabricatorFileinfoSetupCheck' => 'applications/config/check/PhabricatorFileinfoSetupCheck.php',
'PhabricatorFilesApplication' => 'applications/files/application/PhabricatorFilesApplication.php',
'PhabricatorFilesApplicationStorageEnginePanel' => 'applications/files/applicationpanel/PhabricatorFilesApplicationStorageEnginePanel.php',
'PhabricatorFilesBuiltinFile' => 'applications/files/builtin/PhabricatorFilesBuiltinFile.php',
'PhabricatorFilesComposeAvatarBuiltinFile' => 'applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php',
'PhabricatorFilesComposeIconBuiltinFile' => 'applications/files/builtin/PhabricatorFilesComposeIconBuiltinFile.php',
'PhabricatorFilesConfigOptions' => 'applications/files/config/PhabricatorFilesConfigOptions.php',
+ 'PhabricatorFilesCurtainExtension' => 'applications/files/engineextension/PhabricatorFilesCurtainExtension.php',
'PhabricatorFilesManagementCatWorkflow' => 'applications/files/management/PhabricatorFilesManagementCatWorkflow.php',
'PhabricatorFilesManagementCompactWorkflow' => 'applications/files/management/PhabricatorFilesManagementCompactWorkflow.php',
'PhabricatorFilesManagementCycleWorkflow' => 'applications/files/management/PhabricatorFilesManagementCycleWorkflow.php',
'PhabricatorFilesManagementEncodeWorkflow' => 'applications/files/management/PhabricatorFilesManagementEncodeWorkflow.php',
'PhabricatorFilesManagementEnginesWorkflow' => 'applications/files/management/PhabricatorFilesManagementEnginesWorkflow.php',
'PhabricatorFilesManagementGenerateKeyWorkflow' => 'applications/files/management/PhabricatorFilesManagementGenerateKeyWorkflow.php',
'PhabricatorFilesManagementIntegrityWorkflow' => 'applications/files/management/PhabricatorFilesManagementIntegrityWorkflow.php',
'PhabricatorFilesManagementMigrateWorkflow' => 'applications/files/management/PhabricatorFilesManagementMigrateWorkflow.php',
'PhabricatorFilesManagementRebuildWorkflow' => 'applications/files/management/PhabricatorFilesManagementRebuildWorkflow.php',
'PhabricatorFilesManagementWorkflow' => 'applications/files/management/PhabricatorFilesManagementWorkflow.php',
'PhabricatorFilesOnDiskBuiltinFile' => 'applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php',
'PhabricatorFilesOutboundRequestAction' => 'applications/files/action/PhabricatorFilesOutboundRequestAction.php',
'PhabricatorFiletreeVisibleSetting' => 'applications/settings/setting/PhabricatorFiletreeVisibleSetting.php',
'PhabricatorFiletreeWidthSetting' => 'applications/settings/setting/PhabricatorFiletreeWidthSetting.php',
'PhabricatorFlag' => 'applications/flag/storage/PhabricatorFlag.php',
'PhabricatorFlagAddFlagHeraldAction' => 'applications/flag/herald/PhabricatorFlagAddFlagHeraldAction.php',
'PhabricatorFlagColor' => 'applications/flag/constants/PhabricatorFlagColor.php',
'PhabricatorFlagConstants' => 'applications/flag/constants/PhabricatorFlagConstants.php',
'PhabricatorFlagController' => 'applications/flag/controller/PhabricatorFlagController.php',
'PhabricatorFlagDAO' => 'applications/flag/storage/PhabricatorFlagDAO.php',
'PhabricatorFlagDeleteController' => 'applications/flag/controller/PhabricatorFlagDeleteController.php',
'PhabricatorFlagDestructionEngineExtension' => 'applications/flag/engineextension/PhabricatorFlagDestructionEngineExtension.php',
'PhabricatorFlagEditController' => 'applications/flag/controller/PhabricatorFlagEditController.php',
'PhabricatorFlagHeraldAction' => 'applications/flag/herald/PhabricatorFlagHeraldAction.php',
'PhabricatorFlagListController' => 'applications/flag/controller/PhabricatorFlagListController.php',
'PhabricatorFlagQuery' => 'applications/flag/query/PhabricatorFlagQuery.php',
'PhabricatorFlagRemoveFlagHeraldAction' => 'applications/flag/herald/PhabricatorFlagRemoveFlagHeraldAction.php',
'PhabricatorFlagSearchEngine' => 'applications/flag/query/PhabricatorFlagSearchEngine.php',
'PhabricatorFlagSelectControl' => 'applications/flag/view/PhabricatorFlagSelectControl.php',
'PhabricatorFlaggableInterface' => 'applications/flag/interface/PhabricatorFlaggableInterface.php',
'PhabricatorFlagsApplication' => 'applications/flag/application/PhabricatorFlagsApplication.php',
'PhabricatorFlagsUIEventListener' => 'applications/flag/events/PhabricatorFlagsUIEventListener.php',
'PhabricatorFullLoginUserLogType' => 'applications/people/userlog/PhabricatorFullLoginUserLogType.php',
'PhabricatorFulltextEngine' => 'applications/search/index/PhabricatorFulltextEngine.php',
'PhabricatorFulltextEngineExtension' => 'applications/search/index/PhabricatorFulltextEngineExtension.php',
'PhabricatorFulltextEngineExtensionModule' => 'applications/search/index/PhabricatorFulltextEngineExtensionModule.php',
'PhabricatorFulltextIndexEngineExtension' => 'applications/search/engineextension/PhabricatorFulltextIndexEngineExtension.php',
'PhabricatorFulltextInterface' => 'applications/search/interface/PhabricatorFulltextInterface.php',
'PhabricatorFulltextResultSet' => 'applications/search/query/PhabricatorFulltextResultSet.php',
'PhabricatorFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php',
'PhabricatorFulltextToken' => 'applications/search/query/PhabricatorFulltextToken.php',
'PhabricatorFundApplication' => 'applications/fund/application/PhabricatorFundApplication.php',
'PhabricatorGDSetupCheck' => 'applications/config/check/PhabricatorGDSetupCheck.php',
'PhabricatorGarbageCollector' => 'infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php',
'PhabricatorGarbageCollectorManagementCollectWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementCollectWorkflow.php',
'PhabricatorGarbageCollectorManagementCompactEdgesWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementCompactEdgesWorkflow.php',
'PhabricatorGarbageCollectorManagementSetPolicyWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementSetPolicyWorkflow.php',
'PhabricatorGarbageCollectorManagementWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementWorkflow.php',
'PhabricatorGeneralCachePurger' => 'applications/cache/purger/PhabricatorGeneralCachePurger.php',
'PhabricatorGestureUIExample' => 'applications/uiexample/examples/PhabricatorGestureUIExample.php',
'PhabricatorGitGraphStream' => 'applications/repository/daemon/PhabricatorGitGraphStream.php',
'PhabricatorGitHubAuthProvider' => 'applications/auth/provider/PhabricatorGitHubAuthProvider.php',
'PhabricatorGlobalLock' => 'infrastructure/util/PhabricatorGlobalLock.php',
'PhabricatorGlobalLockTestCase' => 'infrastructure/util/__tests__/PhabricatorGlobalLockTestCase.php',
'PhabricatorGlobalUploadTargetView' => 'applications/files/view/PhabricatorGlobalUploadTargetView.php',
'PhabricatorGoogleAuthProvider' => 'applications/auth/provider/PhabricatorGoogleAuthProvider.php',
'PhabricatorGuidanceContext' => 'applications/guides/guidance/PhabricatorGuidanceContext.php',
'PhabricatorGuidanceEngine' => 'applications/guides/guidance/PhabricatorGuidanceEngine.php',
'PhabricatorGuidanceEngineExtension' => 'applications/guides/guidance/PhabricatorGuidanceEngineExtension.php',
'PhabricatorGuidanceMessage' => 'applications/guides/guidance/PhabricatorGuidanceMessage.php',
'PhabricatorGuideApplication' => 'applications/guides/application/PhabricatorGuideApplication.php',
'PhabricatorGuideController' => 'applications/guides/controller/PhabricatorGuideController.php',
'PhabricatorGuideInstallModule' => 'applications/guides/module/PhabricatorGuideInstallModule.php',
'PhabricatorGuideItemView' => 'applications/guides/view/PhabricatorGuideItemView.php',
'PhabricatorGuideListView' => 'applications/guides/view/PhabricatorGuideListView.php',
'PhabricatorGuideModule' => 'applications/guides/module/PhabricatorGuideModule.php',
'PhabricatorGuideModuleController' => 'applications/guides/controller/PhabricatorGuideModuleController.php',
'PhabricatorGuideQuickStartModule' => 'applications/guides/module/PhabricatorGuideQuickStartModule.php',
'PhabricatorHMACTestCase' => 'infrastructure/util/__tests__/PhabricatorHMACTestCase.php',
'PhabricatorHTTPParameterTypeTableView' => 'applications/config/view/PhabricatorHTTPParameterTypeTableView.php',
'PhabricatorHandleList' => 'applications/phid/handle/pool/PhabricatorHandleList.php',
'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/PhabricatorHandleObjectSelectorDataView.php',
'PhabricatorHandlePool' => 'applications/phid/handle/pool/PhabricatorHandlePool.php',
'PhabricatorHandlePoolTestCase' => 'applications/phid/handle/pool/__tests__/PhabricatorHandlePoolTestCase.php',
'PhabricatorHandleQuery' => 'applications/phid/query/PhabricatorHandleQuery.php',
'PhabricatorHandleRemarkupRule' => 'applications/phid/remarkup/PhabricatorHandleRemarkupRule.php',
'PhabricatorHandlesEditField' => 'applications/transactions/editfield/PhabricatorHandlesEditField.php',
'PhabricatorHarbormasterApplication' => 'applications/harbormaster/application/PhabricatorHarbormasterApplication.php',
'PhabricatorHash' => 'infrastructure/util/PhabricatorHash.php',
'PhabricatorHashTestCase' => 'infrastructure/util/__tests__/PhabricatorHashTestCase.php',
'PhabricatorHelpApplication' => 'applications/help/application/PhabricatorHelpApplication.php',
'PhabricatorHelpController' => 'applications/help/controller/PhabricatorHelpController.php',
'PhabricatorHelpDocumentationController' => 'applications/help/controller/PhabricatorHelpDocumentationController.php',
'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php',
'PhabricatorHeraldApplication' => 'applications/herald/application/PhabricatorHeraldApplication.php',
'PhabricatorHeraldContentSource' => 'applications/herald/contentsource/PhabricatorHeraldContentSource.php',
'PhabricatorHexdumpDocumentEngine' => 'applications/files/document/PhabricatorHexdumpDocumentEngine.php',
'PhabricatorHighSecurityRequestExceptionHandler' => 'aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php',
'PhabricatorHigherOrderChartFunction' => 'applications/fact/chart/PhabricatorHigherOrderChartFunction.php',
'PhabricatorHomeApplication' => 'applications/home/application/PhabricatorHomeApplication.php',
'PhabricatorHomeConstants' => 'applications/home/constants/PhabricatorHomeConstants.php',
'PhabricatorHomeController' => 'applications/home/controller/PhabricatorHomeController.php',
'PhabricatorHomeLauncherProfileMenuItem' => 'applications/home/menuitem/PhabricatorHomeLauncherProfileMenuItem.php',
'PhabricatorHomeMenuItemController' => 'applications/home/controller/PhabricatorHomeMenuItemController.php',
'PhabricatorHomeProfileMenuEngine' => 'applications/home/engine/PhabricatorHomeProfileMenuEngine.php',
'PhabricatorHomeProfileMenuItem' => 'applications/home/menuitem/PhabricatorHomeProfileMenuItem.php',
'PhabricatorHovercardEngineExtension' => 'applications/search/engineextension/PhabricatorHovercardEngineExtension.php',
'PhabricatorHovercardEngineExtensionModule' => 'applications/search/engineextension/PhabricatorHovercardEngineExtensionModule.php',
'PhabricatorIDExportField' => 'infrastructure/export/field/PhabricatorIDExportField.php',
'PhabricatorIDsSearchEngineExtension' => 'applications/search/engineextension/PhabricatorIDsSearchEngineExtension.php',
'PhabricatorIDsSearchField' => 'applications/search/field/PhabricatorIDsSearchField.php',
'PhabricatorIconDatasource' => 'applications/files/typeahead/PhabricatorIconDatasource.php',
'PhabricatorIconRemarkupRule' => 'applications/macro/markup/PhabricatorIconRemarkupRule.php',
'PhabricatorIconSet' => 'applications/files/iconset/PhabricatorIconSet.php',
'PhabricatorIconSetEditField' => 'applications/transactions/editfield/PhabricatorIconSetEditField.php',
'PhabricatorIconSetIcon' => 'applications/files/iconset/PhabricatorIconSetIcon.php',
'PhabricatorImageDocumentEngine' => 'applications/files/document/PhabricatorImageDocumentEngine.php',
'PhabricatorImageMacroRemarkupRule' => 'applications/macro/markup/PhabricatorImageMacroRemarkupRule.php',
'PhabricatorImageRemarkupRule' => 'applications/files/markup/PhabricatorImageRemarkupRule.php',
'PhabricatorImageTransformer' => 'applications/files/PhabricatorImageTransformer.php',
'PhabricatorImagemagickSetupCheck' => 'applications/config/check/PhabricatorImagemagickSetupCheck.php',
'PhabricatorInFlightErrorView' => 'applications/config/view/PhabricatorInFlightErrorView.php',
'PhabricatorIndexEngine' => 'applications/search/index/PhabricatorIndexEngine.php',
'PhabricatorIndexEngineExtension' => 'applications/search/index/PhabricatorIndexEngineExtension.php',
'PhabricatorIndexEngineExtensionModule' => 'applications/search/index/PhabricatorIndexEngineExtensionModule.php',
'PhabricatorIndexableInterface' => 'applications/search/interface/PhabricatorIndexableInterface.php',
'PhabricatorInfrastructureTestCase' => '__tests__/PhabricatorInfrastructureTestCase.php',
'PhabricatorInlineComment' => 'infrastructure/diff/interface/PhabricatorInlineComment.php',
'PhabricatorInlineCommentAdjustmentEngine' => 'infrastructure/diff/engine/PhabricatorInlineCommentAdjustmentEngine.php',
'PhabricatorInlineCommentContentState' => 'infrastructure/diff/inline/PhabricatorInlineCommentContentState.php',
'PhabricatorInlineCommentContext' => 'infrastructure/diff/inline/PhabricatorInlineCommentContext.php',
'PhabricatorInlineCommentController' => 'infrastructure/diff/PhabricatorInlineCommentController.php',
'PhabricatorInlineCommentInterface' => 'applications/transactions/interface/PhabricatorInlineCommentInterface.php',
'PhabricatorInlineSummaryView' => 'infrastructure/diff/view/PhabricatorInlineSummaryView.php',
'PhabricatorInstructionsEditField' => 'applications/transactions/editfield/PhabricatorInstructionsEditField.php',
'PhabricatorIntConfigType' => 'applications/config/type/PhabricatorIntConfigType.php',
'PhabricatorIntEditField' => 'applications/transactions/editfield/PhabricatorIntEditField.php',
'PhabricatorIntExportField' => 'infrastructure/export/field/PhabricatorIntExportField.php',
'PhabricatorInternalSetting' => 'applications/settings/setting/PhabricatorInternalSetting.php',
'PhabricatorInternationalizationManagementExtractWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php',
'PhabricatorInternationalizationManagementWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementWorkflow.php',
'PhabricatorInvalidConfigSetupCheck' => 'applications/config/check/PhabricatorInvalidConfigSetupCheck.php',
'PhabricatorInvalidQueryCursorException' => 'infrastructure/query/exception/PhabricatorInvalidQueryCursorException.php',
'PhabricatorIteratedMD5PasswordHasher' => 'infrastructure/util/password/PhabricatorIteratedMD5PasswordHasher.php',
'PhabricatorIteratedMD5PasswordHasherTestCase' => 'infrastructure/util/password/__tests__/PhabricatorIteratedMD5PasswordHasherTestCase.php',
'PhabricatorIteratorFileUploadSource' => 'applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php',
'PhabricatorJIRAAuthProvider' => 'applications/auth/provider/PhabricatorJIRAAuthProvider.php',
'PhabricatorJSONConfigType' => 'applications/config/type/PhabricatorJSONConfigType.php',
'PhabricatorJSONDocumentEngine' => 'applications/files/document/PhabricatorJSONDocumentEngine.php',
'PhabricatorJSONExportFormat' => 'infrastructure/export/format/PhabricatorJSONExportFormat.php',
'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php',
'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php',
'PhabricatorJupyterDocumentEngine' => 'applications/files/document/PhabricatorJupyterDocumentEngine.php',
'PhabricatorKeyValueDatabaseCache' => 'applications/cache/PhabricatorKeyValueDatabaseCache.php',
'PhabricatorKeyValueSerializingCacheProxy' => 'applications/cache/PhabricatorKeyValueSerializingCacheProxy.php',
'PhabricatorKeyboardRemarkupRule' => 'infrastructure/markup/rule/PhabricatorKeyboardRemarkupRule.php',
'PhabricatorKeyring' => 'applications/files/keyring/PhabricatorKeyring.php',
'PhabricatorKeyringConfigOptionType' => 'applications/files/keyring/PhabricatorKeyringConfigOptionType.php',
'PhabricatorLDAPAuthProvider' => 'applications/auth/provider/PhabricatorLDAPAuthProvider.php',
'PhabricatorLabelProfileMenuItem' => 'applications/search/menuitem/PhabricatorLabelProfileMenuItem.php',
'PhabricatorLanguageSettingsPanel' => 'applications/settings/panel/PhabricatorLanguageSettingsPanel.php',
'PhabricatorLegalpadApplication' => 'applications/legalpad/application/PhabricatorLegalpadApplication.php',
'PhabricatorLegalpadDocumentPHIDType' => 'applications/legalpad/phid/PhabricatorLegalpadDocumentPHIDType.php',
'PhabricatorLegalpadSignaturePolicyRule' => 'applications/legalpad/policyrule/PhabricatorLegalpadSignaturePolicyRule.php',
'PhabricatorLibraryTestCase' => '__tests__/PhabricatorLibraryTestCase.php',
'PhabricatorLinkProfileMenuItem' => 'applications/search/menuitem/PhabricatorLinkProfileMenuItem.php',
'PhabricatorLipsumArtist' => 'applications/lipsum/image/PhabricatorLipsumArtist.php',
'PhabricatorLipsumContentSource' => 'infrastructure/contentsource/PhabricatorLipsumContentSource.php',
'PhabricatorLipsumGenerateWorkflow' => 'applications/lipsum/management/PhabricatorLipsumGenerateWorkflow.php',
'PhabricatorLipsumManagementWorkflow' => 'applications/lipsum/management/PhabricatorLipsumManagementWorkflow.php',
'PhabricatorLipsumMondrianArtist' => 'applications/lipsum/image/PhabricatorLipsumMondrianArtist.php',
'PhabricatorLiskDAO' => 'infrastructure/storage/lisk/PhabricatorLiskDAO.php',
'PhabricatorLiskExportEngineExtension' => 'infrastructure/export/engine/PhabricatorLiskExportEngineExtension.php',
'PhabricatorLiskFulltextEngineExtension' => 'applications/search/engineextension/PhabricatorLiskFulltextEngineExtension.php',
'PhabricatorLiskSearchEngineExtension' => 'applications/search/engineextension/PhabricatorLiskSearchEngineExtension.php',
'PhabricatorLiskSerializer' => 'infrastructure/storage/lisk/PhabricatorLiskSerializer.php',
'PhabricatorListExportField' => 'infrastructure/export/field/PhabricatorListExportField.php',
'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php',
'PhabricatorLocalTimeTestCase' => 'view/__tests__/PhabricatorLocalTimeTestCase.php',
'PhabricatorLocaleScopeGuard' => 'infrastructure/internationalization/scope/PhabricatorLocaleScopeGuard.php',
'PhabricatorLocaleScopeGuardTestCase' => 'infrastructure/internationalization/scope/__tests__/PhabricatorLocaleScopeGuardTestCase.php',
'PhabricatorLockLogManagementWorkflow' => 'applications/daemon/management/PhabricatorLockLogManagementWorkflow.php',
'PhabricatorLockManagementWorkflow' => 'applications/daemon/management/PhabricatorLockManagementWorkflow.php',
'PhabricatorLogTriggerAction' => 'infrastructure/daemon/workers/action/PhabricatorLogTriggerAction.php',
'PhabricatorLoginFailureUserLogType' => 'applications/people/userlog/PhabricatorLoginFailureUserLogType.php',
'PhabricatorLoginUserLogType' => 'applications/people/userlog/PhabricatorLoginUserLogType.php',
'PhabricatorLogoutController' => 'applications/auth/controller/PhabricatorLogoutController.php',
'PhabricatorLogoutUserLogType' => 'applications/people/userlog/PhabricatorLogoutUserLogType.php',
'PhabricatorLunarPhasePolicyRule' => 'applications/policy/rule/PhabricatorLunarPhasePolicyRule.php',
'PhabricatorMacroApplication' => 'applications/macro/application/PhabricatorMacroApplication.php',
'PhabricatorMacroAudioBehaviorTransaction' => 'applications/macro/xaction/PhabricatorMacroAudioBehaviorTransaction.php',
'PhabricatorMacroAudioController' => 'applications/macro/controller/PhabricatorMacroAudioController.php',
'PhabricatorMacroAudioTransaction' => 'applications/macro/xaction/PhabricatorMacroAudioTransaction.php',
'PhabricatorMacroController' => 'applications/macro/controller/PhabricatorMacroController.php',
'PhabricatorMacroDatasource' => 'applications/macro/typeahead/PhabricatorMacroDatasource.php',
'PhabricatorMacroDisableController' => 'applications/macro/controller/PhabricatorMacroDisableController.php',
'PhabricatorMacroDisabledTransaction' => 'applications/macro/xaction/PhabricatorMacroDisabledTransaction.php',
'PhabricatorMacroEditController' => 'applications/macro/controller/PhabricatorMacroEditController.php',
'PhabricatorMacroEditEngine' => 'applications/macro/editor/PhabricatorMacroEditEngine.php',
'PhabricatorMacroEditor' => 'applications/macro/editor/PhabricatorMacroEditor.php',
'PhabricatorMacroFileTransaction' => 'applications/macro/xaction/PhabricatorMacroFileTransaction.php',
'PhabricatorMacroListController' => 'applications/macro/controller/PhabricatorMacroListController.php',
'PhabricatorMacroMacroPHIDType' => 'applications/macro/phid/PhabricatorMacroMacroPHIDType.php',
'PhabricatorMacroMailReceiver' => 'applications/macro/mail/PhabricatorMacroMailReceiver.php',
'PhabricatorMacroManageCapability' => 'applications/macro/capability/PhabricatorMacroManageCapability.php',
'PhabricatorMacroMemeController' => 'applications/macro/controller/PhabricatorMacroMemeController.php',
'PhabricatorMacroMemeDialogController' => 'applications/macro/controller/PhabricatorMacroMemeDialogController.php',
'PhabricatorMacroNameTransaction' => 'applications/macro/xaction/PhabricatorMacroNameTransaction.php',
'PhabricatorMacroQuery' => 'applications/macro/query/PhabricatorMacroQuery.php',
'PhabricatorMacroReplyHandler' => 'applications/macro/mail/PhabricatorMacroReplyHandler.php',
'PhabricatorMacroSearchEngine' => 'applications/macro/query/PhabricatorMacroSearchEngine.php',
'PhabricatorMacroTestCase' => 'applications/macro/xaction/__tests__/PhabricatorMacroTestCase.php',
'PhabricatorMacroTransaction' => 'applications/macro/storage/PhabricatorMacroTransaction.php',
'PhabricatorMacroTransactionComment' => 'applications/macro/storage/PhabricatorMacroTransactionComment.php',
'PhabricatorMacroTransactionQuery' => 'applications/macro/query/PhabricatorMacroTransactionQuery.php',
'PhabricatorMacroTransactionType' => 'applications/macro/xaction/PhabricatorMacroTransactionType.php',
'PhabricatorMacroViewController' => 'applications/macro/controller/PhabricatorMacroViewController.php',
'PhabricatorMailAdapter' => 'applications/metamta/adapter/PhabricatorMailAdapter.php',
'PhabricatorMailAdapterTestCase' => 'applications/metamta/adapter/__tests__/PhabricatorMailAdapterTestCase.php',
'PhabricatorMailAmazonSESAdapter' => 'applications/metamta/adapter/PhabricatorMailAmazonSESAdapter.php',
'PhabricatorMailAmazonSNSAdapter' => 'applications/metamta/adapter/PhabricatorMailAmazonSNSAdapter.php',
'PhabricatorMailAttachment' => 'applications/metamta/message/PhabricatorMailAttachment.php',
'PhabricatorMailConfigTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMailConfigTestCase.php',
'PhabricatorMailEmailEngine' => 'applications/metamta/engine/PhabricatorMailEmailEngine.php',
'PhabricatorMailEmailHeraldField' => 'applications/metamta/herald/PhabricatorMailEmailHeraldField.php',
'PhabricatorMailEmailHeraldFieldGroup' => 'applications/metamta/herald/PhabricatorMailEmailHeraldFieldGroup.php',
'PhabricatorMailEmailMessage' => 'applications/metamta/message/PhabricatorMailEmailMessage.php',
'PhabricatorMailEmailSubjectHeraldField' => 'applications/metamta/herald/PhabricatorMailEmailSubjectHeraldField.php',
'PhabricatorMailEngineExtension' => 'applications/metamta/engine/PhabricatorMailEngineExtension.php',
'PhabricatorMailExternalMessage' => 'applications/metamta/message/PhabricatorMailExternalMessage.php',
'PhabricatorMailHeader' => 'applications/metamta/message/PhabricatorMailHeader.php',
'PhabricatorMailMailgunAdapter' => 'applications/metamta/adapter/PhabricatorMailMailgunAdapter.php',
'PhabricatorMailManagementListInboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementListInboundWorkflow.php',
'PhabricatorMailManagementListOutboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementListOutboundWorkflow.php',
'PhabricatorMailManagementReceiveTestWorkflow' => 'applications/metamta/management/PhabricatorMailManagementReceiveTestWorkflow.php',
'PhabricatorMailManagementResendWorkflow' => 'applications/metamta/management/PhabricatorMailManagementResendWorkflow.php',
'PhabricatorMailManagementSendTestWorkflow' => 'applications/metamta/management/PhabricatorMailManagementSendTestWorkflow.php',
'PhabricatorMailManagementShowInboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementShowInboundWorkflow.php',
'PhabricatorMailManagementShowOutboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php',
'PhabricatorMailManagementUnverifyWorkflow' => 'applications/metamta/management/PhabricatorMailManagementUnverifyWorkflow.php',
'PhabricatorMailManagementVolumeWorkflow' => 'applications/metamta/management/PhabricatorMailManagementVolumeWorkflow.php',
'PhabricatorMailManagementWorkflow' => 'applications/metamta/management/PhabricatorMailManagementWorkflow.php',
'PhabricatorMailMessageEngine' => 'applications/metamta/engine/PhabricatorMailMessageEngine.php',
'PhabricatorMailMustEncryptHeraldAction' => 'applications/metamta/herald/PhabricatorMailMustEncryptHeraldAction.php',
'PhabricatorMailOutboundMailHeraldAdapter' => 'applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php',
'PhabricatorMailOutboundRoutingHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingHeraldAction.php',
'PhabricatorMailOutboundRoutingSelfEmailHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingSelfEmailHeraldAction.php',
'PhabricatorMailOutboundRoutingSelfNotificationHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingSelfNotificationHeraldAction.php',
'PhabricatorMailOutboundStatus' => 'applications/metamta/constants/PhabricatorMailOutboundStatus.php',
'PhabricatorMailPostmarkAdapter' => 'applications/metamta/adapter/PhabricatorMailPostmarkAdapter.php',
'PhabricatorMailPropertiesDestructionEngineExtension' => 'applications/metamta/engineextension/PhabricatorMailPropertiesDestructionEngineExtension.php',
'PhabricatorMailReceiver' => 'applications/metamta/receiver/PhabricatorMailReceiver.php',
'PhabricatorMailReceiverTestCase' => 'applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php',
'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/PhabricatorMailReplyHandler.php',
'PhabricatorMailRoutingRule' => 'applications/metamta/constants/PhabricatorMailRoutingRule.php',
'PhabricatorMailSMSEngine' => 'applications/metamta/engine/PhabricatorMailSMSEngine.php',
'PhabricatorMailSMSMessage' => 'applications/metamta/message/PhabricatorMailSMSMessage.php',
'PhabricatorMailSMTPAdapter' => 'applications/metamta/adapter/PhabricatorMailSMTPAdapter.php',
'PhabricatorMailSendGridAdapter' => 'applications/metamta/adapter/PhabricatorMailSendGridAdapter.php',
'PhabricatorMailSendmailAdapter' => 'applications/metamta/adapter/PhabricatorMailSendmailAdapter.php',
'PhabricatorMailSetupCheck' => 'applications/config/check/PhabricatorMailSetupCheck.php',
'PhabricatorMailStamp' => 'applications/metamta/stamp/PhabricatorMailStamp.php',
'PhabricatorMailTarget' => 'applications/metamta/replyhandler/PhabricatorMailTarget.php',
'PhabricatorMailTestAdapter' => 'applications/metamta/adapter/PhabricatorMailTestAdapter.php',
'PhabricatorMailTwilioAdapter' => 'applications/metamta/adapter/PhabricatorMailTwilioAdapter.php',
'PhabricatorMailUtil' => 'applications/metamta/util/PhabricatorMailUtil.php',
'PhabricatorMainMenuBarExtension' => 'view/page/menu/PhabricatorMainMenuBarExtension.php',
'PhabricatorMainMenuSearchView' => 'view/page/menu/PhabricatorMainMenuSearchView.php',
'PhabricatorMainMenuView' => 'view/page/menu/PhabricatorMainMenuView.php',
'PhabricatorManageProfileMenuItem' => 'applications/search/menuitem/PhabricatorManageProfileMenuItem.php',
'PhabricatorManagementWorkflow' => 'infrastructure/management/PhabricatorManagementWorkflow.php',
'PhabricatorManiphestApplication' => 'applications/maniphest/application/PhabricatorManiphestApplication.php',
'PhabricatorManiphestConfigOptions' => 'applications/maniphest/config/PhabricatorManiphestConfigOptions.php',
'PhabricatorManiphestTaskFactEngine' => 'applications/fact/engine/PhabricatorManiphestTaskFactEngine.php',
'PhabricatorManiphestTaskTestDataGenerator' => 'applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php',
'PhabricatorManualActivitySetupCheck' => 'applications/config/check/PhabricatorManualActivitySetupCheck.php',
'PhabricatorMarkupCache' => 'applications/cache/storage/PhabricatorMarkupCache.php',
'PhabricatorMarkupEngine' => 'infrastructure/markup/PhabricatorMarkupEngine.php',
'PhabricatorMarkupEngineTestCase' => 'infrastructure/markup/__tests__/PhabricatorMarkupEngineTestCase.php',
'PhabricatorMarkupInterface' => 'infrastructure/markup/PhabricatorMarkupInterface.php',
'PhabricatorMarkupOneOff' => 'infrastructure/markup/PhabricatorMarkupOneOff.php',
'PhabricatorMarkupPreviewController' => 'infrastructure/markup/PhabricatorMarkupPreviewController.php',
'PhabricatorMaxChartFunction' => 'applications/fact/chart/PhabricatorMaxChartFunction.php',
'PhabricatorMemeEngine' => 'applications/macro/engine/PhabricatorMemeEngine.php',
'PhabricatorMemeRemarkupRule' => 'applications/macro/markup/PhabricatorMemeRemarkupRule.php',
'PhabricatorMentionRemarkupRule' => 'applications/people/markup/PhabricatorMentionRemarkupRule.php',
'PhabricatorMentionableInterface' => 'applications/transactions/interface/PhabricatorMentionableInterface.php',
'PhabricatorMercurialGraphStream' => 'applications/repository/daemon/PhabricatorMercurialGraphStream.php',
'PhabricatorMetaMTAActor' => 'applications/metamta/query/PhabricatorMetaMTAActor.php',
'PhabricatorMetaMTAActorQuery' => 'applications/metamta/query/PhabricatorMetaMTAActorQuery.php',
'PhabricatorMetaMTAApplication' => 'applications/metamta/application/PhabricatorMetaMTAApplication.php',
'PhabricatorMetaMTAApplicationEmail' => 'applications/metamta/storage/PhabricatorMetaMTAApplicationEmail.php',
'PhabricatorMetaMTAApplicationEmailDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAApplicationEmailDatasource.php',
'PhabricatorMetaMTAApplicationEmailEditor' => 'applications/metamta/editor/PhabricatorMetaMTAApplicationEmailEditor.php',
'PhabricatorMetaMTAApplicationEmailHeraldField' => 'applications/metamta/herald/PhabricatorMetaMTAApplicationEmailHeraldField.php',
'PhabricatorMetaMTAApplicationEmailPHIDType' => 'applications/phid/PhabricatorMetaMTAApplicationEmailPHIDType.php',
'PhabricatorMetaMTAApplicationEmailPanel' => 'applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php',
'PhabricatorMetaMTAApplicationEmailQuery' => 'applications/metamta/query/PhabricatorMetaMTAApplicationEmailQuery.php',
'PhabricatorMetaMTAApplicationEmailTransaction' => 'applications/metamta/storage/PhabricatorMetaMTAApplicationEmailTransaction.php',
'PhabricatorMetaMTAApplicationEmailTransactionQuery' => 'applications/metamta/query/PhabricatorMetaMTAApplicationEmailTransactionQuery.php',
'PhabricatorMetaMTAConfigOptions' => 'applications/config/option/PhabricatorMetaMTAConfigOptions.php',
'PhabricatorMetaMTAController' => 'applications/metamta/controller/PhabricatorMetaMTAController.php',
'PhabricatorMetaMTADAO' => 'applications/metamta/storage/PhabricatorMetaMTADAO.php',
'PhabricatorMetaMTAEmailBodyParser' => 'applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php',
'PhabricatorMetaMTAEmailBodyParserTestCase' => 'applications/metamta/parser/__tests__/PhabricatorMetaMTAEmailBodyParserTestCase.php',
'PhabricatorMetaMTAEmailHeraldAction' => 'applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php',
'PhabricatorMetaMTAEmailOthersHeraldAction' => 'applications/metamta/herald/PhabricatorMetaMTAEmailOthersHeraldAction.php',
'PhabricatorMetaMTAEmailSelfHeraldAction' => 'applications/metamta/herald/PhabricatorMetaMTAEmailSelfHeraldAction.php',
'PhabricatorMetaMTAErrorMailAction' => 'applications/metamta/action/PhabricatorMetaMTAErrorMailAction.php',
'PhabricatorMetaMTAMail' => 'applications/metamta/storage/PhabricatorMetaMTAMail.php',
'PhabricatorMetaMTAMailBody' => 'applications/metamta/view/PhabricatorMetaMTAMailBody.php',
'PhabricatorMetaMTAMailBodyTestCase' => 'applications/metamta/view/__tests__/PhabricatorMetaMTAMailBodyTestCase.php',
'PhabricatorMetaMTAMailHasRecipientEdgeType' => 'applications/metamta/edge/PhabricatorMetaMTAMailHasRecipientEdgeType.php',
'PhabricatorMetaMTAMailListController' => 'applications/metamta/controller/PhabricatorMetaMTAMailListController.php',
'PhabricatorMetaMTAMailPHIDType' => 'applications/metamta/phid/PhabricatorMetaMTAMailPHIDType.php',
'PhabricatorMetaMTAMailProperties' => 'applications/metamta/storage/PhabricatorMetaMTAMailProperties.php',
'PhabricatorMetaMTAMailPropertiesQuery' => 'applications/metamta/query/PhabricatorMetaMTAMailPropertiesQuery.php',
'PhabricatorMetaMTAMailQuery' => 'applications/metamta/query/PhabricatorMetaMTAMailQuery.php',
'PhabricatorMetaMTAMailSearchEngine' => 'applications/metamta/query/PhabricatorMetaMTAMailSearchEngine.php',
'PhabricatorMetaMTAMailSection' => 'applications/metamta/view/PhabricatorMetaMTAMailSection.php',
'PhabricatorMetaMTAMailTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMetaMTAMailTestCase.php',
'PhabricatorMetaMTAMailViewController' => 'applications/metamta/controller/PhabricatorMetaMTAMailViewController.php',
'PhabricatorMetaMTAMailableDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAMailableDatasource.php',
'PhabricatorMetaMTAMailableFunctionDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAMailableFunctionDatasource.php',
'PhabricatorMetaMTAMailgunReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php',
'PhabricatorMetaMTAMemberQuery' => 'applications/metamta/query/PhabricatorMetaMTAMemberQuery.php',
'PhabricatorMetaMTAPermanentFailureException' => 'applications/metamta/exception/PhabricatorMetaMTAPermanentFailureException.php',
'PhabricatorMetaMTAPostmarkReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTAPostmarkReceiveController.php',
'PhabricatorMetaMTAReceivedMail' => 'applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php',
'PhabricatorMetaMTAReceivedMailProcessingException' => 'applications/metamta/exception/PhabricatorMetaMTAReceivedMailProcessingException.php',
'PhabricatorMetaMTAReceivedMailTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMetaMTAReceivedMailTestCase.php',
'PhabricatorMetaMTASchemaSpec' => 'applications/metamta/storage/PhabricatorMetaMTASchemaSpec.php',
'PhabricatorMetaMTASendGridReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTASendGridReceiveController.php',
'PhabricatorMetaMTAWorker' => 'applications/metamta/PhabricatorMetaMTAWorker.php',
'PhabricatorMetronome' => 'infrastructure/util/PhabricatorMetronome.php',
'PhabricatorMetronomeTestCase' => 'infrastructure/util/__tests__/PhabricatorMetronomeTestCase.php',
'PhabricatorMetronomicTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorMetronomicTriggerClock.php',
'PhabricatorMinChartFunction' => 'applications/fact/chart/PhabricatorMinChartFunction.php',
'PhabricatorModularTransaction' => 'applications/transactions/storage/PhabricatorModularTransaction.php',
'PhabricatorModularTransactionType' => 'applications/transactions/storage/PhabricatorModularTransactionType.php',
'PhabricatorMonogramDatasourceEngineExtension' => 'applications/typeahead/engineextension/PhabricatorMonogramDatasourceEngineExtension.php',
'PhabricatorMonospacedFontSetting' => 'applications/settings/setting/PhabricatorMonospacedFontSetting.php',
'PhabricatorMonospacedTextareasSetting' => 'applications/settings/setting/PhabricatorMonospacedTextareasSetting.php',
'PhabricatorMotivatorProfileMenuItem' => 'applications/search/menuitem/PhabricatorMotivatorProfileMenuItem.php',
'PhabricatorMultiColumnUIExample' => 'applications/uiexample/examples/PhabricatorMultiColumnUIExample.php',
'PhabricatorMultiFactorSettingsPanel' => 'applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php',
'PhabricatorMultimeterApplication' => 'applications/multimeter/application/PhabricatorMultimeterApplication.php',
'PhabricatorMustVerifyEmailController' => 'applications/auth/controller/PhabricatorMustVerifyEmailController.php',
'PhabricatorMutedByEdgeType' => 'applications/transactions/edges/PhabricatorMutedByEdgeType.php',
'PhabricatorMutedEdgeType' => 'applications/transactions/edges/PhabricatorMutedEdgeType.php',
'PhabricatorMySQLConfigOptions' => 'applications/config/option/PhabricatorMySQLConfigOptions.php',
'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/PhabricatorMySQLFileStorageEngine.php',
'PhabricatorMySQLSearchHost' => 'infrastructure/cluster/search/PhabricatorMySQLSearchHost.php',
'PhabricatorMySQLSetupCheck' => 'applications/config/check/PhabricatorMySQLSetupCheck.php',
'PhabricatorNamedQuery' => 'applications/search/storage/PhabricatorNamedQuery.php',
'PhabricatorNamedQueryConfig' => 'applications/search/storage/PhabricatorNamedQueryConfig.php',
'PhabricatorNamedQueryConfigQuery' => 'applications/search/query/PhabricatorNamedQueryConfigQuery.php',
'PhabricatorNamedQueryQuery' => 'applications/search/query/PhabricatorNamedQueryQuery.php',
'PhabricatorNavigationRemarkupRule' => 'infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php',
'PhabricatorNeverTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorNeverTriggerClock.php',
'PhabricatorNgramsIndexEngineExtension' => 'applications/search/engineextension/PhabricatorNgramsIndexEngineExtension.php',
'PhabricatorNgramsInterface' => 'applications/search/interface/PhabricatorNgramsInterface.php',
'PhabricatorNotificationBuilder' => 'applications/notification/builder/PhabricatorNotificationBuilder.php',
'PhabricatorNotificationClearController' => 'applications/notification/controller/PhabricatorNotificationClearController.php',
'PhabricatorNotificationClient' => 'applications/notification/client/PhabricatorNotificationClient.php',
'PhabricatorNotificationConfigOptions' => 'applications/config/option/PhabricatorNotificationConfigOptions.php',
'PhabricatorNotificationController' => 'applications/notification/controller/PhabricatorNotificationController.php',
'PhabricatorNotificationDestructionEngineExtension' => 'applications/notification/engineextension/PhabricatorNotificationDestructionEngineExtension.php',
'PhabricatorNotificationIndividualController' => 'applications/notification/controller/PhabricatorNotificationIndividualController.php',
'PhabricatorNotificationListController' => 'applications/notification/controller/PhabricatorNotificationListController.php',
'PhabricatorNotificationPanelController' => 'applications/notification/controller/PhabricatorNotificationPanelController.php',
'PhabricatorNotificationQuery' => 'applications/notification/query/PhabricatorNotificationQuery.php',
'PhabricatorNotificationSearchEngine' => 'applications/notification/query/PhabricatorNotificationSearchEngine.php',
'PhabricatorNotificationServerRef' => 'applications/notification/client/PhabricatorNotificationServerRef.php',
'PhabricatorNotificationServersConfigType' => 'applications/notification/config/PhabricatorNotificationServersConfigType.php',
'PhabricatorNotificationStatusView' => 'applications/notification/view/PhabricatorNotificationStatusView.php',
'PhabricatorNotificationTestController' => 'applications/notification/controller/PhabricatorNotificationTestController.php',
'PhabricatorNotificationUIExample' => 'applications/uiexample/examples/PhabricatorNotificationUIExample.php',
'PhabricatorNotificationsApplication' => 'applications/notification/application/PhabricatorNotificationsApplication.php',
'PhabricatorNotificationsSetting' => 'applications/settings/setting/PhabricatorNotificationsSetting.php',
'PhabricatorNotificationsSettingsPanel' => 'applications/settings/panel/PhabricatorNotificationsSettingsPanel.php',
'PhabricatorNuanceApplication' => 'applications/nuance/application/PhabricatorNuanceApplication.php',
'PhabricatorOAuth1AuthProvider' => 'applications/auth/provider/PhabricatorOAuth1AuthProvider.php',
'PhabricatorOAuth1SecretTemporaryTokenType' => 'applications/auth/provider/PhabricatorOAuth1SecretTemporaryTokenType.php',
'PhabricatorOAuth2AuthProvider' => 'applications/auth/provider/PhabricatorOAuth2AuthProvider.php',
'PhabricatorOAuthAuthProvider' => 'applications/auth/provider/PhabricatorOAuthAuthProvider.php',
'PhabricatorOAuthClientAuthorization' => 'applications/oauthserver/storage/PhabricatorOAuthClientAuthorization.php',
'PhabricatorOAuthClientAuthorizationQuery' => 'applications/oauthserver/query/PhabricatorOAuthClientAuthorizationQuery.php',
'PhabricatorOAuthClientController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientController.php',
'PhabricatorOAuthClientDisableController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientDisableController.php',
'PhabricatorOAuthClientEditController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientEditController.php',
'PhabricatorOAuthClientListController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientListController.php',
'PhabricatorOAuthClientSecretController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientSecretController.php',
'PhabricatorOAuthClientTestController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientTestController.php',
'PhabricatorOAuthClientViewController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientViewController.php',
'PhabricatorOAuthResponse' => 'applications/oauthserver/PhabricatorOAuthResponse.php',
'PhabricatorOAuthServer' => 'applications/oauthserver/PhabricatorOAuthServer.php',
'PhabricatorOAuthServerAccessToken' => 'applications/oauthserver/storage/PhabricatorOAuthServerAccessToken.php',
'PhabricatorOAuthServerApplication' => 'applications/oauthserver/application/PhabricatorOAuthServerApplication.php',
'PhabricatorOAuthServerAuthController' => 'applications/oauthserver/controller/PhabricatorOAuthServerAuthController.php',
'PhabricatorOAuthServerAuthorizationCode' => 'applications/oauthserver/storage/PhabricatorOAuthServerAuthorizationCode.php',
'PhabricatorOAuthServerAuthorizationsSettingsPanel' => 'applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php',
'PhabricatorOAuthServerClient' => 'applications/oauthserver/storage/PhabricatorOAuthServerClient.php',
'PhabricatorOAuthServerClientAuthorizationPHIDType' => 'applications/oauthserver/phid/PhabricatorOAuthServerClientAuthorizationPHIDType.php',
'PhabricatorOAuthServerClientPHIDType' => 'applications/oauthserver/phid/PhabricatorOAuthServerClientPHIDType.php',
'PhabricatorOAuthServerClientQuery' => 'applications/oauthserver/query/PhabricatorOAuthServerClientQuery.php',
'PhabricatorOAuthServerClientSearchEngine' => 'applications/oauthserver/query/PhabricatorOAuthServerClientSearchEngine.php',
'PhabricatorOAuthServerController' => 'applications/oauthserver/controller/PhabricatorOAuthServerController.php',
'PhabricatorOAuthServerCreateClientsCapability' => 'applications/oauthserver/capability/PhabricatorOAuthServerCreateClientsCapability.php',
'PhabricatorOAuthServerDAO' => 'applications/oauthserver/storage/PhabricatorOAuthServerDAO.php',
'PhabricatorOAuthServerEditEngine' => 'applications/oauthserver/editor/PhabricatorOAuthServerEditEngine.php',
'PhabricatorOAuthServerEditor' => 'applications/oauthserver/editor/PhabricatorOAuthServerEditor.php',
'PhabricatorOAuthServerSchemaSpec' => 'applications/oauthserver/query/PhabricatorOAuthServerSchemaSpec.php',
'PhabricatorOAuthServerScope' => 'applications/oauthserver/PhabricatorOAuthServerScope.php',
'PhabricatorOAuthServerTestCase' => 'applications/oauthserver/__tests__/PhabricatorOAuthServerTestCase.php',
'PhabricatorOAuthServerTokenController' => 'applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php',
'PhabricatorOAuthServerTransaction' => 'applications/oauthserver/storage/PhabricatorOAuthServerTransaction.php',
'PhabricatorOAuthServerTransactionQuery' => 'applications/oauthserver/query/PhabricatorOAuthServerTransactionQuery.php',
'PhabricatorObjectGraph' => 'infrastructure/graph/PhabricatorObjectGraph.php',
'PhabricatorObjectHandle' => 'applications/phid/PhabricatorObjectHandle.php',
'PhabricatorObjectHasAsanaSubtaskEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasAsanaSubtaskEdgeType.php',
'PhabricatorObjectHasAsanaTaskEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasAsanaTaskEdgeType.php',
'PhabricatorObjectHasContributorEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasContributorEdgeType.php',
'PhabricatorObjectHasDraftEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasDraftEdgeType.php',
- 'PhabricatorObjectHasFileEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasFileEdgeType.php',
'PhabricatorObjectHasJiraIssueEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasJiraIssueEdgeType.php',
'PhabricatorObjectHasSubscriberEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasSubscriberEdgeType.php',
'PhabricatorObjectHasUnsubscriberEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasUnsubscriberEdgeType.php',
'PhabricatorObjectHasWatcherEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasWatcherEdgeType.php',
'PhabricatorObjectListQuery' => 'applications/phid/query/PhabricatorObjectListQuery.php',
'PhabricatorObjectListQueryTestCase' => 'applications/phid/query/__tests__/PhabricatorObjectListQueryTestCase.php',
'PhabricatorObjectMailReceiver' => 'applications/metamta/receiver/PhabricatorObjectMailReceiver.php',
'PhabricatorObjectMailReceiverTestCase' => 'applications/metamta/receiver/__tests__/PhabricatorObjectMailReceiverTestCase.php',
'PhabricatorObjectMentionedByObjectEdgeType' => 'applications/transactions/edges/PhabricatorObjectMentionedByObjectEdgeType.php',
'PhabricatorObjectMentionsObjectEdgeType' => 'applications/transactions/edges/PhabricatorObjectMentionsObjectEdgeType.php',
'PhabricatorObjectQuery' => 'applications/phid/query/PhabricatorObjectQuery.php',
'PhabricatorObjectRelationship' => 'applications/search/relationship/PhabricatorObjectRelationship.php',
'PhabricatorObjectRelationshipList' => 'applications/search/relationship/PhabricatorObjectRelationshipList.php',
'PhabricatorObjectRelationshipSource' => 'applications/search/relationship/PhabricatorObjectRelationshipSource.php',
'PhabricatorObjectRemarkupRule' => 'infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php',
'PhabricatorObjectSelectorDialog' => 'view/control/PhabricatorObjectSelectorDialog.php',
'PhabricatorObjectStatus' => 'infrastructure/status/PhabricatorObjectStatus.php',
'PhabricatorObjectUsesDashboardPanelEdgeType' => 'applications/search/edge/PhabricatorObjectUsesDashboardPanelEdgeType.php',
'PhabricatorOffsetPagedQuery' => 'infrastructure/query/PhabricatorOffsetPagedQuery.php',
'PhabricatorOldWorldContentSource' => 'infrastructure/contentsource/PhabricatorOldWorldContentSource.php',
'PhabricatorOlderInlinesSetting' => 'applications/settings/setting/PhabricatorOlderInlinesSetting.php',
'PhabricatorOneTimeTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorOneTimeTriggerClock.php',
'PhabricatorOpcodeCacheSpec' => 'applications/cache/spec/PhabricatorOpcodeCacheSpec.php',
'PhabricatorOptionExportField' => 'infrastructure/export/field/PhabricatorOptionExportField.php',
'PhabricatorOptionGroupSetting' => 'applications/settings/setting/PhabricatorOptionGroupSetting.php',
'PhabricatorOwnerPathQuery' => 'applications/owners/query/PhabricatorOwnerPathQuery.php',
'PhabricatorOwnersApplication' => 'applications/owners/application/PhabricatorOwnersApplication.php',
'PhabricatorOwnersArchiveController' => 'applications/owners/controller/PhabricatorOwnersArchiveController.php',
'PhabricatorOwnersAuditRule' => 'applications/owners/constants/PhabricatorOwnersAuditRule.php',
'PhabricatorOwnersConfigOptions' => 'applications/owners/config/PhabricatorOwnersConfigOptions.php',
'PhabricatorOwnersConfiguredCustomField' => 'applications/owners/customfield/PhabricatorOwnersConfiguredCustomField.php',
'PhabricatorOwnersController' => 'applications/owners/controller/PhabricatorOwnersController.php',
'PhabricatorOwnersCustomField' => 'applications/owners/customfield/PhabricatorOwnersCustomField.php',
'PhabricatorOwnersCustomFieldNumericIndex' => 'applications/owners/storage/PhabricatorOwnersCustomFieldNumericIndex.php',
'PhabricatorOwnersCustomFieldStorage' => 'applications/owners/storage/PhabricatorOwnersCustomFieldStorage.php',
'PhabricatorOwnersCustomFieldStringIndex' => 'applications/owners/storage/PhabricatorOwnersCustomFieldStringIndex.php',
'PhabricatorOwnersDAO' => 'applications/owners/storage/PhabricatorOwnersDAO.php',
'PhabricatorOwnersDefaultEditCapability' => 'applications/owners/capability/PhabricatorOwnersDefaultEditCapability.php',
'PhabricatorOwnersDefaultViewCapability' => 'applications/owners/capability/PhabricatorOwnersDefaultViewCapability.php',
'PhabricatorOwnersDetailController' => 'applications/owners/controller/PhabricatorOwnersDetailController.php',
'PhabricatorOwnersEditController' => 'applications/owners/controller/PhabricatorOwnersEditController.php',
'PhabricatorOwnersHovercardEngineExtension' => 'applications/owners/engineextension/PhabricatorOwnersHovercardEngineExtension.php',
'PhabricatorOwnersListController' => 'applications/owners/controller/PhabricatorOwnersListController.php',
'PhabricatorOwnersOwner' => 'applications/owners/storage/PhabricatorOwnersOwner.php',
'PhabricatorOwnersPackage' => 'applications/owners/storage/PhabricatorOwnersPackage.php',
'PhabricatorOwnersPackageAuditingTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageAuditingTransaction.php',
'PhabricatorOwnersPackageAuthorityTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageAuthorityTransaction.php',
'PhabricatorOwnersPackageAutoreviewTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageAutoreviewTransaction.php',
'PhabricatorOwnersPackageContextFreeGrammar' => 'applications/owners/lipsum/PhabricatorOwnersPackageContextFreeGrammar.php',
'PhabricatorOwnersPackageDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php',
'PhabricatorOwnersPackageDescriptionTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageDescriptionTransaction.php',
'PhabricatorOwnersPackageDominionTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageDominionTransaction.php',
'PhabricatorOwnersPackageEditEngine' => 'applications/owners/editor/PhabricatorOwnersPackageEditEngine.php',
'PhabricatorOwnersPackageFerretEngine' => 'applications/owners/search/PhabricatorOwnersPackageFerretEngine.php',
'PhabricatorOwnersPackageFulltextEngine' => 'applications/owners/search/PhabricatorOwnersPackageFulltextEngine.php',
'PhabricatorOwnersPackageFunctionDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageFunctionDatasource.php',
'PhabricatorOwnersPackageIgnoredTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageIgnoredTransaction.php',
'PhabricatorOwnersPackageNameNgrams' => 'applications/owners/storage/PhabricatorOwnersPackageNameNgrams.php',
'PhabricatorOwnersPackageNameTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageNameTransaction.php',
'PhabricatorOwnersPackageOwnerDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageOwnerDatasource.php',
'PhabricatorOwnersPackageOwnersTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageOwnersTransaction.php',
'PhabricatorOwnersPackagePHIDType' => 'applications/owners/phid/PhabricatorOwnersPackagePHIDType.php',
'PhabricatorOwnersPackagePathsTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackagePathsTransaction.php',
'PhabricatorOwnersPackagePrimaryTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackagePrimaryTransaction.php',
'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php',
'PhabricatorOwnersPackageRemarkupRule' => 'applications/owners/remarkup/PhabricatorOwnersPackageRemarkupRule.php',
'PhabricatorOwnersPackageSearchEngine' => 'applications/owners/query/PhabricatorOwnersPackageSearchEngine.php',
'PhabricatorOwnersPackageStatusTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageStatusTransaction.php',
'PhabricatorOwnersPackageTestCase' => 'applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php',
'PhabricatorOwnersPackageTestDataGenerator' => 'applications/owners/lipsum/PhabricatorOwnersPackageTestDataGenerator.php',
'PhabricatorOwnersPackageTransaction' => 'applications/owners/storage/PhabricatorOwnersPackageTransaction.php',
'PhabricatorOwnersPackageTransactionEditor' => 'applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php',
'PhabricatorOwnersPackageTransactionQuery' => 'applications/owners/query/PhabricatorOwnersPackageTransactionQuery.php',
'PhabricatorOwnersPackageTransactionType' => 'applications/owners/xaction/PhabricatorOwnersPackageTransactionType.php',
'PhabricatorOwnersPath' => 'applications/owners/storage/PhabricatorOwnersPath.php',
'PhabricatorOwnersPathContextFreeGrammar' => 'applications/owners/lipsum/PhabricatorOwnersPathContextFreeGrammar.php',
'PhabricatorOwnersPathsController' => 'applications/owners/controller/PhabricatorOwnersPathsController.php',
'PhabricatorOwnersPathsSearchEngineAttachment' => 'applications/owners/engineextension/PhabricatorOwnersPathsSearchEngineAttachment.php',
'PhabricatorOwnersSchemaSpec' => 'applications/owners/storage/PhabricatorOwnersSchemaSpec.php',
'PhabricatorOwnersSearchField' => 'applications/owners/searchfield/PhabricatorOwnersSearchField.php',
'PhabricatorPDFCatalogObject' => 'applications/phortune/pdf/PhabricatorPDFCatalogObject.php',
'PhabricatorPDFContentsObject' => 'applications/phortune/pdf/PhabricatorPDFContentsObject.php',
'PhabricatorPDFDocumentEngine' => 'applications/files/document/PhabricatorPDFDocumentEngine.php',
'PhabricatorPDFFontObject' => 'applications/phortune/pdf/PhabricatorPDFFontObject.php',
'PhabricatorPDFFragment' => 'applications/phortune/pdf/PhabricatorPDFFragment.php',
'PhabricatorPDFFragmentOffset' => 'applications/phortune/pdf/PhabricatorPDFFragmentOffset.php',
'PhabricatorPDFGenerator' => 'applications/phortune/pdf/PhabricatorPDFGenerator.php',
'PhabricatorPDFHeadFragment' => 'applications/phortune/pdf/PhabricatorPDFHeadFragment.php',
'PhabricatorPDFInfoObject' => 'applications/phortune/pdf/PhabricatorPDFInfoObject.php',
'PhabricatorPDFIterator' => 'applications/phortune/pdf/PhabricatorPDFIterator.php',
'PhabricatorPDFObject' => 'applications/phortune/pdf/PhabricatorPDFObject.php',
'PhabricatorPDFPageObject' => 'applications/phortune/pdf/PhabricatorPDFPageObject.php',
'PhabricatorPDFPagesObject' => 'applications/phortune/pdf/PhabricatorPDFPagesObject.php',
'PhabricatorPDFResourcesObject' => 'applications/phortune/pdf/PhabricatorPDFResourcesObject.php',
'PhabricatorPDFTailFragment' => 'applications/phortune/pdf/PhabricatorPDFTailFragment.php',
'PhabricatorPHDConfigOptions' => 'applications/config/option/PhabricatorPHDConfigOptions.php',
'PhabricatorPHID' => 'applications/phid/storage/PhabricatorPHID.php',
'PhabricatorPHIDConstants' => 'applications/phid/PhabricatorPHIDConstants.php',
'PhabricatorPHIDExportField' => 'infrastructure/export/field/PhabricatorPHIDExportField.php',
'PhabricatorPHIDInterface' => 'applications/phid/interface/PhabricatorPHIDInterface.php',
'PhabricatorPHIDListEditField' => 'applications/transactions/editfield/PhabricatorPHIDListEditField.php',
'PhabricatorPHIDListEditType' => 'applications/transactions/edittype/PhabricatorPHIDListEditType.php',
'PhabricatorPHIDListExportField' => 'infrastructure/export/field/PhabricatorPHIDListExportField.php',
'PhabricatorPHIDMailStamp' => 'applications/metamta/stamp/PhabricatorPHIDMailStamp.php',
'PhabricatorPHIDResolver' => 'applications/phid/resolver/PhabricatorPHIDResolver.php',
'PhabricatorPHIDType' => 'applications/phid/type/PhabricatorPHIDType.php',
'PhabricatorPHIDTypeTestCase' => 'applications/phid/type/__tests__/PhabricatorPHIDTypeTestCase.php',
'PhabricatorPHIDsSearchField' => 'applications/search/field/PhabricatorPHIDsSearchField.php',
'PhabricatorPHPASTApplication' => 'applications/phpast/application/PhabricatorPHPASTApplication.php',
'PhabricatorPHPConfigSetupCheck' => 'applications/config/check/PhabricatorPHPConfigSetupCheck.php',
'PhabricatorPHPPreflightSetupCheck' => 'applications/config/check/PhabricatorPHPPreflightSetupCheck.php',
'PhabricatorPackagesApplication' => 'applications/packages/application/PhabricatorPackagesApplication.php',
'PhabricatorPackagesController' => 'applications/packages/controller/PhabricatorPackagesController.php',
'PhabricatorPackagesCreatePublisherCapability' => 'applications/packages/capability/PhabricatorPackagesCreatePublisherCapability.php',
'PhabricatorPackagesDAO' => 'applications/packages/storage/PhabricatorPackagesDAO.php',
'PhabricatorPackagesEditEngine' => 'applications/packages/editor/PhabricatorPackagesEditEngine.php',
'PhabricatorPackagesEditor' => 'applications/packages/editor/PhabricatorPackagesEditor.php',
'PhabricatorPackagesNgrams' => 'applications/packages/storage/PhabricatorPackagesNgrams.php',
'PhabricatorPackagesPackage' => 'applications/packages/storage/PhabricatorPackagesPackage.php',
'PhabricatorPackagesPackageController' => 'applications/packages/controller/PhabricatorPackagesPackageController.php',
'PhabricatorPackagesPackageDatasource' => 'applications/packages/typeahead/PhabricatorPackagesPackageDatasource.php',
'PhabricatorPackagesPackageDefaultEditCapability' => 'applications/packages/capability/PhabricatorPackagesPackageDefaultEditCapability.php',
'PhabricatorPackagesPackageDefaultViewCapability' => 'applications/packages/capability/PhabricatorPackagesPackageDefaultViewCapability.php',
'PhabricatorPackagesPackageEditConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPackageEditConduitAPIMethod.php',
'PhabricatorPackagesPackageEditController' => 'applications/packages/controller/PhabricatorPackagesPackageEditController.php',
'PhabricatorPackagesPackageEditEngine' => 'applications/packages/editor/PhabricatorPackagesPackageEditEngine.php',
'PhabricatorPackagesPackageEditor' => 'applications/packages/editor/PhabricatorPackagesPackageEditor.php',
'PhabricatorPackagesPackageKeyTransaction' => 'applications/packages/xaction/package/PhabricatorPackagesPackageKeyTransaction.php',
'PhabricatorPackagesPackageListController' => 'applications/packages/controller/PhabricatorPackagesPackageListController.php',
'PhabricatorPackagesPackageListView' => 'applications/packages/view/PhabricatorPackagesPackageListView.php',
'PhabricatorPackagesPackageNameNgrams' => 'applications/packages/storage/PhabricatorPackagesPackageNameNgrams.php',
'PhabricatorPackagesPackageNameTransaction' => 'applications/packages/xaction/package/PhabricatorPackagesPackageNameTransaction.php',
'PhabricatorPackagesPackagePHIDType' => 'applications/packages/phid/PhabricatorPackagesPackagePHIDType.php',
'PhabricatorPackagesPackagePublisherTransaction' => 'applications/packages/xaction/package/PhabricatorPackagesPackagePublisherTransaction.php',
'PhabricatorPackagesPackageQuery' => 'applications/packages/query/PhabricatorPackagesPackageQuery.php',
'PhabricatorPackagesPackageSearchConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPackageSearchConduitAPIMethod.php',
'PhabricatorPackagesPackageSearchEngine' => 'applications/packages/query/PhabricatorPackagesPackageSearchEngine.php',
'PhabricatorPackagesPackageTransaction' => 'applications/packages/storage/PhabricatorPackagesPackageTransaction.php',
'PhabricatorPackagesPackageTransactionQuery' => 'applications/packages/query/PhabricatorPackagesPackageTransactionQuery.php',
'PhabricatorPackagesPackageTransactionType' => 'applications/packages/xaction/package/PhabricatorPackagesPackageTransactionType.php',
'PhabricatorPackagesPackageViewController' => 'applications/packages/controller/PhabricatorPackagesPackageViewController.php',
'PhabricatorPackagesPublisher' => 'applications/packages/storage/PhabricatorPackagesPublisher.php',
'PhabricatorPackagesPublisherController' => 'applications/packages/controller/PhabricatorPackagesPublisherController.php',
'PhabricatorPackagesPublisherDatasource' => 'applications/packages/typeahead/PhabricatorPackagesPublisherDatasource.php',
'PhabricatorPackagesPublisherDefaultEditCapability' => 'applications/packages/capability/PhabricatorPackagesPublisherDefaultEditCapability.php',
'PhabricatorPackagesPublisherEditConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPublisherEditConduitAPIMethod.php',
'PhabricatorPackagesPublisherEditController' => 'applications/packages/controller/PhabricatorPackagesPublisherEditController.php',
'PhabricatorPackagesPublisherEditEngine' => 'applications/packages/editor/PhabricatorPackagesPublisherEditEngine.php',
'PhabricatorPackagesPublisherEditor' => 'applications/packages/editor/PhabricatorPackagesPublisherEditor.php',
'PhabricatorPackagesPublisherKeyTransaction' => 'applications/packages/xaction/publisher/PhabricatorPackagesPublisherKeyTransaction.php',
'PhabricatorPackagesPublisherListController' => 'applications/packages/controller/PhabricatorPackagesPublisherListController.php',
'PhabricatorPackagesPublisherListView' => 'applications/packages/view/PhabricatorPackagesPublisherListView.php',
'PhabricatorPackagesPublisherNameNgrams' => 'applications/packages/storage/PhabricatorPackagesPublisherNameNgrams.php',
'PhabricatorPackagesPublisherNameTransaction' => 'applications/packages/xaction/publisher/PhabricatorPackagesPublisherNameTransaction.php',
'PhabricatorPackagesPublisherPHIDType' => 'applications/packages/phid/PhabricatorPackagesPublisherPHIDType.php',
'PhabricatorPackagesPublisherQuery' => 'applications/packages/query/PhabricatorPackagesPublisherQuery.php',
'PhabricatorPackagesPublisherSearchConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesPublisherSearchConduitAPIMethod.php',
'PhabricatorPackagesPublisherSearchEngine' => 'applications/packages/query/PhabricatorPackagesPublisherSearchEngine.php',
'PhabricatorPackagesPublisherTransaction' => 'applications/packages/storage/PhabricatorPackagesPublisherTransaction.php',
'PhabricatorPackagesPublisherTransactionQuery' => 'applications/packages/query/PhabricatorPackagesPublisherTransactionQuery.php',
'PhabricatorPackagesPublisherTransactionType' => 'applications/packages/xaction/publisher/PhabricatorPackagesPublisherTransactionType.php',
'PhabricatorPackagesPublisherViewController' => 'applications/packages/controller/PhabricatorPackagesPublisherViewController.php',
'PhabricatorPackagesQuery' => 'applications/packages/query/PhabricatorPackagesQuery.php',
'PhabricatorPackagesSchemaSpec' => 'applications/packages/storage/PhabricatorPackagesSchemaSpec.php',
'PhabricatorPackagesTransactionType' => 'applications/packages/xaction/PhabricatorPackagesTransactionType.php',
'PhabricatorPackagesVersion' => 'applications/packages/storage/PhabricatorPackagesVersion.php',
'PhabricatorPackagesVersionController' => 'applications/packages/controller/PhabricatorPackagesVersionController.php',
'PhabricatorPackagesVersionEditConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesVersionEditConduitAPIMethod.php',
'PhabricatorPackagesVersionEditController' => 'applications/packages/controller/PhabricatorPackagesVersionEditController.php',
'PhabricatorPackagesVersionEditEngine' => 'applications/packages/editor/PhabricatorPackagesVersionEditEngine.php',
'PhabricatorPackagesVersionEditor' => 'applications/packages/editor/PhabricatorPackagesVersionEditor.php',
'PhabricatorPackagesVersionListController' => 'applications/packages/controller/PhabricatorPackagesVersionListController.php',
'PhabricatorPackagesVersionListView' => 'applications/packages/view/PhabricatorPackagesVersionListView.php',
'PhabricatorPackagesVersionNameNgrams' => 'applications/packages/storage/PhabricatorPackagesVersionNameNgrams.php',
'PhabricatorPackagesVersionNameTransaction' => 'applications/packages/xaction/version/PhabricatorPackagesVersionNameTransaction.php',
'PhabricatorPackagesVersionPHIDType' => 'applications/packages/phid/PhabricatorPackagesVersionPHIDType.php',
'PhabricatorPackagesVersionPackageTransaction' => 'applications/packages/xaction/version/PhabricatorPackagesVersionPackageTransaction.php',
'PhabricatorPackagesVersionQuery' => 'applications/packages/query/PhabricatorPackagesVersionQuery.php',
'PhabricatorPackagesVersionSearchConduitAPIMethod' => 'applications/packages/conduit/PhabricatorPackagesVersionSearchConduitAPIMethod.php',
'PhabricatorPackagesVersionSearchEngine' => 'applications/packages/query/PhabricatorPackagesVersionSearchEngine.php',
'PhabricatorPackagesVersionTransaction' => 'applications/packages/storage/PhabricatorPackagesVersionTransaction.php',
'PhabricatorPackagesVersionTransactionQuery' => 'applications/packages/query/PhabricatorPackagesVersionTransactionQuery.php',
'PhabricatorPackagesVersionTransactionType' => 'applications/packages/xaction/version/PhabricatorPackagesVersionTransactionType.php',
'PhabricatorPackagesVersionViewController' => 'applications/packages/controller/PhabricatorPackagesVersionViewController.php',
'PhabricatorPackagesView' => 'applications/packages/view/PhabricatorPackagesView.php',
'PhabricatorPagerUIExample' => 'applications/uiexample/examples/PhabricatorPagerUIExample.php',
'PhabricatorPartialLoginUserLogType' => 'applications/people/userlog/PhabricatorPartialLoginUserLogType.php',
'PhabricatorPassphraseApplication' => 'applications/passphrase/application/PhabricatorPassphraseApplication.php',
'PhabricatorPasswordAuthProvider' => 'applications/auth/provider/PhabricatorPasswordAuthProvider.php',
'PhabricatorPasswordDestructionEngineExtension' => 'applications/auth/extension/PhabricatorPasswordDestructionEngineExtension.php',
'PhabricatorPasswordHasher' => 'infrastructure/util/password/PhabricatorPasswordHasher.php',
'PhabricatorPasswordHasherTestCase' => 'infrastructure/util/password/__tests__/PhabricatorPasswordHasherTestCase.php',
'PhabricatorPasswordHasherUnavailableException' => 'infrastructure/util/password/PhabricatorPasswordHasherUnavailableException.php',
'PhabricatorPasswordSettingsPanel' => 'applications/settings/panel/PhabricatorPasswordSettingsPanel.php',
'PhabricatorPaste' => 'applications/paste/storage/PhabricatorPaste.php',
'PhabricatorPasteApplication' => 'applications/paste/application/PhabricatorPasteApplication.php',
'PhabricatorPasteArchiveController' => 'applications/paste/controller/PhabricatorPasteArchiveController.php',
'PhabricatorPasteContentSearchEngineAttachment' => 'applications/paste/engineextension/PhabricatorPasteContentSearchEngineAttachment.php',
'PhabricatorPasteContentTransaction' => 'applications/paste/xaction/PhabricatorPasteContentTransaction.php',
'PhabricatorPasteController' => 'applications/paste/controller/PhabricatorPasteController.php',
'PhabricatorPasteDAO' => 'applications/paste/storage/PhabricatorPasteDAO.php',
'PhabricatorPasteEditController' => 'applications/paste/controller/PhabricatorPasteEditController.php',
'PhabricatorPasteEditEngine' => 'applications/paste/editor/PhabricatorPasteEditEngine.php',
'PhabricatorPasteEditor' => 'applications/paste/editor/PhabricatorPasteEditor.php',
'PhabricatorPasteFerretEngine' => 'applications/paste/engine/PhabricatorPasteFerretEngine.php',
'PhabricatorPasteFilenameContextFreeGrammar' => 'applications/paste/lipsum/PhabricatorPasteFilenameContextFreeGrammar.php',
'PhabricatorPasteFulltextEngine' => 'applications/paste/engine/PhabricatorPasteFulltextEngine.php',
'PhabricatorPasteLanguageTransaction' => 'applications/paste/xaction/PhabricatorPasteLanguageTransaction.php',
'PhabricatorPasteListController' => 'applications/paste/controller/PhabricatorPasteListController.php',
'PhabricatorPastePastePHIDType' => 'applications/paste/phid/PhabricatorPastePastePHIDType.php',
'PhabricatorPasteQuery' => 'applications/paste/query/PhabricatorPasteQuery.php',
'PhabricatorPasteRawController' => 'applications/paste/controller/PhabricatorPasteRawController.php',
'PhabricatorPasteRemarkupRule' => 'applications/paste/remarkup/PhabricatorPasteRemarkupRule.php',
'PhabricatorPasteSchemaSpec' => 'applications/paste/storage/PhabricatorPasteSchemaSpec.php',
'PhabricatorPasteSearchEngine' => 'applications/paste/query/PhabricatorPasteSearchEngine.php',
'PhabricatorPasteSnippet' => 'applications/paste/snippet/PhabricatorPasteSnippet.php',
'PhabricatorPasteStatusTransaction' => 'applications/paste/xaction/PhabricatorPasteStatusTransaction.php',
'PhabricatorPasteTestDataGenerator' => 'applications/paste/lipsum/PhabricatorPasteTestDataGenerator.php',
'PhabricatorPasteTitleTransaction' => 'applications/paste/xaction/PhabricatorPasteTitleTransaction.php',
'PhabricatorPasteTransaction' => 'applications/paste/storage/PhabricatorPasteTransaction.php',
'PhabricatorPasteTransactionComment' => 'applications/paste/storage/PhabricatorPasteTransactionComment.php',
'PhabricatorPasteTransactionQuery' => 'applications/paste/query/PhabricatorPasteTransactionQuery.php',
'PhabricatorPasteTransactionType' => 'applications/paste/xaction/PhabricatorPasteTransactionType.php',
'PhabricatorPasteViewController' => 'applications/paste/controller/PhabricatorPasteViewController.php',
'PhabricatorPathSetupCheck' => 'applications/config/check/PhabricatorPathSetupCheck.php',
'PhabricatorPeopleAnyOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleAnyOwnerDatasource.php',
'PhabricatorPeopleApplication' => 'applications/people/application/PhabricatorPeopleApplication.php',
'PhabricatorPeopleApproveController' => 'applications/people/controller/PhabricatorPeopleApproveController.php',
'PhabricatorPeopleAvailabilitySearchEngineAttachment' => 'applications/people/engineextension/PhabricatorPeopleAvailabilitySearchEngineAttachment.php',
'PhabricatorPeopleBadgesProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleBadgesProfileMenuItem.php',
'PhabricatorPeopleCommitsProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleCommitsProfileMenuItem.php',
'PhabricatorPeopleController' => 'applications/people/controller/PhabricatorPeopleController.php',
'PhabricatorPeopleCreateController' => 'applications/people/controller/PhabricatorPeopleCreateController.php',
'PhabricatorPeopleCreateGuidanceContext' => 'applications/people/guidance/PhabricatorPeopleCreateGuidanceContext.php',
'PhabricatorPeopleDatasource' => 'applications/people/typeahead/PhabricatorPeopleDatasource.php',
'PhabricatorPeopleDatasourceEngineExtension' => 'applications/people/engineextension/PhabricatorPeopleDatasourceEngineExtension.php',
'PhabricatorPeopleDeleteController' => 'applications/people/controller/PhabricatorPeopleDeleteController.php',
'PhabricatorPeopleDetailsProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleDetailsProfileMenuItem.php',
'PhabricatorPeopleDisableController' => 'applications/people/controller/PhabricatorPeopleDisableController.php',
'PhabricatorPeopleEmailLoginMailEngine' => 'applications/people/mail/PhabricatorPeopleEmailLoginMailEngine.php',
'PhabricatorPeopleEmpowerController' => 'applications/people/controller/PhabricatorPeopleEmpowerController.php',
'PhabricatorPeopleExternalIdentifierPHIDType' => 'applications/people/phid/PhabricatorPeopleExternalIdentifierPHIDType.php',
'PhabricatorPeopleExternalPHIDType' => 'applications/people/phid/PhabricatorPeopleExternalPHIDType.php',
'PhabricatorPeopleIconSet' => 'applications/people/icon/PhabricatorPeopleIconSet.php',
'PhabricatorPeopleInviteController' => 'applications/people/controller/PhabricatorPeopleInviteController.php',
'PhabricatorPeopleInviteListController' => 'applications/people/controller/PhabricatorPeopleInviteListController.php',
'PhabricatorPeopleInviteSendController' => 'applications/people/controller/PhabricatorPeopleInviteSendController.php',
'PhabricatorPeopleListController' => 'applications/people/controller/PhabricatorPeopleListController.php',
'PhabricatorPeopleLogQuery' => 'applications/people/query/PhabricatorPeopleLogQuery.php',
'PhabricatorPeopleLogSearchEngine' => 'applications/people/query/PhabricatorPeopleLogSearchEngine.php',
'PhabricatorPeopleLogViewController' => 'applications/people/controller/PhabricatorPeopleLogViewController.php',
'PhabricatorPeopleLogsController' => 'applications/people/controller/PhabricatorPeopleLogsController.php',
'PhabricatorPeopleMailEngine' => 'applications/people/mail/PhabricatorPeopleMailEngine.php',
'PhabricatorPeopleMailEngineException' => 'applications/people/mail/PhabricatorPeopleMailEngineException.php',
'PhabricatorPeopleManageProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleManageProfileMenuItem.php',
'PhabricatorPeopleManagementApproveWorkflow' => 'applications/people/management/PhabricatorPeopleManagementApproveWorkflow.php',
'PhabricatorPeopleManagementEmpowerWorkflow' => 'applications/people/management/PhabricatorPeopleManagementEmpowerWorkflow.php',
'PhabricatorPeopleManagementEnableWorkflow' => 'applications/people/management/PhabricatorPeopleManagementEnableWorkflow.php',
'PhabricatorPeopleManagementWorkflow' => 'applications/people/management/PhabricatorPeopleManagementWorkflow.php',
'PhabricatorPeopleNewController' => 'applications/people/controller/PhabricatorPeopleNewController.php',
'PhabricatorPeopleNoOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleNoOwnerDatasource.php',
'PhabricatorPeopleOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleOwnerDatasource.php',
'PhabricatorPeoplePictureProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeoplePictureProfileMenuItem.php',
'PhabricatorPeopleProfileBadgesController' => 'applications/people/controller/PhabricatorPeopleProfileBadgesController.php',
'PhabricatorPeopleProfileCommitsController' => 'applications/people/controller/PhabricatorPeopleProfileCommitsController.php',
'PhabricatorPeopleProfileController' => 'applications/people/controller/PhabricatorPeopleProfileController.php',
'PhabricatorPeopleProfileEditController' => 'applications/people/controller/PhabricatorPeopleProfileEditController.php',
'PhabricatorPeopleProfileManageController' => 'applications/people/controller/PhabricatorPeopleProfileManageController.php',
'PhabricatorPeopleProfileMenuEngine' => 'applications/people/engine/PhabricatorPeopleProfileMenuEngine.php',
'PhabricatorPeopleProfilePictureController' => 'applications/people/controller/PhabricatorPeopleProfilePictureController.php',
'PhabricatorPeopleProfileRevisionsController' => 'applications/people/controller/PhabricatorPeopleProfileRevisionsController.php',
'PhabricatorPeopleProfileTasksController' => 'applications/people/controller/PhabricatorPeopleProfileTasksController.php',
'PhabricatorPeopleProfileViewController' => 'applications/people/controller/PhabricatorPeopleProfileViewController.php',
'PhabricatorPeopleQuery' => 'applications/people/query/PhabricatorPeopleQuery.php',
'PhabricatorPeopleRenameController' => 'applications/people/controller/PhabricatorPeopleRenameController.php',
'PhabricatorPeopleRevisionsProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php',
'PhabricatorPeopleSearchEngine' => 'applications/people/query/PhabricatorPeopleSearchEngine.php',
'PhabricatorPeopleTasksProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleTasksProfileMenuItem.php',
'PhabricatorPeopleTestDataGenerator' => 'applications/people/lipsum/PhabricatorPeopleTestDataGenerator.php',
'PhabricatorPeopleTransactionQuery' => 'applications/people/query/PhabricatorPeopleTransactionQuery.php',
'PhabricatorPeopleUserEmailPHIDType' => 'applications/people/phid/PhabricatorPeopleUserEmailPHIDType.php',
'PhabricatorPeopleUserEmailQuery' => 'applications/people/query/PhabricatorPeopleUserEmailQuery.php',
'PhabricatorPeopleUserFunctionDatasource' => 'applications/people/typeahead/PhabricatorPeopleUserFunctionDatasource.php',
'PhabricatorPeopleUserPHIDType' => 'applications/people/phid/PhabricatorPeopleUserPHIDType.php',
'PhabricatorPeopleUsernameMailEngine' => 'applications/people/mail/PhabricatorPeopleUsernameMailEngine.php',
'PhabricatorPeopleWelcomeController' => 'applications/people/controller/PhabricatorPeopleWelcomeController.php',
'PhabricatorPeopleWelcomeMailEngine' => 'applications/people/mail/PhabricatorPeopleWelcomeMailEngine.php',
'PhabricatorPhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorPhabricatorAuthProvider.php',
'PhabricatorPhameApplication' => 'applications/phame/application/PhabricatorPhameApplication.php',
'PhabricatorPhameBlogPHIDType' => 'applications/phame/phid/PhabricatorPhameBlogPHIDType.php',
'PhabricatorPhamePostPHIDType' => 'applications/phame/phid/PhabricatorPhamePostPHIDType.php',
'PhabricatorPhluxApplication' => 'applications/phlux/application/PhabricatorPhluxApplication.php',
'PhabricatorPholioApplication' => 'applications/pholio/application/PhabricatorPholioApplication.php',
'PhabricatorPholioMockTestDataGenerator' => 'applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php',
'PhabricatorPhoneNumber' => 'applications/metamta/message/PhabricatorPhoneNumber.php',
'PhabricatorPhoneNumberTestCase' => 'applications/metamta/message/__tests__/PhabricatorPhoneNumberTestCase.php',
'PhabricatorPhortuneApplication' => 'applications/phortune/application/PhabricatorPhortuneApplication.php',
'PhabricatorPhortuneContentSource' => 'applications/phortune/contentsource/PhabricatorPhortuneContentSource.php',
'PhabricatorPhortuneManagementInvoiceWorkflow' => 'applications/phortune/management/PhabricatorPhortuneManagementInvoiceWorkflow.php',
'PhabricatorPhortuneManagementWorkflow' => 'applications/phortune/management/PhabricatorPhortuneManagementWorkflow.php',
'PhabricatorPhortuneTestCase' => 'applications/phortune/__tests__/PhabricatorPhortuneTestCase.php',
- 'PhabricatorPhragmentApplication' => 'applications/phragment/application/PhabricatorPhragmentApplication.php',
'PhabricatorPhrequentApplication' => 'applications/phrequent/application/PhabricatorPhrequentApplication.php',
'PhabricatorPhrictionApplication' => 'applications/phriction/application/PhabricatorPhrictionApplication.php',
'PhabricatorPhurlApplication' => 'applications/phurl/application/PhabricatorPhurlApplication.php',
'PhabricatorPhurlConfigOptions' => 'applications/config/option/PhabricatorPhurlConfigOptions.php',
'PhabricatorPhurlController' => 'applications/phurl/controller/PhabricatorPhurlController.php',
'PhabricatorPhurlDAO' => 'applications/phurl/storage/PhabricatorPhurlDAO.php',
'PhabricatorPhurlLinkRemarkupRule' => 'applications/phurl/remarkup/PhabricatorPhurlLinkRemarkupRule.php',
'PhabricatorPhurlRemarkupRule' => 'applications/phurl/remarkup/PhabricatorPhurlRemarkupRule.php',
'PhabricatorPhurlSchemaSpec' => 'applications/phurl/storage/PhabricatorPhurlSchemaSpec.php',
'PhabricatorPhurlShortURLController' => 'applications/phurl/controller/PhabricatorPhurlShortURLController.php',
'PhabricatorPhurlShortURLDefaultController' => 'applications/phurl/controller/PhabricatorPhurlShortURLDefaultController.php',
'PhabricatorPhurlURL' => 'applications/phurl/storage/PhabricatorPhurlURL.php',
'PhabricatorPhurlURLAccessController' => 'applications/phurl/controller/PhabricatorPhurlURLAccessController.php',
'PhabricatorPhurlURLAliasTransaction' => 'applications/phurl/xaction/PhabricatorPhurlURLAliasTransaction.php',
'PhabricatorPhurlURLCreateCapability' => 'applications/phurl/capability/PhabricatorPhurlURLCreateCapability.php',
'PhabricatorPhurlURLDatasource' => 'applications/phurl/typeahead/PhabricatorPhurlURLDatasource.php',
'PhabricatorPhurlURLDescriptionTransaction' => 'applications/phurl/xaction/PhabricatorPhurlURLDescriptionTransaction.php',
'PhabricatorPhurlURLEditConduitAPIMethod' => 'applications/phurl/conduit/PhabricatorPhurlURLEditConduitAPIMethod.php',
'PhabricatorPhurlURLEditController' => 'applications/phurl/controller/PhabricatorPhurlURLEditController.php',
'PhabricatorPhurlURLEditEngine' => 'applications/phurl/editor/PhabricatorPhurlURLEditEngine.php',
'PhabricatorPhurlURLEditor' => 'applications/phurl/editor/PhabricatorPhurlURLEditor.php',
'PhabricatorPhurlURLListController' => 'applications/phurl/controller/PhabricatorPhurlURLListController.php',
'PhabricatorPhurlURLLongURLTransaction' => 'applications/phurl/xaction/PhabricatorPhurlURLLongURLTransaction.php',
'PhabricatorPhurlURLMailReceiver' => 'applications/phurl/mail/PhabricatorPhurlURLMailReceiver.php',
'PhabricatorPhurlURLNameNgrams' => 'applications/phurl/storage/PhabricatorPhurlURLNameNgrams.php',
'PhabricatorPhurlURLNameTransaction' => 'applications/phurl/xaction/PhabricatorPhurlURLNameTransaction.php',
'PhabricatorPhurlURLPHIDType' => 'applications/phurl/phid/PhabricatorPhurlURLPHIDType.php',
'PhabricatorPhurlURLQuery' => 'applications/phurl/query/PhabricatorPhurlURLQuery.php',
'PhabricatorPhurlURLReplyHandler' => 'applications/phurl/mail/PhabricatorPhurlURLReplyHandler.php',
'PhabricatorPhurlURLSearchConduitAPIMethod' => 'applications/phurl/conduit/PhabricatorPhurlURLSearchConduitAPIMethod.php',
'PhabricatorPhurlURLSearchEngine' => 'applications/phurl/query/PhabricatorPhurlURLSearchEngine.php',
'PhabricatorPhurlURLTransaction' => 'applications/phurl/storage/PhabricatorPhurlURLTransaction.php',
'PhabricatorPhurlURLTransactionComment' => 'applications/phurl/storage/PhabricatorPhurlURLTransactionComment.php',
'PhabricatorPhurlURLTransactionQuery' => 'applications/phurl/query/PhabricatorPhurlURLTransactionQuery.php',
'PhabricatorPhurlURLTransactionType' => 'applications/phurl/xaction/PhabricatorPhurlURLTransactionType.php',
'PhabricatorPhurlURLViewController' => 'applications/phurl/controller/PhabricatorPhurlURLViewController.php',
'PhabricatorPinnedApplicationsSetting' => 'applications/settings/setting/PhabricatorPinnedApplicationsSetting.php',
'PhabricatorPirateEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorPirateEnglishTranslation.php',
'PhabricatorPlatform404Controller' => 'applications/base/controller/PhabricatorPlatform404Controller.php',
'PhabricatorPlatformSite' => 'aphront/site/PhabricatorPlatformSite.php',
'PhabricatorPointsEditField' => 'applications/transactions/editfield/PhabricatorPointsEditField.php',
'PhabricatorPointsFact' => 'applications/fact/fact/PhabricatorPointsFact.php',
'PhabricatorPolicies' => 'applications/policy/constants/PhabricatorPolicies.php',
'PhabricatorPolicy' => 'applications/policy/storage/PhabricatorPolicy.php',
'PhabricatorPolicyApplication' => 'applications/policy/application/PhabricatorPolicyApplication.php',
'PhabricatorPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorPolicyAwareQuery.php',
'PhabricatorPolicyAwareTestQuery' => 'applications/policy/__tests__/PhabricatorPolicyAwareTestQuery.php',
'PhabricatorPolicyCanEditCapability' => 'applications/policy/capability/PhabricatorPolicyCanEditCapability.php',
'PhabricatorPolicyCanInteractCapability' => 'applications/policy/capability/PhabricatorPolicyCanInteractCapability.php',
'PhabricatorPolicyCanJoinCapability' => 'applications/policy/capability/PhabricatorPolicyCanJoinCapability.php',
'PhabricatorPolicyCanViewCapability' => 'applications/policy/capability/PhabricatorPolicyCanViewCapability.php',
'PhabricatorPolicyCapability' => 'applications/policy/capability/PhabricatorPolicyCapability.php',
'PhabricatorPolicyCapabilityTestCase' => 'applications/policy/capability/__tests__/PhabricatorPolicyCapabilityTestCase.php',
'PhabricatorPolicyCodex' => 'applications/policy/codex/PhabricatorPolicyCodex.php',
'PhabricatorPolicyCodexInterface' => 'applications/policy/codex/PhabricatorPolicyCodexInterface.php',
'PhabricatorPolicyCodexRuleDescription' => 'applications/policy/codex/PhabricatorPolicyCodexRuleDescription.php',
'PhabricatorPolicyConfigOptions' => 'applications/policy/config/PhabricatorPolicyConfigOptions.php',
'PhabricatorPolicyConstants' => 'applications/policy/constants/PhabricatorPolicyConstants.php',
'PhabricatorPolicyController' => 'applications/policy/controller/PhabricatorPolicyController.php',
'PhabricatorPolicyDAO' => 'applications/policy/storage/PhabricatorPolicyDAO.php',
'PhabricatorPolicyDataTestCase' => 'applications/policy/__tests__/PhabricatorPolicyDataTestCase.php',
'PhabricatorPolicyEditController' => 'applications/policy/controller/PhabricatorPolicyEditController.php',
'PhabricatorPolicyEditEngineExtension' => 'applications/policy/editor/PhabricatorPolicyEditEngineExtension.php',
'PhabricatorPolicyEditField' => 'applications/transactions/editfield/PhabricatorPolicyEditField.php',
'PhabricatorPolicyException' => 'applications/policy/exception/PhabricatorPolicyException.php',
'PhabricatorPolicyExplainController' => 'applications/policy/controller/PhabricatorPolicyExplainController.php',
'PhabricatorPolicyFavoritesSetting' => 'applications/settings/setting/PhabricatorPolicyFavoritesSetting.php',
'PhabricatorPolicyFilter' => 'applications/policy/filter/PhabricatorPolicyFilter.php',
'PhabricatorPolicyFilterSet' => 'applications/policy/filter/PhabricatorPolicyFilterSet.php',
'PhabricatorPolicyInterface' => 'applications/policy/interface/PhabricatorPolicyInterface.php',
'PhabricatorPolicyManagementShowWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementShowWorkflow.php',
'PhabricatorPolicyManagementUnlockWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementUnlockWorkflow.php',
'PhabricatorPolicyManagementWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementWorkflow.php',
'PhabricatorPolicyPHIDTypePolicy' => 'applications/policy/phid/PhabricatorPolicyPHIDTypePolicy.php',
'PhabricatorPolicyQuery' => 'applications/policy/query/PhabricatorPolicyQuery.php',
'PhabricatorPolicyRef' => 'applications/policy/view/PhabricatorPolicyRef.php',
'PhabricatorPolicyRequestExceptionHandler' => 'aphront/handler/PhabricatorPolicyRequestExceptionHandler.php',
'PhabricatorPolicyRule' => 'applications/policy/rule/PhabricatorPolicyRule.php',
'PhabricatorPolicyRulesView' => 'applications/policy/view/PhabricatorPolicyRulesView.php',
'PhabricatorPolicySearchEngineExtension' => 'applications/policy/engineextension/PhabricatorPolicySearchEngineExtension.php',
'PhabricatorPolicyTestCase' => 'applications/policy/__tests__/PhabricatorPolicyTestCase.php',
'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php',
'PhabricatorPolicyType' => 'applications/policy/constants/PhabricatorPolicyType.php',
'PhabricatorPonderApplication' => 'applications/ponder/application/PhabricatorPonderApplication.php',
'PhabricatorPreambleTestCase' => 'infrastructure/util/__tests__/PhabricatorPreambleTestCase.php',
'PhabricatorPrimaryEmailUserLogType' => 'applications/people/userlog/PhabricatorPrimaryEmailUserLogType.php',
'PhabricatorProfileMenuEditEngine' => 'applications/search/editor/PhabricatorProfileMenuEditEngine.php',
'PhabricatorProfileMenuEditor' => 'applications/search/editor/PhabricatorProfileMenuEditor.php',
'PhabricatorProfileMenuEngine' => 'applications/search/engine/PhabricatorProfileMenuEngine.php',
'PhabricatorProfileMenuItem' => 'applications/search/menuitem/PhabricatorProfileMenuItem.php',
'PhabricatorProfileMenuItemAffectsObjectEdgeType' => 'applications/search/edge/PhabricatorProfileMenuItemAffectsObjectEdgeType.php',
'PhabricatorProfileMenuItemConfiguration' => 'applications/search/storage/PhabricatorProfileMenuItemConfiguration.php',
'PhabricatorProfileMenuItemConfigurationQuery' => 'applications/search/query/PhabricatorProfileMenuItemConfigurationQuery.php',
'PhabricatorProfileMenuItemConfigurationTransaction' => 'applications/search/storage/PhabricatorProfileMenuItemConfigurationTransaction.php',
'PhabricatorProfileMenuItemConfigurationTransactionQuery' => 'applications/search/query/PhabricatorProfileMenuItemConfigurationTransactionQuery.php',
'PhabricatorProfileMenuItemIconSet' => 'applications/search/menuitem/PhabricatorProfileMenuItemIconSet.php',
'PhabricatorProfileMenuItemIndexEngineExtension' => 'applications/search/engineextension/PhabricatorProfileMenuItemIndexEngineExtension.php',
'PhabricatorProfileMenuItemPHIDType' => 'applications/search/phidtype/PhabricatorProfileMenuItemPHIDType.php',
'PhabricatorProfileMenuItemView' => 'applications/search/engine/PhabricatorProfileMenuItemView.php',
'PhabricatorProfileMenuItemViewList' => 'applications/search/engine/PhabricatorProfileMenuItemViewList.php',
'PhabricatorProject' => 'applications/project/storage/PhabricatorProject.php',
'PhabricatorProjectActivityChartEngine' => 'applications/project/chart/PhabricatorProjectActivityChartEngine.php',
'PhabricatorProjectAddHeraldAction' => 'applications/project/herald/PhabricatorProjectAddHeraldAction.php',
'PhabricatorProjectApplication' => 'applications/project/application/PhabricatorProjectApplication.php',
'PhabricatorProjectArchiveController' => 'applications/project/controller/PhabricatorProjectArchiveController.php',
'PhabricatorProjectBoardBackgroundController' => 'applications/project/controller/PhabricatorProjectBoardBackgroundController.php',
'PhabricatorProjectBoardController' => 'applications/project/controller/PhabricatorProjectBoardController.php',
'PhabricatorProjectBoardDefaultController' => 'applications/project/controller/PhabricatorProjectBoardDefaultController.php',
'PhabricatorProjectBoardDisableController' => 'applications/project/controller/PhabricatorProjectBoardDisableController.php',
'PhabricatorProjectBoardFilterController' => 'applications/project/controller/PhabricatorProjectBoardFilterController.php',
'PhabricatorProjectBoardImportController' => 'applications/project/controller/PhabricatorProjectBoardImportController.php',
'PhabricatorProjectBoardManageController' => 'applications/project/controller/PhabricatorProjectBoardManageController.php',
'PhabricatorProjectBoardReloadController' => 'applications/project/controller/PhabricatorProjectBoardReloadController.php',
'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php',
'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php',
'PhabricatorProjectBuiltinsExample' => 'applications/uiexample/examples/PhabricatorProjectBuiltinsExample.php',
'PhabricatorProjectBurndownChartEngine' => 'applications/project/chart/PhabricatorProjectBurndownChartEngine.php',
'PhabricatorProjectCardView' => 'applications/project/view/PhabricatorProjectCardView.php',
'PhabricatorProjectColorTransaction' => 'applications/project/xaction/PhabricatorProjectColorTransaction.php',
'PhabricatorProjectColorsConfigType' => 'applications/project/config/PhabricatorProjectColorsConfigType.php',
'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php',
'PhabricatorProjectColumnAuthorOrder' => 'applications/project/order/PhabricatorProjectColumnAuthorOrder.php',
'PhabricatorProjectColumnBulkEditController' => 'applications/project/controller/PhabricatorProjectColumnBulkEditController.php',
'PhabricatorProjectColumnBulkMoveController' => 'applications/project/controller/PhabricatorProjectColumnBulkMoveController.php',
'PhabricatorProjectColumnCreatedOrder' => 'applications/project/order/PhabricatorProjectColumnCreatedOrder.php',
'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php',
'PhabricatorProjectColumnEditController' => 'applications/project/controller/PhabricatorProjectColumnEditController.php',
'PhabricatorProjectColumnHeader' => 'applications/project/order/PhabricatorProjectColumnHeader.php',
'PhabricatorProjectColumnHideController' => 'applications/project/controller/PhabricatorProjectColumnHideController.php',
'PhabricatorProjectColumnLimitTransaction' => 'applications/project/xaction/column/PhabricatorProjectColumnLimitTransaction.php',
'PhabricatorProjectColumnNameTransaction' => 'applications/project/xaction/column/PhabricatorProjectColumnNameTransaction.php',
'PhabricatorProjectColumnNaturalOrder' => 'applications/project/order/PhabricatorProjectColumnNaturalOrder.php',
'PhabricatorProjectColumnOrder' => 'applications/project/order/PhabricatorProjectColumnOrder.php',
'PhabricatorProjectColumnOwnerOrder' => 'applications/project/order/PhabricatorProjectColumnOwnerOrder.php',
'PhabricatorProjectColumnPHIDType' => 'applications/project/phid/PhabricatorProjectColumnPHIDType.php',
'PhabricatorProjectColumnPointsOrder' => 'applications/project/order/PhabricatorProjectColumnPointsOrder.php',
'PhabricatorProjectColumnPosition' => 'applications/project/storage/PhabricatorProjectColumnPosition.php',
'PhabricatorProjectColumnPositionQuery' => 'applications/project/query/PhabricatorProjectColumnPositionQuery.php',
'PhabricatorProjectColumnPriorityOrder' => 'applications/project/order/PhabricatorProjectColumnPriorityOrder.php',
'PhabricatorProjectColumnQuery' => 'applications/project/query/PhabricatorProjectColumnQuery.php',
'PhabricatorProjectColumnRemoveTriggerController' => 'applications/project/controller/PhabricatorProjectColumnRemoveTriggerController.php',
'PhabricatorProjectColumnSearchEngine' => 'applications/project/query/PhabricatorProjectColumnSearchEngine.php',
'PhabricatorProjectColumnStatusOrder' => 'applications/project/order/PhabricatorProjectColumnStatusOrder.php',
'PhabricatorProjectColumnStatusTransaction' => 'applications/project/xaction/column/PhabricatorProjectColumnStatusTransaction.php',
'PhabricatorProjectColumnTitleOrder' => 'applications/project/order/PhabricatorProjectColumnTitleOrder.php',
'PhabricatorProjectColumnTransaction' => 'applications/project/storage/PhabricatorProjectColumnTransaction.php',
'PhabricatorProjectColumnTransactionEditor' => 'applications/project/editor/PhabricatorProjectColumnTransactionEditor.php',
'PhabricatorProjectColumnTransactionQuery' => 'applications/project/query/PhabricatorProjectColumnTransactionQuery.php',
'PhabricatorProjectColumnTransactionType' => 'applications/project/xaction/column/PhabricatorProjectColumnTransactionType.php',
'PhabricatorProjectColumnTriggerTransaction' => 'applications/project/xaction/column/PhabricatorProjectColumnTriggerTransaction.php',
'PhabricatorProjectColumnViewQueryController' => 'applications/project/controller/PhabricatorProjectColumnViewQueryController.php',
'PhabricatorProjectConfigOptions' => 'applications/project/config/PhabricatorProjectConfigOptions.php',
'PhabricatorProjectConfiguredCustomField' => 'applications/project/customfield/PhabricatorProjectConfiguredCustomField.php',
'PhabricatorProjectController' => 'applications/project/controller/PhabricatorProjectController.php',
'PhabricatorProjectCoreTestCase' => 'applications/project/__tests__/PhabricatorProjectCoreTestCase.php',
'PhabricatorProjectCoverController' => 'applications/project/controller/PhabricatorProjectCoverController.php',
'PhabricatorProjectCustomField' => 'applications/project/customfield/PhabricatorProjectCustomField.php',
'PhabricatorProjectCustomFieldNumericIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldNumericIndex.php',
'PhabricatorProjectCustomFieldStorage' => 'applications/project/storage/PhabricatorProjectCustomFieldStorage.php',
'PhabricatorProjectCustomFieldStringIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldStringIndex.php',
'PhabricatorProjectDAO' => 'applications/project/storage/PhabricatorProjectDAO.php',
'PhabricatorProjectDatasource' => 'applications/project/typeahead/PhabricatorProjectDatasource.php',
'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php',
'PhabricatorProjectDetailsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectDetailsProfileMenuItem.php',
'PhabricatorProjectDropEffect' => 'applications/project/icon/PhabricatorProjectDropEffect.php',
'PhabricatorProjectEditController' => 'applications/project/controller/PhabricatorProjectEditController.php',
'PhabricatorProjectEditEngine' => 'applications/project/engine/PhabricatorProjectEditEngine.php',
'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php',
'PhabricatorProjectFerretEngine' => 'applications/project/search/PhabricatorProjectFerretEngine.php',
'PhabricatorProjectFilterTransaction' => 'applications/project/xaction/PhabricatorProjectFilterTransaction.php',
'PhabricatorProjectFulltextEngine' => 'applications/project/search/PhabricatorProjectFulltextEngine.php',
'PhabricatorProjectHeraldAction' => 'applications/project/herald/PhabricatorProjectHeraldAction.php',
'PhabricatorProjectHeraldAdapter' => 'applications/project/herald/PhabricatorProjectHeraldAdapter.php',
'PhabricatorProjectHeraldFieldGroup' => 'applications/project/herald/PhabricatorProjectHeraldFieldGroup.php',
'PhabricatorProjectHovercardEngineExtension' => 'applications/project/engineextension/PhabricatorProjectHovercardEngineExtension.php',
'PhabricatorProjectIconSet' => 'applications/project/icon/PhabricatorProjectIconSet.php',
'PhabricatorProjectIconTransaction' => 'applications/project/xaction/PhabricatorProjectIconTransaction.php',
'PhabricatorProjectIconsConfigType' => 'applications/project/config/PhabricatorProjectIconsConfigType.php',
'PhabricatorProjectImageTransaction' => 'applications/project/xaction/PhabricatorProjectImageTransaction.php',
'PhabricatorProjectInterface' => 'applications/project/interface/PhabricatorProjectInterface.php',
'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php',
'PhabricatorProjectListView' => 'applications/project/view/PhabricatorProjectListView.php',
'PhabricatorProjectLockController' => 'applications/project/controller/PhabricatorProjectLockController.php',
'PhabricatorProjectLockTransaction' => 'applications/project/xaction/PhabricatorProjectLockTransaction.php',
'PhabricatorProjectLogicalAncestorDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalAncestorDatasource.php',
'PhabricatorProjectLogicalDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalDatasource.php',
'PhabricatorProjectLogicalOnlyDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalOnlyDatasource.php',
'PhabricatorProjectLogicalOrNotDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalOrNotDatasource.php',
'PhabricatorProjectLogicalUserDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalUserDatasource.php',
'PhabricatorProjectLogicalViewerDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php',
'PhabricatorProjectManageController' => 'applications/project/controller/PhabricatorProjectManageController.php',
'PhabricatorProjectManageProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectManageProfileMenuItem.php',
'PhabricatorProjectMaterializedMemberEdgeType' => 'applications/project/edge/PhabricatorProjectMaterializedMemberEdgeType.php',
'PhabricatorProjectMemberListView' => 'applications/project/view/PhabricatorProjectMemberListView.php',
'PhabricatorProjectMemberOfProjectEdgeType' => 'applications/project/edge/PhabricatorProjectMemberOfProjectEdgeType.php',
'PhabricatorProjectMembersAddController' => 'applications/project/controller/PhabricatorProjectMembersAddController.php',
'PhabricatorProjectMembersDatasource' => 'applications/project/typeahead/PhabricatorProjectMembersDatasource.php',
'PhabricatorProjectMembersPolicyRule' => 'applications/project/policyrule/PhabricatorProjectMembersPolicyRule.php',
'PhabricatorProjectMembersProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectMembersProfileMenuItem.php',
'PhabricatorProjectMembersRemoveController' => 'applications/project/controller/PhabricatorProjectMembersRemoveController.php',
'PhabricatorProjectMembersViewController' => 'applications/project/controller/PhabricatorProjectMembersViewController.php',
'PhabricatorProjectMenuItemController' => 'applications/project/controller/PhabricatorProjectMenuItemController.php',
'PhabricatorProjectMilestoneTransaction' => 'applications/project/xaction/PhabricatorProjectMilestoneTransaction.php',
'PhabricatorProjectMoveController' => 'applications/project/controller/PhabricatorProjectMoveController.php',
'PhabricatorProjectNameContextFreeGrammar' => 'applications/project/lipsum/PhabricatorProjectNameContextFreeGrammar.php',
'PhabricatorProjectNameTransaction' => 'applications/project/xaction/PhabricatorProjectNameTransaction.php',
'PhabricatorProjectNoProjectsDatasource' => 'applications/project/typeahead/PhabricatorProjectNoProjectsDatasource.php',
'PhabricatorProjectObjectHasProjectEdgeType' => 'applications/project/edge/PhabricatorProjectObjectHasProjectEdgeType.php',
'PhabricatorProjectOrUserDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserDatasource.php',
'PhabricatorProjectOrUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserFunctionDatasource.php',
'PhabricatorProjectPHIDResolver' => 'applications/phid/resolver/PhabricatorProjectPHIDResolver.php',
'PhabricatorProjectParentTransaction' => 'applications/project/xaction/PhabricatorProjectParentTransaction.php',
'PhabricatorProjectPictureProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectPictureProfileMenuItem.php',
'PhabricatorProjectPointsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectPointsProfileMenuItem.php',
'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php',
'PhabricatorProjectProfileMenuEngine' => 'applications/project/engine/PhabricatorProjectProfileMenuEngine.php',
'PhabricatorProjectProfileMenuItem' => 'applications/search/menuitem/PhabricatorProjectProfileMenuItem.php',
'PhabricatorProjectProjectHasMemberEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasMemberEdgeType.php',
'PhabricatorProjectProjectHasObjectEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasObjectEdgeType.php',
'PhabricatorProjectProjectPHIDType' => 'applications/project/phid/PhabricatorProjectProjectPHIDType.php',
'PhabricatorProjectQuery' => 'applications/project/query/PhabricatorProjectQuery.php',
'PhabricatorProjectRemoveHeraldAction' => 'applications/project/herald/PhabricatorProjectRemoveHeraldAction.php',
'PhabricatorProjectReportsController' => 'applications/project/controller/PhabricatorProjectReportsController.php',
'PhabricatorProjectReportsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectReportsProfileMenuItem.php',
'PhabricatorProjectSchemaSpec' => 'applications/project/storage/PhabricatorProjectSchemaSpec.php',
'PhabricatorProjectSearchEngine' => 'applications/project/query/PhabricatorProjectSearchEngine.php',
'PhabricatorProjectSearchField' => 'applications/project/searchfield/PhabricatorProjectSearchField.php',
'PhabricatorProjectSilenceController' => 'applications/project/controller/PhabricatorProjectSilenceController.php',
'PhabricatorProjectSilencedEdgeType' => 'applications/project/edge/PhabricatorProjectSilencedEdgeType.php',
'PhabricatorProjectSlug' => 'applications/project/storage/PhabricatorProjectSlug.php',
'PhabricatorProjectSlugsTransaction' => 'applications/project/xaction/PhabricatorProjectSlugsTransaction.php',
'PhabricatorProjectSortTransaction' => 'applications/project/xaction/PhabricatorProjectSortTransaction.php',
'PhabricatorProjectStandardCustomField' => 'applications/project/customfield/PhabricatorProjectStandardCustomField.php',
'PhabricatorProjectStatus' => 'applications/project/constants/PhabricatorProjectStatus.php',
'PhabricatorProjectStatusTransaction' => 'applications/project/xaction/PhabricatorProjectStatusTransaction.php',
'PhabricatorProjectSubprojectWarningController' => 'applications/project/controller/PhabricatorProjectSubprojectWarningController.php',
'PhabricatorProjectSubprojectsController' => 'applications/project/controller/PhabricatorProjectSubprojectsController.php',
'PhabricatorProjectSubprojectsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectSubprojectsProfileMenuItem.php',
'PhabricatorProjectSubtypeDatasource' => 'applications/project/typeahead/PhabricatorProjectSubtypeDatasource.php',
'PhabricatorProjectSubtypesConfigType' => 'applications/project/config/PhabricatorProjectSubtypesConfigType.php',
'PhabricatorProjectTagsAddedField' => 'applications/project/herald/PhabricatorProjectTagsAddedField.php',
'PhabricatorProjectTagsField' => 'applications/project/herald/PhabricatorProjectTagsField.php',
'PhabricatorProjectTagsRemovedField' => 'applications/project/herald/PhabricatorProjectTagsRemovedField.php',
'PhabricatorProjectTestDataGenerator' => 'applications/project/lipsum/PhabricatorProjectTestDataGenerator.php',
'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php',
'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php',
'PhabricatorProjectTransactionQuery' => 'applications/project/query/PhabricatorProjectTransactionQuery.php',
'PhabricatorProjectTransactionType' => 'applications/project/xaction/PhabricatorProjectTransactionType.php',
'PhabricatorProjectTrigger' => 'applications/project/storage/PhabricatorProjectTrigger.php',
'PhabricatorProjectTriggerAddProjectsRule' => 'applications/project/trigger/PhabricatorProjectTriggerAddProjectsRule.php',
'PhabricatorProjectTriggerController' => 'applications/project/controller/trigger/PhabricatorProjectTriggerController.php',
'PhabricatorProjectTriggerCorruptionException' => 'applications/project/exception/PhabricatorProjectTriggerCorruptionException.php',
'PhabricatorProjectTriggerEditController' => 'applications/project/controller/trigger/PhabricatorProjectTriggerEditController.php',
'PhabricatorProjectTriggerEditor' => 'applications/project/editor/PhabricatorProjectTriggerEditor.php',
'PhabricatorProjectTriggerInvalidRule' => 'applications/project/trigger/PhabricatorProjectTriggerInvalidRule.php',
'PhabricatorProjectTriggerListController' => 'applications/project/controller/trigger/PhabricatorProjectTriggerListController.php',
'PhabricatorProjectTriggerManiphestOwnerRule' => 'applications/project/trigger/PhabricatorProjectTriggerManiphestOwnerRule.php',
'PhabricatorProjectTriggerManiphestPriorityRule' => 'applications/project/trigger/PhabricatorProjectTriggerManiphestPriorityRule.php',
'PhabricatorProjectTriggerManiphestStatusRule' => 'applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php',
'PhabricatorProjectTriggerNameTransaction' => 'applications/project/xaction/trigger/PhabricatorProjectTriggerNameTransaction.php',
'PhabricatorProjectTriggerPHIDType' => 'applications/project/phid/PhabricatorProjectTriggerPHIDType.php',
'PhabricatorProjectTriggerPlaySoundRule' => 'applications/project/trigger/PhabricatorProjectTriggerPlaySoundRule.php',
'PhabricatorProjectTriggerQuery' => 'applications/project/query/PhabricatorProjectTriggerQuery.php',
'PhabricatorProjectTriggerRemoveProjectsRule' => 'applications/project/trigger/PhabricatorProjectTriggerRemoveProjectsRule.php',
'PhabricatorProjectTriggerRule' => 'applications/project/trigger/PhabricatorProjectTriggerRule.php',
'PhabricatorProjectTriggerRuleRecord' => 'applications/project/trigger/PhabricatorProjectTriggerRuleRecord.php',
'PhabricatorProjectTriggerRulesetTransaction' => 'applications/project/xaction/trigger/PhabricatorProjectTriggerRulesetTransaction.php',
'PhabricatorProjectTriggerSearchEngine' => 'applications/project/query/PhabricatorProjectTriggerSearchEngine.php',
'PhabricatorProjectTriggerTransaction' => 'applications/project/storage/PhabricatorProjectTriggerTransaction.php',
'PhabricatorProjectTriggerTransactionQuery' => 'applications/project/query/PhabricatorProjectTriggerTransactionQuery.php',
'PhabricatorProjectTriggerTransactionType' => 'applications/project/xaction/trigger/PhabricatorProjectTriggerTransactionType.php',
'PhabricatorProjectTriggerUnknownRule' => 'applications/project/trigger/PhabricatorProjectTriggerUnknownRule.php',
'PhabricatorProjectTriggerUsage' => 'applications/project/storage/PhabricatorProjectTriggerUsage.php',
'PhabricatorProjectTriggerUsageIndexEngineExtension' => 'applications/project/engineextension/PhabricatorProjectTriggerUsageIndexEngineExtension.php',
'PhabricatorProjectTriggerViewController' => 'applications/project/controller/trigger/PhabricatorProjectTriggerViewController.php',
'PhabricatorProjectTypeTransaction' => 'applications/project/xaction/PhabricatorProjectTypeTransaction.php',
'PhabricatorProjectUIEventListener' => 'applications/project/events/PhabricatorProjectUIEventListener.php',
'PhabricatorProjectUpdateController' => 'applications/project/controller/PhabricatorProjectUpdateController.php',
'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php',
'PhabricatorProjectUserListView' => 'applications/project/view/PhabricatorProjectUserListView.php',
'PhabricatorProjectViewController' => 'applications/project/controller/PhabricatorProjectViewController.php',
'PhabricatorProjectWatchController' => 'applications/project/controller/PhabricatorProjectWatchController.php',
'PhabricatorProjectWatcherListView' => 'applications/project/view/PhabricatorProjectWatcherListView.php',
'PhabricatorProjectWorkboardBackgroundColor' => 'applications/project/constants/PhabricatorProjectWorkboardBackgroundColor.php',
'PhabricatorProjectWorkboardBackgroundTransaction' => 'applications/project/xaction/PhabricatorProjectWorkboardBackgroundTransaction.php',
'PhabricatorProjectWorkboardProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectWorkboardProfileMenuItem.php',
'PhabricatorProjectWorkboardTransaction' => 'applications/project/xaction/PhabricatorProjectWorkboardTransaction.php',
'PhabricatorProjectsAllPolicyRule' => 'applications/project/policyrule/PhabricatorProjectsAllPolicyRule.php',
'PhabricatorProjectsAncestorsSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsAncestorsSearchEngineAttachment.php',
'PhabricatorProjectsBasePolicyRule' => 'applications/project/policyrule/PhabricatorProjectsBasePolicyRule.php',
'PhabricatorProjectsCurtainExtension' => 'applications/project/engineextension/PhabricatorProjectsCurtainExtension.php',
'PhabricatorProjectsEditEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php',
'PhabricatorProjectsEditField' => 'applications/transactions/editfield/PhabricatorProjectsEditField.php',
'PhabricatorProjectsExportEngineExtension' => 'infrastructure/export/engine/PhabricatorProjectsExportEngineExtension.php',
'PhabricatorProjectsFulltextEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsFulltextEngineExtension.php',
'PhabricatorProjectsMailEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsMailEngineExtension.php',
'PhabricatorProjectsMembersSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsMembersSearchEngineAttachment.php',
'PhabricatorProjectsMembershipIndexEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php',
'PhabricatorProjectsPolicyRule' => 'applications/project/policyrule/PhabricatorProjectsPolicyRule.php',
'PhabricatorProjectsSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsSearchEngineAttachment.php',
'PhabricatorProjectsSearchEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsSearchEngineExtension.php',
'PhabricatorProjectsWatchersSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsWatchersSearchEngineAttachment.php',
'PhabricatorPronounSetting' => 'applications/settings/setting/PhabricatorPronounSetting.php',
'PhabricatorProtocolLog' => 'infrastructure/log/PhabricatorProtocolLog.php',
'PhabricatorPureChartFunction' => 'applications/fact/chart/PhabricatorPureChartFunction.php',
'PhabricatorPygmentSetupCheck' => 'applications/config/check/PhabricatorPygmentSetupCheck.php',
'PhabricatorQuery' => 'infrastructure/query/PhabricatorQuery.php',
'PhabricatorQueryConstraint' => 'infrastructure/query/constraint/PhabricatorQueryConstraint.php',
'PhabricatorQueryCursor' => 'infrastructure/query/policy/PhabricatorQueryCursor.php',
'PhabricatorQueryIterator' => 'infrastructure/storage/lisk/PhabricatorQueryIterator.php',
'PhabricatorQueryOrderItem' => 'infrastructure/query/order/PhabricatorQueryOrderItem.php',
'PhabricatorQueryOrderTestCase' => 'infrastructure/query/order/__tests__/PhabricatorQueryOrderTestCase.php',
'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php',
'PhabricatorRateLimitRequestExceptionHandler' => 'aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php',
'PhabricatorReassignEmailUserLogType' => 'applications/people/userlog/PhabricatorReassignEmailUserLogType.php',
'PhabricatorRebuildIndexesWorker' => 'applications/search/worker/PhabricatorRebuildIndexesWorker.php',
'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php',
'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php',
'PhabricatorRefreshCSRFController' => 'applications/auth/controller/PhabricatorRefreshCSRFController.php',
'PhabricatorRegexListConfigType' => 'applications/config/type/PhabricatorRegexListConfigType.php',
'PhabricatorRegistrationProfile' => 'applications/people/storage/PhabricatorRegistrationProfile.php',
- 'PhabricatorReleephApplication' => 'applications/releeph/application/PhabricatorReleephApplication.php',
- 'PhabricatorReleephApplicationConfigOptions' => 'applications/releeph/config/PhabricatorReleephApplicationConfigOptions.php',
'PhabricatorRemarkupCachePurger' => 'applications/cache/purger/PhabricatorRemarkupCachePurger.php',
'PhabricatorRemarkupControl' => 'view/form/control/PhabricatorRemarkupControl.php',
'PhabricatorRemarkupCowsayBlockInterpreter' => 'infrastructure/markup/interpreter/PhabricatorRemarkupCowsayBlockInterpreter.php',
'PhabricatorRemarkupCustomBlockRule' => 'infrastructure/markup/rule/PhabricatorRemarkupCustomBlockRule.php',
'PhabricatorRemarkupCustomInlineRule' => 'infrastructure/markup/rule/PhabricatorRemarkupCustomInlineRule.php',
'PhabricatorRemarkupDocumentEngine' => 'applications/files/document/PhabricatorRemarkupDocumentEngine.php',
'PhabricatorRemarkupEditField' => 'applications/transactions/editfield/PhabricatorRemarkupEditField.php',
'PhabricatorRemarkupFigletBlockInterpreter' => 'infrastructure/markup/interpreter/PhabricatorRemarkupFigletBlockInterpreter.php',
'PhabricatorRemarkupHyperlinkEngineExtension' => 'applications/remarkup/engineextension/PhabricatorRemarkupHyperlinkEngineExtension.php',
'PhabricatorRemarkupUIExample' => 'applications/uiexample/examples/PhabricatorRemarkupUIExample.php',
'PhabricatorRemoveEmailUserLogType' => 'applications/people/userlog/PhabricatorRemoveEmailUserLogType.php',
'PhabricatorRemoveMultifactorUserLogType' => 'applications/people/userlog/PhabricatorRemoveMultifactorUserLogType.php',
'PhabricatorRepositoriesSetupCheck' => 'applications/config/check/PhabricatorRepositoriesSetupCheck.php',
'PhabricatorRepository' => 'applications/repository/storage/PhabricatorRepository.php',
'PhabricatorRepositoryActivateTransaction' => 'applications/repository/xaction/PhabricatorRepositoryActivateTransaction.php',
'PhabricatorRepositoryAuditRequest' => 'applications/repository/storage/PhabricatorRepositoryAuditRequest.php',
'PhabricatorRepositoryBlueprintsTransaction' => 'applications/repository/xaction/PhabricatorRepositoryBlueprintsTransaction.php',
'PhabricatorRepositoryBranch' => 'applications/repository/storage/PhabricatorRepositoryBranch.php',
'PhabricatorRepositoryCallsignTransaction' => 'applications/repository/xaction/PhabricatorRepositoryCallsignTransaction.php',
'PhabricatorRepositoryCommit' => 'applications/repository/storage/PhabricatorRepositoryCommit.php',
'PhabricatorRepositoryCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php',
'PhabricatorRepositoryCommitData' => 'applications/repository/storage/PhabricatorRepositoryCommitData.php',
'PhabricatorRepositoryCommitHint' => 'applications/repository/storage/PhabricatorRepositoryCommitHint.php',
'PhabricatorRepositoryCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php',
'PhabricatorRepositoryCommitPHIDType' => 'applications/repository/phid/PhabricatorRepositoryCommitPHIDType.php',
'PhabricatorRepositoryCommitParserWorker' => 'applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php',
'PhabricatorRepositoryCommitPublishWorker' => 'applications/repository/worker/PhabricatorRepositoryCommitPublishWorker.php',
'PhabricatorRepositoryCommitRef' => 'applications/repository/engine/PhabricatorRepositoryCommitRef.php',
'PhabricatorRepositoryCommitTestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryCommitTestCase.php',
'PhabricatorRepositoryConfigOptions' => 'applications/repository/config/PhabricatorRepositoryConfigOptions.php',
'PhabricatorRepositoryCopyTimeLimitTransaction' => 'applications/repository/xaction/PhabricatorRepositoryCopyTimeLimitTransaction.php',
'PhabricatorRepositoryDAO' => 'applications/repository/storage/PhabricatorRepositoryDAO.php',
'PhabricatorRepositoryDangerousTransaction' => 'applications/repository/xaction/PhabricatorRepositoryDangerousTransaction.php',
'PhabricatorRepositoryDefaultBranchTransaction' => 'applications/repository/xaction/PhabricatorRepositoryDefaultBranchTransaction.php',
'PhabricatorRepositoryDescriptionTransaction' => 'applications/repository/xaction/PhabricatorRepositoryDescriptionTransaction.php',
'PhabricatorRepositoryDestructibleCodex' => 'applications/repository/codex/PhabricatorRepositoryDestructibleCodex.php',
'PhabricatorRepositoryDiscoveryEngine' => 'applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php',
'PhabricatorRepositoryEditor' => 'applications/repository/editor/PhabricatorRepositoryEditor.php',
'PhabricatorRepositoryEncodingTransaction' => 'applications/repository/xaction/PhabricatorRepositoryEncodingTransaction.php',
'PhabricatorRepositoryEngine' => 'applications/repository/engine/PhabricatorRepositoryEngine.php',
'PhabricatorRepositoryEnormousTransaction' => 'applications/repository/xaction/PhabricatorRepositoryEnormousTransaction.php',
'PhabricatorRepositoryFerretEngine' => 'applications/repository/search/PhabricatorRepositoryFerretEngine.php',
'PhabricatorRepositoryFetchRefsTransaction' => 'applications/repository/xaction/PhabricatorRepositoryFetchRefsTransaction.php',
'PhabricatorRepositoryFilesizeLimitTransaction' => 'applications/repository/xaction/PhabricatorRepositoryFilesizeLimitTransaction.php',
'PhabricatorRepositoryFulltextEngine' => 'applications/repository/search/PhabricatorRepositoryFulltextEngine.php',
'PhabricatorRepositoryGitCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryGitCommitChangeParserWorker.php',
'PhabricatorRepositoryGitCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryGitCommitMessageParserWorker.php',
'PhabricatorRepositoryGitLFSRef' => 'applications/repository/storage/PhabricatorRepositoryGitLFSRef.php',
'PhabricatorRepositoryGitLFSRefQuery' => 'applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php',
'PhabricatorRepositoryGraphCache' => 'applications/repository/graphcache/PhabricatorRepositoryGraphCache.php',
'PhabricatorRepositoryGraphStream' => 'applications/repository/daemon/PhabricatorRepositoryGraphStream.php',
'PhabricatorRepositoryIdentity' => 'applications/repository/storage/PhabricatorRepositoryIdentity.php',
'PhabricatorRepositoryIdentityAssignTransaction' => 'applications/repository/xaction/PhabricatorRepositoryIdentityAssignTransaction.php',
'PhabricatorRepositoryIdentityChangeWorker' => 'applications/repository/worker/PhabricatorRepositoryIdentityChangeWorker.php',
'PhabricatorRepositoryIdentityEditEngine' => 'applications/repository/engine/PhabricatorRepositoryIdentityEditEngine.php',
'PhabricatorRepositoryIdentityFerretEngine' => 'applications/repository/search/PhabricatorRepositoryIdentityFerretEngine.php',
'PhabricatorRepositoryIdentityPHIDType' => 'applications/repository/phid/PhabricatorRepositoryIdentityPHIDType.php',
'PhabricatorRepositoryIdentityQuery' => 'applications/repository/query/PhabricatorRepositoryIdentityQuery.php',
'PhabricatorRepositoryIdentityTransaction' => 'applications/repository/storage/PhabricatorRepositoryIdentityTransaction.php',
'PhabricatorRepositoryIdentityTransactionQuery' => 'applications/repository/query/PhabricatorRepositoryIdentityTransactionQuery.php',
'PhabricatorRepositoryIdentityTransactionType' => 'applications/repository/xaction/PhabricatorRepositoryIdentityTransactionType.php',
'PhabricatorRepositoryMaintenanceTransaction' => 'applications/repository/xaction/PhabricatorRepositoryMaintenanceTransaction.php',
'PhabricatorRepositoryManagementCacheWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementCacheWorkflow.php',
'PhabricatorRepositoryManagementClusterizeWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementClusterizeWorkflow.php',
'PhabricatorRepositoryManagementDiscoverWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php',
'PhabricatorRepositoryManagementHintWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementHintWorkflow.php',
'PhabricatorRepositoryManagementImportingWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php',
'PhabricatorRepositoryManagementListPathsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListPathsWorkflow.php',
'PhabricatorRepositoryManagementListWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php',
'PhabricatorRepositoryManagementLockWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementLockWorkflow.php',
'PhabricatorRepositoryManagementMaintenanceWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMaintenanceWorkflow.php',
'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php',
'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkReachableWorkflow.php',
'PhabricatorRepositoryManagementMirrorWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php',
'PhabricatorRepositoryManagementMovePathsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMovePathsWorkflow.php',
'PhabricatorRepositoryManagementParentsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementParentsWorkflow.php',
'PhabricatorRepositoryManagementPullWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementPullWorkflow.php',
'PhabricatorRepositoryManagementRebuildIdentitiesWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementRebuildIdentitiesWorkflow.php',
'PhabricatorRepositoryManagementRefsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php',
'PhabricatorRepositoryManagementReparseWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php',
'PhabricatorRepositoryManagementThawWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementThawWorkflow.php',
'PhabricatorRepositoryManagementUnpublishWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementUnpublishWorkflow.php',
'PhabricatorRepositoryManagementUpdateWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php',
'PhabricatorRepositoryManagementWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementWorkflow.php',
'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryMercurialCommitChangeParserWorker.php',
'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryMercurialCommitMessageParserWorker.php',
'PhabricatorRepositoryMirror' => 'applications/repository/storage/PhabricatorRepositoryMirror.php',
'PhabricatorRepositoryMirrorEngine' => 'applications/repository/engine/PhabricatorRepositoryMirrorEngine.php',
'PhabricatorRepositoryNameTransaction' => 'applications/repository/xaction/PhabricatorRepositoryNameTransaction.php',
'PhabricatorRepositoryNotifyTransaction' => 'applications/repository/xaction/PhabricatorRepositoryNotifyTransaction.php',
'PhabricatorRepositoryOldRef' => 'applications/repository/storage/PhabricatorRepositoryOldRef.php',
'PhabricatorRepositoryParsedChange' => 'applications/repository/data/PhabricatorRepositoryParsedChange.php',
'PhabricatorRepositoryPermanentRefsTransaction' => 'applications/repository/xaction/PhabricatorRepositoryPermanentRefsTransaction.php',
'PhabricatorRepositoryPublisher' => 'applications/repository/query/PhabricatorRepositoryPublisher.php',
'PhabricatorRepositoryPublisherHoldReason' => 'applications/repository/query/PhabricatorRepositoryPublisherHoldReason.php',
'PhabricatorRepositoryPullEngine' => 'applications/repository/engine/PhabricatorRepositoryPullEngine.php',
'PhabricatorRepositoryPullEvent' => 'applications/repository/storage/PhabricatorRepositoryPullEvent.php',
'PhabricatorRepositoryPullEventPHIDType' => 'applications/repository/phid/PhabricatorRepositoryPullEventPHIDType.php',
'PhabricatorRepositoryPullEventQuery' => 'applications/repository/query/PhabricatorRepositoryPullEventQuery.php',
'PhabricatorRepositoryPullLocalDaemon' => 'applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php',
'PhabricatorRepositoryPullLocalDaemonModule' => 'applications/repository/daemon/PhabricatorRepositoryPullLocalDaemonModule.php',
'PhabricatorRepositoryPushEvent' => 'applications/repository/storage/PhabricatorRepositoryPushEvent.php',
'PhabricatorRepositoryPushEventPHIDType' => 'applications/repository/phid/PhabricatorRepositoryPushEventPHIDType.php',
'PhabricatorRepositoryPushEventQuery' => 'applications/repository/query/PhabricatorRepositoryPushEventQuery.php',
'PhabricatorRepositoryPushLog' => 'applications/repository/storage/PhabricatorRepositoryPushLog.php',
'PhabricatorRepositoryPushLogPHIDType' => 'applications/repository/phid/PhabricatorRepositoryPushLogPHIDType.php',
'PhabricatorRepositoryPushLogQuery' => 'applications/repository/query/PhabricatorRepositoryPushLogQuery.php',
'PhabricatorRepositoryPushLogSearchEngine' => 'applications/repository/query/PhabricatorRepositoryPushLogSearchEngine.php',
'PhabricatorRepositoryPushMailWorker' => 'applications/repository/worker/PhabricatorRepositoryPushMailWorker.php',
'PhabricatorRepositoryPushPolicyTransaction' => 'applications/repository/xaction/PhabricatorRepositoryPushPolicyTransaction.php',
'PhabricatorRepositoryPushReplyHandler' => 'applications/repository/mail/PhabricatorRepositoryPushReplyHandler.php',
'PhabricatorRepositoryQuery' => 'applications/repository/query/PhabricatorRepositoryQuery.php',
'PhabricatorRepositoryRefCursor' => 'applications/repository/storage/PhabricatorRepositoryRefCursor.php',
'PhabricatorRepositoryRefCursorPHIDType' => 'applications/repository/phid/PhabricatorRepositoryRefCursorPHIDType.php',
'PhabricatorRepositoryRefCursorQuery' => 'applications/repository/query/PhabricatorRepositoryRefCursorQuery.php',
'PhabricatorRepositoryRefEngine' => 'applications/repository/engine/PhabricatorRepositoryRefEngine.php',
'PhabricatorRepositoryRefPosition' => 'applications/repository/storage/PhabricatorRepositoryRefPosition.php',
'PhabricatorRepositoryRepositoryPHIDType' => 'applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php',
'PhabricatorRepositorySVNSubpathTransaction' => 'applications/repository/xaction/PhabricatorRepositorySVNSubpathTransaction.php',
'PhabricatorRepositorySchemaSpec' => 'applications/repository/storage/PhabricatorRepositorySchemaSpec.php',
'PhabricatorRepositorySearchEngine' => 'applications/repository/query/PhabricatorRepositorySearchEngine.php',
'PhabricatorRepositoryServiceTransaction' => 'applications/repository/xaction/PhabricatorRepositoryServiceTransaction.php',
'PhabricatorRepositorySlugTransaction' => 'applications/repository/xaction/PhabricatorRepositorySlugTransaction.php',
'PhabricatorRepositoryStagingURITransaction' => 'applications/repository/xaction/PhabricatorRepositoryStagingURITransaction.php',
'PhabricatorRepositoryStatusMessage' => 'applications/repository/storage/PhabricatorRepositoryStatusMessage.php',
'PhabricatorRepositorySvnCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositorySvnCommitChangeParserWorker.php',
'PhabricatorRepositorySvnCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositorySvnCommitMessageParserWorker.php',
'PhabricatorRepositorySymbol' => 'applications/repository/storage/PhabricatorRepositorySymbol.php',
'PhabricatorRepositorySymbolLanguagesTransaction' => 'applications/repository/xaction/PhabricatorRepositorySymbolLanguagesTransaction.php',
'PhabricatorRepositorySymbolSourcesTransaction' => 'applications/repository/xaction/PhabricatorRepositorySymbolSourcesTransaction.php',
'PhabricatorRepositorySyncEvent' => 'applications/repository/storage/PhabricatorRepositorySyncEvent.php',
'PhabricatorRepositorySyncEventPHIDType' => 'applications/repository/phid/PhabricatorRepositorySyncEventPHIDType.php',
'PhabricatorRepositorySyncEventQuery' => 'applications/repository/query/PhabricatorRepositorySyncEventQuery.php',
'PhabricatorRepositoryTestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php',
'PhabricatorRepositoryTouchLimitTransaction' => 'applications/repository/xaction/PhabricatorRepositoryTouchLimitTransaction.php',
'PhabricatorRepositoryTrackOnlyTransaction' => 'applications/repository/xaction/PhabricatorRepositoryTrackOnlyTransaction.php',
'PhabricatorRepositoryTransaction' => 'applications/repository/storage/PhabricatorRepositoryTransaction.php',
'PhabricatorRepositoryTransactionQuery' => 'applications/repository/query/PhabricatorRepositoryTransactionQuery.php',
'PhabricatorRepositoryTransactionType' => 'applications/repository/xaction/PhabricatorRepositoryTransactionType.php',
'PhabricatorRepositoryType' => 'applications/repository/constants/PhabricatorRepositoryType.php',
'PhabricatorRepositoryURI' => 'applications/repository/storage/PhabricatorRepositoryURI.php',
'PhabricatorRepositoryURIIndex' => 'applications/repository/storage/PhabricatorRepositoryURIIndex.php',
'PhabricatorRepositoryURIPHIDType' => 'applications/repository/phid/PhabricatorRepositoryURIPHIDType.php',
'PhabricatorRepositoryURIQuery' => 'applications/repository/query/PhabricatorRepositoryURIQuery.php',
'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php',
'PhabricatorRepositoryURITransaction' => 'applications/repository/storage/PhabricatorRepositoryURITransaction.php',
'PhabricatorRepositoryURITransactionQuery' => 'applications/repository/query/PhabricatorRepositoryURITransactionQuery.php',
'PhabricatorRepositoryVCSTransaction' => 'applications/repository/xaction/PhabricatorRepositoryVCSTransaction.php',
'PhabricatorRepositoryWorkingCopyVersion' => 'applications/repository/storage/PhabricatorRepositoryWorkingCopyVersion.php',
'PhabricatorRequestExceptionHandler' => 'aphront/handler/PhabricatorRequestExceptionHandler.php',
'PhabricatorResetPasswordUserLogType' => 'applications/people/userlog/PhabricatorResetPasswordUserLogType.php',
'PhabricatorResourceSite' => 'aphront/site/PhabricatorResourceSite.php',
'PhabricatorRobotsBlogController' => 'applications/system/controller/robots/PhabricatorRobotsBlogController.php',
'PhabricatorRobotsController' => 'applications/system/controller/robots/PhabricatorRobotsController.php',
'PhabricatorRobotsPlatformController' => 'applications/system/controller/robots/PhabricatorRobotsPlatformController.php',
'PhabricatorRobotsResourceController' => 'applications/system/controller/robots/PhabricatorRobotsResourceController.php',
'PhabricatorRobotsShortController' => 'applications/system/controller/robots/PhabricatorRobotsShortController.php',
'PhabricatorS3FileStorageEngine' => 'applications/files/engine/PhabricatorS3FileStorageEngine.php',
'PhabricatorSMSAuthFactor' => 'applications/auth/factor/PhabricatorSMSAuthFactor.php',
'PhabricatorSQLPatchList' => 'infrastructure/storage/patch/PhabricatorSQLPatchList.php',
'PhabricatorSSHKeyGenerator' => 'infrastructure/util/PhabricatorSSHKeyGenerator.php',
'PhabricatorSSHKeysSettingsPanel' => 'applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php',
'PhabricatorSSHLog' => 'infrastructure/log/PhabricatorSSHLog.php',
'PhabricatorSSHPassthruCommand' => 'infrastructure/ssh/PhabricatorSSHPassthruCommand.php',
'PhabricatorSSHPublicKeyInterface' => 'applications/auth/sshkey/PhabricatorSSHPublicKeyInterface.php',
'PhabricatorSSHWorkflow' => 'infrastructure/ssh/PhabricatorSSHWorkflow.php',
'PhabricatorSavedQuery' => 'applications/search/storage/PhabricatorSavedQuery.php',
'PhabricatorSavedQueryQuery' => 'applications/search/query/PhabricatorSavedQueryQuery.php',
'PhabricatorScaleChartFunction' => 'applications/fact/chart/PhabricatorScaleChartFunction.php',
'PhabricatorScheduleTaskTriggerAction' => 'infrastructure/daemon/workers/action/PhabricatorScheduleTaskTriggerAction.php',
'PhabricatorScopedEnv' => 'infrastructure/env/PhabricatorScopedEnv.php',
'PhabricatorSearchAbstractDocument' => 'applications/search/index/PhabricatorSearchAbstractDocument.php',
'PhabricatorSearchApplication' => 'applications/search/application/PhabricatorSearchApplication.php',
'PhabricatorSearchApplicationSearchEngine' => 'applications/search/query/PhabricatorSearchApplicationSearchEngine.php',
'PhabricatorSearchApplicationStorageEnginePanel' => 'applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php',
'PhabricatorSearchBaseController' => 'applications/search/controller/PhabricatorSearchBaseController.php',
'PhabricatorSearchCheckboxesField' => 'applications/search/field/PhabricatorSearchCheckboxesField.php',
'PhabricatorSearchConstraintException' => 'applications/search/exception/PhabricatorSearchConstraintException.php',
'PhabricatorSearchController' => 'applications/search/controller/PhabricatorSearchController.php',
'PhabricatorSearchCustomFieldProxyField' => 'applications/search/field/PhabricatorSearchCustomFieldProxyField.php',
'PhabricatorSearchDAO' => 'applications/search/storage/PhabricatorSearchDAO.php',
'PhabricatorSearchDatasource' => 'applications/search/typeahead/PhabricatorSearchDatasource.php',
'PhabricatorSearchDatasourceField' => 'applications/search/field/PhabricatorSearchDatasourceField.php',
'PhabricatorSearchDateControlField' => 'applications/search/field/PhabricatorSearchDateControlField.php',
'PhabricatorSearchDateField' => 'applications/search/field/PhabricatorSearchDateField.php',
'PhabricatorSearchDefaultController' => 'applications/search/controller/PhabricatorSearchDefaultController.php',
'PhabricatorSearchDeleteController' => 'applications/search/controller/PhabricatorSearchDeleteController.php',
'PhabricatorSearchDocument' => 'applications/search/storage/document/PhabricatorSearchDocument.php',
'PhabricatorSearchDocumentFieldType' => 'applications/search/constants/PhabricatorSearchDocumentFieldType.php',
'PhabricatorSearchDocumentQuery' => 'applications/search/query/PhabricatorSearchDocumentQuery.php',
'PhabricatorSearchDocumentRelationship' => 'applications/search/storage/document/PhabricatorSearchDocumentRelationship.php',
'PhabricatorSearchDocumentTypeDatasource' => 'applications/search/typeahead/PhabricatorSearchDocumentTypeDatasource.php',
'PhabricatorSearchEditController' => 'applications/search/controller/PhabricatorSearchEditController.php',
'PhabricatorSearchEngineAPIMethod' => 'applications/search/engine/PhabricatorSearchEngineAPIMethod.php',
'PhabricatorSearchEngineAttachment' => 'applications/search/engineextension/PhabricatorSearchEngineAttachment.php',
'PhabricatorSearchEngineExtension' => 'applications/search/engineextension/PhabricatorSearchEngineExtension.php',
'PhabricatorSearchEngineExtensionModule' => 'applications/search/engineextension/PhabricatorSearchEngineExtensionModule.php',
'PhabricatorSearchFerretNgramGarbageCollector' => 'applications/search/garbagecollector/PhabricatorSearchFerretNgramGarbageCollector.php',
'PhabricatorSearchField' => 'applications/search/field/PhabricatorSearchField.php',
'PhabricatorSearchHandleController' => 'applications/search/controller/PhabricatorSearchHandleController.php',
'PhabricatorSearchHost' => 'infrastructure/cluster/search/PhabricatorSearchHost.php',
'PhabricatorSearchHovercardController' => 'applications/search/controller/PhabricatorSearchHovercardController.php',
'PhabricatorSearchIndexVersion' => 'applications/search/storage/PhabricatorSearchIndexVersion.php',
'PhabricatorSearchIndexVersionDestructionEngineExtension' => 'applications/search/engineextension/PhabricatorSearchIndexVersionDestructionEngineExtension.php',
'PhabricatorSearchIntField' => 'applications/search/field/PhabricatorSearchIntField.php',
'PhabricatorSearchManagementIndexWorkflow' => 'applications/search/management/PhabricatorSearchManagementIndexWorkflow.php',
'PhabricatorSearchManagementInitWorkflow' => 'applications/search/management/PhabricatorSearchManagementInitWorkflow.php',
'PhabricatorSearchManagementNgramsWorkflow' => 'applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php',
'PhabricatorSearchManagementQueryWorkflow' => 'applications/search/management/PhabricatorSearchManagementQueryWorkflow.php',
'PhabricatorSearchManagementWorkflow' => 'applications/search/management/PhabricatorSearchManagementWorkflow.php',
'PhabricatorSearchNgramEngine' => 'applications/search/engine/PhabricatorSearchNgramEngine.php',
'PhabricatorSearchNgrams' => 'applications/search/ngrams/PhabricatorSearchNgrams.php',
'PhabricatorSearchNgramsDestructionEngineExtension' => 'applications/search/engineextension/PhabricatorSearchNgramsDestructionEngineExtension.php',
'PhabricatorSearchOrderController' => 'applications/search/controller/PhabricatorSearchOrderController.php',
'PhabricatorSearchOrderField' => 'applications/search/field/PhabricatorSearchOrderField.php',
'PhabricatorSearchRelationship' => 'applications/search/constants/PhabricatorSearchRelationship.php',
'PhabricatorSearchRelationshipController' => 'applications/search/controller/PhabricatorSearchRelationshipController.php',
'PhabricatorSearchRelationshipSourceController' => 'applications/search/controller/PhabricatorSearchRelationshipSourceController.php',
'PhabricatorSearchResultBucket' => 'applications/search/buckets/PhabricatorSearchResultBucket.php',
'PhabricatorSearchResultBucketGroup' => 'applications/search/buckets/PhabricatorSearchResultBucketGroup.php',
'PhabricatorSearchResultView' => 'applications/search/view/PhabricatorSearchResultView.php',
'PhabricatorSearchSchemaSpec' => 'applications/search/storage/PhabricatorSearchSchemaSpec.php',
'PhabricatorSearchScopeSetting' => 'applications/settings/setting/PhabricatorSearchScopeSetting.php',
'PhabricatorSearchSelectField' => 'applications/search/field/PhabricatorSearchSelectField.php',
'PhabricatorSearchService' => 'infrastructure/cluster/search/PhabricatorSearchService.php',
'PhabricatorSearchSettingsPanel' => 'applications/settings/panel/PhabricatorSearchSettingsPanel.php',
'PhabricatorSearchStringListField' => 'applications/search/field/PhabricatorSearchStringListField.php',
'PhabricatorSearchSubscribersField' => 'applications/search/field/PhabricatorSearchSubscribersField.php',
'PhabricatorSearchTextField' => 'applications/search/field/PhabricatorSearchTextField.php',
'PhabricatorSearchThreeStateField' => 'applications/search/field/PhabricatorSearchThreeStateField.php',
'PhabricatorSearchTokenizerField' => 'applications/search/field/PhabricatorSearchTokenizerField.php',
'PhabricatorSearchWorker' => 'applications/search/worker/PhabricatorSearchWorker.php',
'PhabricatorSecurityConfigOptions' => 'applications/config/option/PhabricatorSecurityConfigOptions.php',
'PhabricatorSecuritySetupCheck' => 'applications/config/check/PhabricatorSecuritySetupCheck.php',
'PhabricatorSelectEditField' => 'applications/transactions/editfield/PhabricatorSelectEditField.php',
'PhabricatorSelectSetting' => 'applications/settings/setting/PhabricatorSelectSetting.php',
'PhabricatorSelfHyperlinkEngineExtension' => 'applications/meta/engineextension/PhabricatorSelfHyperlinkEngineExtension.php',
'PhabricatorSessionsSettingsPanel' => 'applications/settings/panel/PhabricatorSessionsSettingsPanel.php',
'PhabricatorSetConfigType' => 'applications/config/type/PhabricatorSetConfigType.php',
'PhabricatorSetting' => 'applications/settings/setting/PhabricatorSetting.php',
'PhabricatorSettingsAccountPanelGroup' => 'applications/settings/panelgroup/PhabricatorSettingsAccountPanelGroup.php',
'PhabricatorSettingsAddEmailAction' => 'applications/settings/action/PhabricatorSettingsAddEmailAction.php',
'PhabricatorSettingsAdjustController' => 'applications/settings/controller/PhabricatorSettingsAdjustController.php',
'PhabricatorSettingsApplication' => 'applications/settings/application/PhabricatorSettingsApplication.php',
'PhabricatorSettingsApplicationsPanelGroup' => 'applications/settings/panelgroup/PhabricatorSettingsApplicationsPanelGroup.php',
'PhabricatorSettingsAuthenticationPanelGroup' => 'applications/settings/panelgroup/PhabricatorSettingsAuthenticationPanelGroup.php',
'PhabricatorSettingsDeveloperPanelGroup' => 'applications/settings/panelgroup/PhabricatorSettingsDeveloperPanelGroup.php',
'PhabricatorSettingsEditEngine' => 'applications/settings/editor/PhabricatorSettingsEditEngine.php',
'PhabricatorSettingsEmailPanelGroup' => 'applications/settings/panelgroup/PhabricatorSettingsEmailPanelGroup.php',
'PhabricatorSettingsIssueController' => 'applications/settings/controller/PhabricatorSettingsIssueController.php',
'PhabricatorSettingsListController' => 'applications/settings/controller/PhabricatorSettingsListController.php',
'PhabricatorSettingsLogsPanelGroup' => 'applications/settings/panelgroup/PhabricatorSettingsLogsPanelGroup.php',
'PhabricatorSettingsMainController' => 'applications/settings/controller/PhabricatorSettingsMainController.php',
'PhabricatorSettingsPanel' => 'applications/settings/panel/PhabricatorSettingsPanel.php',
'PhabricatorSettingsPanelGroup' => 'applications/settings/panelgroup/PhabricatorSettingsPanelGroup.php',
'PhabricatorSettingsTimezoneController' => 'applications/settings/controller/PhabricatorSettingsTimezoneController.php',
'PhabricatorSetupCheck' => 'applications/config/check/PhabricatorSetupCheck.php',
'PhabricatorSetupCheckTestCase' => 'applications/config/check/__tests__/PhabricatorSetupCheckTestCase.php',
'PhabricatorSetupEngine' => 'applications/config/engine/PhabricatorSetupEngine.php',
'PhabricatorSetupIssue' => 'applications/config/issue/PhabricatorSetupIssue.php',
'PhabricatorSetupIssueUIExample' => 'applications/uiexample/examples/PhabricatorSetupIssueUIExample.php',
'PhabricatorSetupIssueView' => 'applications/config/view/PhabricatorSetupIssueView.php',
'PhabricatorShiftChartFunction' => 'applications/fact/chart/PhabricatorShiftChartFunction.php',
'PhabricatorShortSite' => 'aphront/site/PhabricatorShortSite.php',
'PhabricatorSignDocumentsUserLogType' => 'applications/people/userlog/PhabricatorSignDocumentsUserLogType.php',
'PhabricatorSimpleEditType' => 'applications/transactions/edittype/PhabricatorSimpleEditType.php',
'PhabricatorSinChartFunction' => 'applications/fact/chart/PhabricatorSinChartFunction.php',
'PhabricatorSite' => 'aphront/site/PhabricatorSite.php',
'PhabricatorSlackAuthProvider' => 'applications/auth/provider/PhabricatorSlackAuthProvider.php',
'PhabricatorSlowvoteApplication' => 'applications/slowvote/application/PhabricatorSlowvoteApplication.php',
'PhabricatorSlowvoteChoice' => 'applications/slowvote/storage/PhabricatorSlowvoteChoice.php',
'PhabricatorSlowvoteCloseController' => 'applications/slowvote/controller/PhabricatorSlowvoteCloseController.php',
- 'PhabricatorSlowvoteCloseTransaction' => 'applications/slowvote/xaction/PhabricatorSlowvoteCloseTransaction.php',
'PhabricatorSlowvoteCommentController' => 'applications/slowvote/controller/PhabricatorSlowvoteCommentController.php',
'PhabricatorSlowvoteController' => 'applications/slowvote/controller/PhabricatorSlowvoteController.php',
'PhabricatorSlowvoteDAO' => 'applications/slowvote/storage/PhabricatorSlowvoteDAO.php',
'PhabricatorSlowvoteDefaultViewCapability' => 'applications/slowvote/capability/PhabricatorSlowvoteDefaultViewCapability.php',
'PhabricatorSlowvoteDescriptionTransaction' => 'applications/slowvote/xaction/PhabricatorSlowvoteDescriptionTransaction.php',
'PhabricatorSlowvoteEditController' => 'applications/slowvote/controller/PhabricatorSlowvoteEditController.php',
'PhabricatorSlowvoteEditor' => 'applications/slowvote/editor/PhabricatorSlowvoteEditor.php',
'PhabricatorSlowvoteListController' => 'applications/slowvote/controller/PhabricatorSlowvoteListController.php',
'PhabricatorSlowvoteMailReceiver' => 'applications/slowvote/mail/PhabricatorSlowvoteMailReceiver.php',
'PhabricatorSlowvoteOption' => 'applications/slowvote/storage/PhabricatorSlowvoteOption.php',
'PhabricatorSlowvotePoll' => 'applications/slowvote/storage/PhabricatorSlowvotePoll.php',
'PhabricatorSlowvotePollController' => 'applications/slowvote/controller/PhabricatorSlowvotePollController.php',
'PhabricatorSlowvotePollPHIDType' => 'applications/slowvote/phid/PhabricatorSlowvotePollPHIDType.php',
'PhabricatorSlowvoteQuery' => 'applications/slowvote/query/PhabricatorSlowvoteQuery.php',
'PhabricatorSlowvoteQuestionTransaction' => 'applications/slowvote/xaction/PhabricatorSlowvoteQuestionTransaction.php',
'PhabricatorSlowvoteReplyHandler' => 'applications/slowvote/mail/PhabricatorSlowvoteReplyHandler.php',
'PhabricatorSlowvoteResponsesTransaction' => 'applications/slowvote/xaction/PhabricatorSlowvoteResponsesTransaction.php',
'PhabricatorSlowvoteSchemaSpec' => 'applications/slowvote/storage/PhabricatorSlowvoteSchemaSpec.php',
'PhabricatorSlowvoteSearchEngine' => 'applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php',
'PhabricatorSlowvoteShuffleTransaction' => 'applications/slowvote/xaction/PhabricatorSlowvoteShuffleTransaction.php',
+ 'PhabricatorSlowvoteStatusTransaction' => 'applications/slowvote/xaction/PhabricatorSlowvoteStatusTransaction.php',
'PhabricatorSlowvoteTransaction' => 'applications/slowvote/storage/PhabricatorSlowvoteTransaction.php',
'PhabricatorSlowvoteTransactionComment' => 'applications/slowvote/storage/PhabricatorSlowvoteTransactionComment.php',
'PhabricatorSlowvoteTransactionQuery' => 'applications/slowvote/query/PhabricatorSlowvoteTransactionQuery.php',
'PhabricatorSlowvoteTransactionType' => 'applications/slowvote/xaction/PhabricatorSlowvoteTransactionType.php',
'PhabricatorSlowvoteVoteController' => 'applications/slowvote/controller/PhabricatorSlowvoteVoteController.php',
+ 'PhabricatorSlowvoteVotingMethodTransaction' => 'applications/slowvote/xaction/PhabricatorSlowvoteVotingMethodTransaction.php',
'PhabricatorSlug' => 'infrastructure/util/PhabricatorSlug.php',
'PhabricatorSlugTestCase' => 'infrastructure/util/__tests__/PhabricatorSlugTestCase.php',
'PhabricatorSourceCodeView' => 'view/layout/PhabricatorSourceCodeView.php',
'PhabricatorSourceDocumentEngine' => 'applications/files/document/PhabricatorSourceDocumentEngine.php',
'PhabricatorSpaceEditField' => 'applications/transactions/editfield/PhabricatorSpaceEditField.php',
'PhabricatorSpacesApplication' => 'applications/spaces/application/PhabricatorSpacesApplication.php',
'PhabricatorSpacesArchiveController' => 'applications/spaces/controller/PhabricatorSpacesArchiveController.php',
'PhabricatorSpacesCapabilityCreateSpaces' => 'applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php',
'PhabricatorSpacesCapabilityDefaultEdit' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultEdit.php',
'PhabricatorSpacesCapabilityDefaultView' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultView.php',
'PhabricatorSpacesController' => 'applications/spaces/controller/PhabricatorSpacesController.php',
'PhabricatorSpacesDAO' => 'applications/spaces/storage/PhabricatorSpacesDAO.php',
'PhabricatorSpacesEditController' => 'applications/spaces/controller/PhabricatorSpacesEditController.php',
'PhabricatorSpacesExportEngineExtension' => 'infrastructure/export/engine/PhabricatorSpacesExportEngineExtension.php',
'PhabricatorSpacesInterface' => 'applications/spaces/interface/PhabricatorSpacesInterface.php',
'PhabricatorSpacesListController' => 'applications/spaces/controller/PhabricatorSpacesListController.php',
'PhabricatorSpacesMailEngineExtension' => 'applications/spaces/engineextension/PhabricatorSpacesMailEngineExtension.php',
'PhabricatorSpacesNamespace' => 'applications/spaces/storage/PhabricatorSpacesNamespace.php',
'PhabricatorSpacesNamespaceArchiveTransaction' => 'applications/spaces/xaction/PhabricatorSpacesNamespaceArchiveTransaction.php',
'PhabricatorSpacesNamespaceDatasource' => 'applications/spaces/typeahead/PhabricatorSpacesNamespaceDatasource.php',
'PhabricatorSpacesNamespaceDefaultTransaction' => 'applications/spaces/xaction/PhabricatorSpacesNamespaceDefaultTransaction.php',
'PhabricatorSpacesNamespaceDescriptionTransaction' => 'applications/spaces/xaction/PhabricatorSpacesNamespaceDescriptionTransaction.php',
'PhabricatorSpacesNamespaceEditor' => 'applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php',
'PhabricatorSpacesNamespaceNameTransaction' => 'applications/spaces/xaction/PhabricatorSpacesNamespaceNameTransaction.php',
'PhabricatorSpacesNamespacePHIDType' => 'applications/spaces/phid/PhabricatorSpacesNamespacePHIDType.php',
'PhabricatorSpacesNamespaceQuery' => 'applications/spaces/query/PhabricatorSpacesNamespaceQuery.php',
'PhabricatorSpacesNamespaceSearchEngine' => 'applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php',
'PhabricatorSpacesNamespaceTransaction' => 'applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php',
'PhabricatorSpacesNamespaceTransactionQuery' => 'applications/spaces/query/PhabricatorSpacesNamespaceTransactionQuery.php',
'PhabricatorSpacesNamespaceTransactionType' => 'applications/spaces/xaction/PhabricatorSpacesNamespaceTransactionType.php',
'PhabricatorSpacesNoAccessController' => 'applications/spaces/controller/PhabricatorSpacesNoAccessController.php',
'PhabricatorSpacesRemarkupRule' => 'applications/spaces/remarkup/PhabricatorSpacesRemarkupRule.php',
'PhabricatorSpacesSchemaSpec' => 'applications/spaces/storage/PhabricatorSpacesSchemaSpec.php',
'PhabricatorSpacesSearchEngineExtension' => 'applications/spaces/engineextension/PhabricatorSpacesSearchEngineExtension.php',
'PhabricatorSpacesSearchField' => 'applications/spaces/searchfield/PhabricatorSpacesSearchField.php',
'PhabricatorSpacesTestCase' => 'applications/spaces/__tests__/PhabricatorSpacesTestCase.php',
'PhabricatorSpacesViewController' => 'applications/spaces/controller/PhabricatorSpacesViewController.php',
'PhabricatorStandardCustomField' => 'infrastructure/customfield/standard/PhabricatorStandardCustomField.php',
'PhabricatorStandardCustomFieldBlueprints' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldBlueprints.php',
'PhabricatorStandardCustomFieldBool' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php',
'PhabricatorStandardCustomFieldCredential' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php',
'PhabricatorStandardCustomFieldDatasource' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldDatasource.php',
'PhabricatorStandardCustomFieldDate' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php',
'PhabricatorStandardCustomFieldHeader' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldHeader.php',
'PhabricatorStandardCustomFieldInt' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldInt.php',
'PhabricatorStandardCustomFieldInterface' => 'infrastructure/customfield/interface/PhabricatorStandardCustomFieldInterface.php',
'PhabricatorStandardCustomFieldLink' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldLink.php',
'PhabricatorStandardCustomFieldPHIDs' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php',
'PhabricatorStandardCustomFieldRemarkup' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php',
'PhabricatorStandardCustomFieldSelect' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldSelect.php',
'PhabricatorStandardCustomFieldText' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php',
'PhabricatorStandardCustomFieldTokenizer' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldTokenizer.php',
'PhabricatorStandardCustomFieldUsers' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php',
'PhabricatorStandardPageView' => 'view/page/PhabricatorStandardPageView.php',
'PhabricatorStandardSelectCustomFieldDatasource' => 'infrastructure/customfield/datasource/PhabricatorStandardSelectCustomFieldDatasource.php',
'PhabricatorStandardTimelineEngine' => 'applications/transactions/engine/PhabricatorStandardTimelineEngine.php',
'PhabricatorStaticEditField' => 'applications/transactions/editfield/PhabricatorStaticEditField.php',
'PhabricatorStatusController' => 'applications/system/controller/PhabricatorStatusController.php',
'PhabricatorStatusUIExample' => 'applications/uiexample/examples/PhabricatorStatusUIExample.php',
'PhabricatorStorageFixtureScopeGuard' => 'infrastructure/testing/fixture/PhabricatorStorageFixtureScopeGuard.php',
'PhabricatorStorageManagementAPI' => 'infrastructure/storage/management/PhabricatorStorageManagementAPI.php',
'PhabricatorStorageManagementAdjustWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php',
'PhabricatorStorageManagementAnalyzeWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementAnalyzeWorkflow.php',
'PhabricatorStorageManagementDatabasesWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php',
'PhabricatorStorageManagementDestroyWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php',
'PhabricatorStorageManagementDumpWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php',
'PhabricatorStorageManagementOptimizeWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementOptimizeWorkflow.php',
'PhabricatorStorageManagementPartitionWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementPartitionWorkflow.php',
'PhabricatorStorageManagementProbeWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementProbeWorkflow.php',
'PhabricatorStorageManagementQuickstartWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php',
'PhabricatorStorageManagementRenamespaceWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementRenamespaceWorkflow.php',
'PhabricatorStorageManagementShellWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php',
'PhabricatorStorageManagementStatusWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementStatusWorkflow.php',
'PhabricatorStorageManagementUpgradeWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php',
'PhabricatorStorageManagementWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php',
'PhabricatorStoragePatch' => 'infrastructure/storage/management/PhabricatorStoragePatch.php',
'PhabricatorStorageSchemaSpec' => 'infrastructure/storage/schema/PhabricatorStorageSchemaSpec.php',
'PhabricatorStorageSetupCheck' => 'applications/config/check/PhabricatorStorageSetupCheck.php',
'PhabricatorStringConfigType' => 'applications/config/type/PhabricatorStringConfigType.php',
'PhabricatorStringExportField' => 'infrastructure/export/field/PhabricatorStringExportField.php',
'PhabricatorStringListConfigType' => 'applications/config/type/PhabricatorStringListConfigType.php',
'PhabricatorStringListEditField' => 'applications/transactions/editfield/PhabricatorStringListEditField.php',
'PhabricatorStringListExportField' => 'infrastructure/export/field/PhabricatorStringListExportField.php',
'PhabricatorStringMailStamp' => 'applications/metamta/stamp/PhabricatorStringMailStamp.php',
'PhabricatorStringSetting' => 'applications/settings/setting/PhabricatorStringSetting.php',
'PhabricatorSubmitEditField' => 'applications/transactions/editfield/PhabricatorSubmitEditField.php',
'PhabricatorSubscribableInterface' => 'applications/subscriptions/interface/PhabricatorSubscribableInterface.php',
'PhabricatorSubscribedToObjectEdgeType' => 'applications/transactions/edges/PhabricatorSubscribedToObjectEdgeType.php',
'PhabricatorSubscribersEditField' => 'applications/transactions/editfield/PhabricatorSubscribersEditField.php',
'PhabricatorSubscribersQuery' => 'applications/subscriptions/query/PhabricatorSubscribersQuery.php',
'PhabricatorSubscriptionTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorSubscriptionTriggerClock.php',
'PhabricatorSubscriptionsAddSelfHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsAddSelfHeraldAction.php',
'PhabricatorSubscriptionsAddSubscribersHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php',
'PhabricatorSubscriptionsApplication' => 'applications/subscriptions/application/PhabricatorSubscriptionsApplication.php',
'PhabricatorSubscriptionsCurtainExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsCurtainExtension.php',
'PhabricatorSubscriptionsEditController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php',
'PhabricatorSubscriptionsEditEngineExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php',
'PhabricatorSubscriptionsEditor' => 'applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php',
'PhabricatorSubscriptionsExportEngineExtension' => 'infrastructure/export/engine/PhabricatorSubscriptionsExportEngineExtension.php',
'PhabricatorSubscriptionsFulltextEngineExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsFulltextEngineExtension.php',
'PhabricatorSubscriptionsHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php',
'PhabricatorSubscriptionsListController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsListController.php',
'PhabricatorSubscriptionsMailEngineExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsMailEngineExtension.php',
'PhabricatorSubscriptionsMuteController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsMuteController.php',
'PhabricatorSubscriptionsRemoveSelfHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSelfHeraldAction.php',
'PhabricatorSubscriptionsRemoveSubscribersHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php',
'PhabricatorSubscriptionsSearchEngineAttachment' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsSearchEngineAttachment.php',
'PhabricatorSubscriptionsSearchEngineExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsSearchEngineExtension.php',
'PhabricatorSubscriptionsSubscribeEmailCommand' => 'applications/subscriptions/command/PhabricatorSubscriptionsSubscribeEmailCommand.php',
'PhabricatorSubscriptionsSubscribersPolicyRule' => 'applications/subscriptions/policyrule/PhabricatorSubscriptionsSubscribersPolicyRule.php',
'PhabricatorSubscriptionsTransactionController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsTransactionController.php',
'PhabricatorSubscriptionsUIEventListener' => 'applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php',
'PhabricatorSubscriptionsUnsubscribeEmailCommand' => 'applications/subscriptions/command/PhabricatorSubscriptionsUnsubscribeEmailCommand.php',
'PhabricatorSubtypeEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorSubtypeEditEngineExtension.php',
'PhabricatorSumChartFunction' => 'applications/fact/chart/PhabricatorSumChartFunction.php',
'PhabricatorSupportApplication' => 'applications/support/application/PhabricatorSupportApplication.php',
'PhabricatorSyntaxHighlighter' => 'infrastructure/markup/PhabricatorSyntaxHighlighter.php',
'PhabricatorSyntaxHighlightingConfigOptions' => 'applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php',
'PhabricatorSyntaxStyle' => 'infrastructure/syntax/PhabricatorSyntaxStyle.php',
'PhabricatorSystemAction' => 'applications/system/action/PhabricatorSystemAction.php',
'PhabricatorSystemActionEngine' => 'applications/system/engine/PhabricatorSystemActionEngine.php',
'PhabricatorSystemActionGarbageCollector' => 'applications/system/garbagecollector/PhabricatorSystemActionGarbageCollector.php',
'PhabricatorSystemActionLog' => 'applications/system/storage/PhabricatorSystemActionLog.php',
'PhabricatorSystemActionRateLimitException' => 'applications/system/exception/PhabricatorSystemActionRateLimitException.php',
'PhabricatorSystemApplication' => 'applications/system/application/PhabricatorSystemApplication.php',
'PhabricatorSystemDAO' => 'applications/system/storage/PhabricatorSystemDAO.php',
'PhabricatorSystemDebugUIEventListener' => 'applications/system/events/PhabricatorSystemDebugUIEventListener.php',
'PhabricatorSystemDestructionGarbageCollector' => 'applications/system/garbagecollector/PhabricatorSystemDestructionGarbageCollector.php',
'PhabricatorSystemDestructionLog' => 'applications/system/storage/PhabricatorSystemDestructionLog.php',
'PhabricatorSystemObjectController' => 'applications/system/controller/PhabricatorSystemObjectController.php',
'PhabricatorSystemReadOnlyController' => 'applications/system/controller/PhabricatorSystemReadOnlyController.php',
'PhabricatorSystemRemoveDestroyWorkflow' => 'applications/system/management/PhabricatorSystemRemoveDestroyWorkflow.php',
'PhabricatorSystemRemoveLogWorkflow' => 'applications/system/management/PhabricatorSystemRemoveLogWorkflow.php',
'PhabricatorSystemRemoveWorkflow' => 'applications/system/management/PhabricatorSystemRemoveWorkflow.php',
'PhabricatorSystemSelectEncodingController' => 'applications/system/controller/PhabricatorSystemSelectEncodingController.php',
'PhabricatorSystemSelectHighlightController' => 'applications/system/controller/PhabricatorSystemSelectHighlightController.php',
'PhabricatorSystemSelectViewAsController' => 'applications/system/controller/PhabricatorSystemSelectViewAsController.php',
'PhabricatorTOTPAuthFactor' => 'applications/auth/factor/PhabricatorTOTPAuthFactor.php',
'PhabricatorTOTPAuthFactorTestCase' => 'applications/auth/factor/__tests__/PhabricatorTOTPAuthFactorTestCase.php',
'PhabricatorTaskmasterDaemon' => 'infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php',
'PhabricatorTaskmasterDaemonModule' => 'infrastructure/daemon/workers/PhabricatorTaskmasterDaemonModule.php',
'PhabricatorTestApplication' => 'applications/base/controller/__tests__/PhabricatorTestApplication.php',
'PhabricatorTestCase' => 'infrastructure/testing/PhabricatorTestCase.php',
'PhabricatorTestController' => 'applications/base/controller/__tests__/PhabricatorTestController.php',
'PhabricatorTestDataGenerator' => 'applications/lipsum/generator/PhabricatorTestDataGenerator.php',
'PhabricatorTestNoCycleEdgeType' => 'applications/transactions/edges/PhabricatorTestNoCycleEdgeType.php',
'PhabricatorTestStorageEngine' => 'applications/files/engine/PhabricatorTestStorageEngine.php',
'PhabricatorTestWorker' => 'infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php',
'PhabricatorTextAreaEditField' => 'applications/transactions/editfield/PhabricatorTextAreaEditField.php',
'PhabricatorTextConfigType' => 'applications/config/type/PhabricatorTextConfigType.php',
'PhabricatorTextDocumentEngine' => 'applications/files/document/PhabricatorTextDocumentEngine.php',
'PhabricatorTextEditField' => 'applications/transactions/editfield/PhabricatorTextEditField.php',
'PhabricatorTextExportFormat' => 'infrastructure/export/format/PhabricatorTextExportFormat.php',
'PhabricatorTextListConfigType' => 'applications/config/type/PhabricatorTextListConfigType.php',
'PhabricatorTime' => 'infrastructure/time/PhabricatorTime.php',
'PhabricatorTimeFormatSetting' => 'applications/settings/setting/PhabricatorTimeFormatSetting.php',
'PhabricatorTimeGuard' => 'infrastructure/time/PhabricatorTimeGuard.php',
'PhabricatorTimeTestCase' => 'infrastructure/time/__tests__/PhabricatorTimeTestCase.php',
'PhabricatorTimelineEngine' => 'applications/transactions/engine/PhabricatorTimelineEngine.php',
'PhabricatorTimelineInterface' => 'applications/transactions/interface/PhabricatorTimelineInterface.php',
'PhabricatorTimezoneIgnoreOffsetSetting' => 'applications/settings/setting/PhabricatorTimezoneIgnoreOffsetSetting.php',
'PhabricatorTimezoneSetting' => 'applications/settings/setting/PhabricatorTimezoneSetting.php',
'PhabricatorTimezoneSetupCheck' => 'applications/config/check/PhabricatorTimezoneSetupCheck.php',
'PhabricatorTitleGlyphsSetting' => 'applications/settings/setting/PhabricatorTitleGlyphsSetting.php',
'PhabricatorToken' => 'applications/tokens/storage/PhabricatorToken.php',
'PhabricatorTokenController' => 'applications/tokens/controller/PhabricatorTokenController.php',
'PhabricatorTokenCount' => 'applications/tokens/storage/PhabricatorTokenCount.php',
'PhabricatorTokenCountQuery' => 'applications/tokens/query/PhabricatorTokenCountQuery.php',
'PhabricatorTokenDAO' => 'applications/tokens/storage/PhabricatorTokenDAO.php',
'PhabricatorTokenDestructionEngineExtension' => 'applications/tokens/engineextension/PhabricatorTokenDestructionEngineExtension.php',
'PhabricatorTokenGiveController' => 'applications/tokens/controller/PhabricatorTokenGiveController.php',
'PhabricatorTokenGiven' => 'applications/tokens/storage/PhabricatorTokenGiven.php',
'PhabricatorTokenGivenController' => 'applications/tokens/controller/PhabricatorTokenGivenController.php',
'PhabricatorTokenGivenEditor' => 'applications/tokens/editor/PhabricatorTokenGivenEditor.php',
'PhabricatorTokenGivenFeedStory' => 'applications/tokens/feed/PhabricatorTokenGivenFeedStory.php',
'PhabricatorTokenGivenQuery' => 'applications/tokens/query/PhabricatorTokenGivenQuery.php',
'PhabricatorTokenLeaderController' => 'applications/tokens/controller/PhabricatorTokenLeaderController.php',
'PhabricatorTokenQuery' => 'applications/tokens/query/PhabricatorTokenQuery.php',
'PhabricatorTokenReceiverInterface' => 'applications/tokens/interface/PhabricatorTokenReceiverInterface.php',
'PhabricatorTokenReceiverQuery' => 'applications/tokens/query/PhabricatorTokenReceiverQuery.php',
'PhabricatorTokenTokenPHIDType' => 'applications/tokens/phid/PhabricatorTokenTokenPHIDType.php',
'PhabricatorTokenUIEventListener' => 'applications/tokens/event/PhabricatorTokenUIEventListener.php',
'PhabricatorTokenizerEditField' => 'applications/transactions/editfield/PhabricatorTokenizerEditField.php',
'PhabricatorTokensApplication' => 'applications/tokens/application/PhabricatorTokensApplication.php',
'PhabricatorTokensCurtainExtension' => 'applications/tokens/engineextension/PhabricatorTokensCurtainExtension.php',
'PhabricatorTokensSettingsPanel' => 'applications/settings/panel/PhabricatorTokensSettingsPanel.php',
'PhabricatorTokensToken' => 'applications/tokens/storage/PhabricatorTokensToken.php',
'PhabricatorTransactionChange' => 'applications/transactions/data/PhabricatorTransactionChange.php',
'PhabricatorTransactionFactEngine' => 'applications/fact/engine/PhabricatorTransactionFactEngine.php',
'PhabricatorTransactionRemarkupChange' => 'applications/transactions/data/PhabricatorTransactionRemarkupChange.php',
'PhabricatorTransactionWarning' => 'applications/transactions/data/PhabricatorTransactionWarning.php',
'PhabricatorTransactions' => 'applications/transactions/constants/PhabricatorTransactions.php',
'PhabricatorTransactionsApplication' => 'applications/transactions/application/PhabricatorTransactionsApplication.php',
'PhabricatorTransactionsDestructionEngineExtension' => 'applications/transactions/engineextension/PhabricatorTransactionsDestructionEngineExtension.php',
'PhabricatorTransactionsFulltextEngineExtension' => 'applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php',
'PhabricatorTransactionsObjectTypeDatasource' => 'applications/transactions/typeahead/PhabricatorTransactionsObjectTypeDatasource.php',
'PhabricatorTransformedFile' => 'applications/files/storage/PhabricatorTransformedFile.php',
'PhabricatorTranslationSetting' => 'applications/settings/setting/PhabricatorTranslationSetting.php',
'PhabricatorTranslationsConfigOptions' => 'applications/config/option/PhabricatorTranslationsConfigOptions.php',
'PhabricatorTriggerAction' => 'infrastructure/daemon/workers/action/PhabricatorTriggerAction.php',
'PhabricatorTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorTriggerClock.php',
'PhabricatorTriggerClockTestCase' => 'infrastructure/daemon/workers/clock/__tests__/PhabricatorTriggerClockTestCase.php',
'PhabricatorTriggerDaemon' => 'infrastructure/daemon/workers/PhabricatorTriggerDaemon.php',
'PhabricatorTrivialTestCase' => 'infrastructure/testing/__tests__/PhabricatorTrivialTestCase.php',
'PhabricatorTwilioFuture' => 'applications/metamta/future/PhabricatorTwilioFuture.php',
'PhabricatorTwitchAuthProvider' => 'applications/auth/provider/PhabricatorTwitchAuthProvider.php',
'PhabricatorTwitterAuthProvider' => 'applications/auth/provider/PhabricatorTwitterAuthProvider.php',
'PhabricatorTypeaheadApplication' => 'applications/typeahead/application/PhabricatorTypeaheadApplication.php',
'PhabricatorTypeaheadCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php',
'PhabricatorTypeaheadDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php',
'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/PhabricatorTypeaheadDatasourceController.php',
'PhabricatorTypeaheadDatasourceTestCase' => 'applications/typeahead/datasource/__tests__/PhabricatorTypeaheadDatasourceTestCase.php',
'PhabricatorTypeaheadFunctionHelpController' => 'applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php',
'PhabricatorTypeaheadInvalidTokenException' => 'applications/typeahead/exception/PhabricatorTypeaheadInvalidTokenException.php',
'PhabricatorTypeaheadModularDatasourceController' => 'applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php',
'PhabricatorTypeaheadMonogramDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php',
'PhabricatorTypeaheadProxyDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadProxyDatasource.php',
'PhabricatorTypeaheadResult' => 'applications/typeahead/storage/PhabricatorTypeaheadResult.php',
'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadRuntimeCompositeDatasource.php',
'PhabricatorTypeaheadTestNumbersDatasource' => 'applications/typeahead/datasource/__tests__/PhabricatorTypeaheadTestNumbersDatasource.php',
'PhabricatorTypeaheadTokenView' => 'applications/typeahead/view/PhabricatorTypeaheadTokenView.php',
'PhabricatorUIConfigOptions' => 'applications/config/option/PhabricatorUIConfigOptions.php',
'PhabricatorUIExample' => 'applications/uiexample/examples/PhabricatorUIExample.php',
'PhabricatorUIExampleRenderController' => 'applications/uiexample/controller/PhabricatorUIExampleRenderController.php',
'PhabricatorUIExamplesApplication' => 'applications/uiexample/application/PhabricatorUIExamplesApplication.php',
'PhabricatorURIExportField' => 'infrastructure/export/field/PhabricatorURIExportField.php',
'PhabricatorUSEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php',
'PhabricatorUnifiedDiffsSetting' => 'applications/settings/setting/PhabricatorUnifiedDiffsSetting.php',
'PhabricatorUnitTestContentSource' => 'infrastructure/contentsource/PhabricatorUnitTestContentSource.php',
'PhabricatorUnitsTestCase' => 'view/__tests__/PhabricatorUnitsTestCase.php',
'PhabricatorUnknownContentSource' => 'infrastructure/contentsource/PhabricatorUnknownContentSource.php',
'PhabricatorUnlockEngine' => 'applications/system/engine/PhabricatorUnlockEngine.php',
'PhabricatorUnlockableInterface' => 'applications/system/interface/PhabricatorUnlockableInterface.php',
'PhabricatorUnsubscribedFromObjectEdgeType' => 'applications/transactions/edges/PhabricatorUnsubscribedFromObjectEdgeType.php',
'PhabricatorUser' => 'applications/people/storage/PhabricatorUser.php',
'PhabricatorUserApproveTransaction' => 'applications/people/xaction/PhabricatorUserApproveTransaction.php',
'PhabricatorUserBadgesCacheType' => 'applications/people/cache/PhabricatorUserBadgesCacheType.php',
'PhabricatorUserBlurbField' => 'applications/people/customfield/PhabricatorUserBlurbField.php',
'PhabricatorUserCache' => 'applications/people/storage/PhabricatorUserCache.php',
'PhabricatorUserCachePurger' => 'applications/cache/purger/PhabricatorUserCachePurger.php',
'PhabricatorUserCacheType' => 'applications/people/cache/PhabricatorUserCacheType.php',
'PhabricatorUserCardView' => 'applications/people/view/PhabricatorUserCardView.php',
'PhabricatorUserConfigOptions' => 'applications/people/config/PhabricatorUserConfigOptions.php',
'PhabricatorUserConfiguredCustomField' => 'applications/people/customfield/PhabricatorUserConfiguredCustomField.php',
'PhabricatorUserConfiguredCustomFieldStorage' => 'applications/people/storage/PhabricatorUserConfiguredCustomFieldStorage.php',
'PhabricatorUserCustomField' => 'applications/people/customfield/PhabricatorUserCustomField.php',
'PhabricatorUserCustomFieldNumericIndex' => 'applications/people/storage/PhabricatorUserCustomFieldNumericIndex.php',
'PhabricatorUserCustomFieldStringIndex' => 'applications/people/storage/PhabricatorUserCustomFieldStringIndex.php',
'PhabricatorUserDAO' => 'applications/people/storage/PhabricatorUserDAO.php',
'PhabricatorUserDisableTransaction' => 'applications/people/xaction/PhabricatorUserDisableTransaction.php',
'PhabricatorUserEditEngine' => 'applications/people/editor/PhabricatorUserEditEngine.php',
'PhabricatorUserEditor' => 'applications/people/editor/PhabricatorUserEditor.php',
'PhabricatorUserEditorTestCase' => 'applications/people/editor/__tests__/PhabricatorUserEditorTestCase.php',
'PhabricatorUserEmail' => 'applications/people/storage/PhabricatorUserEmail.php',
'PhabricatorUserEmailTestCase' => 'applications/people/storage/__tests__/PhabricatorUserEmailTestCase.php',
'PhabricatorUserEmpowerTransaction' => 'applications/people/xaction/PhabricatorUserEmpowerTransaction.php',
'PhabricatorUserFerretEngine' => 'applications/people/search/PhabricatorUserFerretEngine.php',
'PhabricatorUserFulltextEngine' => 'applications/people/search/PhabricatorUserFulltextEngine.php',
'PhabricatorUserIconField' => 'applications/people/customfield/PhabricatorUserIconField.php',
'PhabricatorUserLog' => 'applications/people/storage/PhabricatorUserLog.php',
'PhabricatorUserLogType' => 'applications/people/userlog/PhabricatorUserLogType.php',
'PhabricatorUserLogTypeDatasource' => 'applications/people/typeahead/PhabricatorUserLogTypeDatasource.php',
'PhabricatorUserLogView' => 'applications/people/view/PhabricatorUserLogView.php',
'PhabricatorUserMessageCountCacheType' => 'applications/people/cache/PhabricatorUserMessageCountCacheType.php',
'PhabricatorUserNotificationCountCacheType' => 'applications/people/cache/PhabricatorUserNotificationCountCacheType.php',
'PhabricatorUserNotifyTransaction' => 'applications/people/xaction/PhabricatorUserNotifyTransaction.php',
'PhabricatorUserPHIDResolver' => 'applications/phid/resolver/PhabricatorUserPHIDResolver.php',
'PhabricatorUserPreferences' => 'applications/settings/storage/PhabricatorUserPreferences.php',
'PhabricatorUserPreferencesCacheType' => 'applications/people/cache/PhabricatorUserPreferencesCacheType.php',
'PhabricatorUserPreferencesEditor' => 'applications/settings/editor/PhabricatorUserPreferencesEditor.php',
'PhabricatorUserPreferencesPHIDType' => 'applications/settings/phid/PhabricatorUserPreferencesPHIDType.php',
'PhabricatorUserPreferencesQuery' => 'applications/settings/query/PhabricatorUserPreferencesQuery.php',
'PhabricatorUserPreferencesSearchEngine' => 'applications/settings/query/PhabricatorUserPreferencesSearchEngine.php',
'PhabricatorUserPreferencesTransaction' => 'applications/settings/storage/PhabricatorUserPreferencesTransaction.php',
'PhabricatorUserPreferencesTransactionQuery' => 'applications/settings/query/PhabricatorUserPreferencesTransactionQuery.php',
'PhabricatorUserProfile' => 'applications/people/storage/PhabricatorUserProfile.php',
'PhabricatorUserProfileImageCacheType' => 'applications/people/cache/PhabricatorUserProfileImageCacheType.php',
'PhabricatorUserRealNameField' => 'applications/people/customfield/PhabricatorUserRealNameField.php',
'PhabricatorUserRolesField' => 'applications/people/customfield/PhabricatorUserRolesField.php',
'PhabricatorUserSchemaSpec' => 'applications/people/storage/PhabricatorUserSchemaSpec.php',
'PhabricatorUserSinceField' => 'applications/people/customfield/PhabricatorUserSinceField.php',
'PhabricatorUserStatusField' => 'applications/people/customfield/PhabricatorUserStatusField.php',
'PhabricatorUserTestCase' => 'applications/people/storage/__tests__/PhabricatorUserTestCase.php',
'PhabricatorUserTitleField' => 'applications/people/customfield/PhabricatorUserTitleField.php',
'PhabricatorUserTransaction' => 'applications/people/storage/PhabricatorUserTransaction.php',
'PhabricatorUserTransactionEditor' => 'applications/people/editor/PhabricatorUserTransactionEditor.php',
'PhabricatorUserTransactionType' => 'applications/people/xaction/PhabricatorUserTransactionType.php',
'PhabricatorUserUsernameTransaction' => 'applications/people/xaction/PhabricatorUserUsernameTransaction.php',
'PhabricatorUsersEditField' => 'applications/transactions/editfield/PhabricatorUsersEditField.php',
'PhabricatorUsersPolicyRule' => 'applications/people/policyrule/PhabricatorUsersPolicyRule.php',
'PhabricatorUsersSearchField' => 'applications/people/searchfield/PhabricatorUsersSearchField.php',
'PhabricatorVCSResponse' => 'applications/repository/response/PhabricatorVCSResponse.php',
'PhabricatorVerifyEmailUserLogType' => 'applications/people/userlog/PhabricatorVerifyEmailUserLogType.php',
'PhabricatorVersionedDraft' => 'applications/draft/storage/PhabricatorVersionedDraft.php',
'PhabricatorVeryWowEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php',
'PhabricatorVideoDocumentEngine' => 'applications/files/document/PhabricatorVideoDocumentEngine.php',
'PhabricatorViewerDatasource' => 'applications/people/typeahead/PhabricatorViewerDatasource.php',
'PhabricatorVoidDocumentEngine' => 'applications/files/document/PhabricatorVoidDocumentEngine.php',
'PhabricatorWatcherHasObjectEdgeType' => 'applications/transactions/edges/PhabricatorWatcherHasObjectEdgeType.php',
'PhabricatorWebContentSource' => 'infrastructure/contentsource/PhabricatorWebContentSource.php',
'PhabricatorWebServerSetupCheck' => 'applications/config/check/PhabricatorWebServerSetupCheck.php',
'PhabricatorWeekStartDaySetting' => 'applications/settings/setting/PhabricatorWeekStartDaySetting.php',
'PhabricatorWildConfigType' => 'applications/config/type/PhabricatorWildConfigType.php',
'PhabricatorWordPressAuthProvider' => 'applications/auth/provider/PhabricatorWordPressAuthProvider.php',
'PhabricatorWorkboardInterface' => 'applications/project/interface/PhabricatorWorkboardInterface.php',
'PhabricatorWorkboardViewState' => 'applications/project/state/PhabricatorWorkboardViewState.php',
'PhabricatorWorker' => 'infrastructure/daemon/workers/PhabricatorWorker.php',
'PhabricatorWorkerActiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php',
'PhabricatorWorkerActiveTaskQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerActiveTaskQuery.php',
'PhabricatorWorkerArchiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php',
'PhabricatorWorkerArchiveTaskQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerArchiveTaskQuery.php',
'PhabricatorWorkerBulkJob' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJob.php',
'PhabricatorWorkerBulkJobCreateWorker' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobCreateWorker.php',
'PhabricatorWorkerBulkJobEditor' => 'infrastructure/daemon/workers/editor/PhabricatorWorkerBulkJobEditor.php',
'PhabricatorWorkerBulkJobPHIDType' => 'infrastructure/daemon/workers/phid/PhabricatorWorkerBulkJobPHIDType.php',
'PhabricatorWorkerBulkJobQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobQuery.php',
'PhabricatorWorkerBulkJobSearchEngine' => 'infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobSearchEngine.php',
'PhabricatorWorkerBulkJobTaskWorker' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobTaskWorker.php',
'PhabricatorWorkerBulkJobTestCase' => 'infrastructure/daemon/workers/__tests__/PhabricatorWorkerBulkJobTestCase.php',
'PhabricatorWorkerBulkJobTransaction' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJobTransaction.php',
'PhabricatorWorkerBulkJobTransactionQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobTransactionQuery.php',
'PhabricatorWorkerBulkJobType' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobType.php',
'PhabricatorWorkerBulkJobWorker' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobWorker.php',
'PhabricatorWorkerBulkTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerBulkTask.php',
'PhabricatorWorkerDAO' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerDAO.php',
'PhabricatorWorkerDestructionEngineExtension' => 'infrastructure/daemon/workers/engineextension/PhabricatorWorkerDestructionEngineExtension.php',
'PhabricatorWorkerLeaseQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php',
'PhabricatorWorkerManagementCancelWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementCancelWorkflow.php',
'PhabricatorWorkerManagementDelayWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementDelayWorkflow.php',
'PhabricatorWorkerManagementExecuteWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementExecuteWorkflow.php',
'PhabricatorWorkerManagementFloodWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementFloodWorkflow.php',
'PhabricatorWorkerManagementFreeWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementFreeWorkflow.php',
'PhabricatorWorkerManagementPriorityWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementPriorityWorkflow.php',
'PhabricatorWorkerManagementRetryWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementRetryWorkflow.php',
'PhabricatorWorkerManagementWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php',
'PhabricatorWorkerPermanentFailureException' => 'infrastructure/daemon/workers/exception/PhabricatorWorkerPermanentFailureException.php',
'PhabricatorWorkerSchemaSpec' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerSchemaSpec.php',
'PhabricatorWorkerSingleBulkJobType' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerSingleBulkJobType.php',
'PhabricatorWorkerTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTask.php',
'PhabricatorWorkerTaskData' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTaskData.php',
'PhabricatorWorkerTaskDetailController' => 'applications/daemon/controller/PhabricatorWorkerTaskDetailController.php',
'PhabricatorWorkerTaskQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerTaskQuery.php',
'PhabricatorWorkerTestCase' => 'infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php',
'PhabricatorWorkerTrigger' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTrigger.php',
'PhabricatorWorkerTriggerEvent' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTriggerEvent.php',
'PhabricatorWorkerTriggerManagementFireWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php',
'PhabricatorWorkerTriggerManagementWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementWorkflow.php',
'PhabricatorWorkerTriggerPHIDType' => 'infrastructure/daemon/workers/phid/PhabricatorWorkerTriggerPHIDType.php',
'PhabricatorWorkerTriggerQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php',
'PhabricatorWorkerYieldException' => 'infrastructure/daemon/workers/exception/PhabricatorWorkerYieldException.php',
'PhabricatorWorkingCopyDiscoveryTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyDiscoveryTestCase.php',
'PhabricatorWorkingCopyPullTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyPullTestCase.php',
'PhabricatorWorkingCopyTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php',
'PhabricatorXHPASTDAO' => 'applications/phpast/storage/PhabricatorXHPASTDAO.php',
'PhabricatorXHPASTParseTree' => 'applications/phpast/storage/PhabricatorXHPASTParseTree.php',
'PhabricatorXHPASTViewController' => 'applications/phpast/controller/PhabricatorXHPASTViewController.php',
'PhabricatorXHPASTViewFrameController' => 'applications/phpast/controller/PhabricatorXHPASTViewFrameController.php',
'PhabricatorXHPASTViewFramesetController' => 'applications/phpast/controller/PhabricatorXHPASTViewFramesetController.php',
'PhabricatorXHPASTViewInputController' => 'applications/phpast/controller/PhabricatorXHPASTViewInputController.php',
'PhabricatorXHPASTViewPanelController' => 'applications/phpast/controller/PhabricatorXHPASTViewPanelController.php',
'PhabricatorXHPASTViewRunController' => 'applications/phpast/controller/PhabricatorXHPASTViewRunController.php',
'PhabricatorXHPASTViewStreamController' => 'applications/phpast/controller/PhabricatorXHPASTViewStreamController.php',
'PhabricatorXHPASTViewTreeController' => 'applications/phpast/controller/PhabricatorXHPASTViewTreeController.php',
'PhabricatorXHProfApplication' => 'applications/xhprof/application/PhabricatorXHProfApplication.php',
'PhabricatorXHProfController' => 'applications/xhprof/controller/PhabricatorXHProfController.php',
'PhabricatorXHProfDAO' => 'applications/xhprof/storage/PhabricatorXHProfDAO.php',
'PhabricatorXHProfDropController' => 'applications/xhprof/controller/PhabricatorXHProfDropController.php',
'PhabricatorXHProfProfileController' => 'applications/xhprof/controller/PhabricatorXHProfProfileController.php',
'PhabricatorXHProfProfileSymbolView' => 'applications/xhprof/view/PhabricatorXHProfProfileSymbolView.php',
'PhabricatorXHProfProfileTopLevelView' => 'applications/xhprof/view/PhabricatorXHProfProfileTopLevelView.php',
'PhabricatorXHProfProfileView' => 'applications/xhprof/view/PhabricatorXHProfProfileView.php',
'PhabricatorXHProfSample' => 'applications/xhprof/storage/PhabricatorXHProfSample.php',
'PhabricatorXHProfSampleListController' => 'applications/xhprof/controller/PhabricatorXHProfSampleListController.php',
'PhabricatorXHProfSampleQuery' => 'applications/xhprof/query/PhabricatorXHProfSampleQuery.php',
'PhabricatorXHProfSampleSearchEngine' => 'applications/xhprof/query/PhabricatorXHProfSampleSearchEngine.php',
'PhabricatorYoutubeRemarkupRule' => 'infrastructure/markup/rule/PhabricatorYoutubeRemarkupRule.php',
'PhabricatorZipSetupCheck' => 'applications/config/check/PhabricatorZipSetupCheck.php',
'Phame404Response' => 'applications/phame/site/Phame404Response.php',
'PhameBlog' => 'applications/phame/storage/PhameBlog.php',
'PhameBlog404Controller' => 'applications/phame/controller/blog/PhameBlog404Controller.php',
'PhameBlogArchiveController' => 'applications/phame/controller/blog/PhameBlogArchiveController.php',
'PhameBlogController' => 'applications/phame/controller/blog/PhameBlogController.php',
'PhameBlogCreateCapability' => 'applications/phame/capability/PhameBlogCreateCapability.php',
'PhameBlogDatasource' => 'applications/phame/typeahead/PhameBlogDatasource.php',
'PhameBlogDescriptionTransaction' => 'applications/phame/xaction/PhameBlogDescriptionTransaction.php',
'PhameBlogEditConduitAPIMethod' => 'applications/phame/conduit/PhameBlogEditConduitAPIMethod.php',
'PhameBlogEditController' => 'applications/phame/controller/blog/PhameBlogEditController.php',
'PhameBlogEditEngine' => 'applications/phame/editor/PhameBlogEditEngine.php',
'PhameBlogEditor' => 'applications/phame/editor/PhameBlogEditor.php',
'PhameBlogFeedController' => 'applications/phame/controller/blog/PhameBlogFeedController.php',
'PhameBlogFerretEngine' => 'applications/phame/search/PhameBlogFerretEngine.php',
'PhameBlogFullDomainTransaction' => 'applications/phame/xaction/PhameBlogFullDomainTransaction.php',
'PhameBlogFulltextEngine' => 'applications/phame/search/PhameBlogFulltextEngine.php',
'PhameBlogHeaderImageTransaction' => 'applications/phame/xaction/PhameBlogHeaderImageTransaction.php',
'PhameBlogHeaderPictureController' => 'applications/phame/controller/blog/PhameBlogHeaderPictureController.php',
'PhameBlogListController' => 'applications/phame/controller/blog/PhameBlogListController.php',
'PhameBlogListView' => 'applications/phame/view/PhameBlogListView.php',
'PhameBlogManageController' => 'applications/phame/controller/blog/PhameBlogManageController.php',
'PhameBlogNameTransaction' => 'applications/phame/xaction/PhameBlogNameTransaction.php',
'PhameBlogParentDomainTransaction' => 'applications/phame/xaction/PhameBlogParentDomainTransaction.php',
'PhameBlogParentSiteTransaction' => 'applications/phame/xaction/PhameBlogParentSiteTransaction.php',
'PhameBlogProfileImageTransaction' => 'applications/phame/xaction/PhameBlogProfileImageTransaction.php',
'PhameBlogProfilePictureController' => 'applications/phame/controller/blog/PhameBlogProfilePictureController.php',
'PhameBlogQuery' => 'applications/phame/query/PhameBlogQuery.php',
'PhameBlogReplyHandler' => 'applications/phame/mail/PhameBlogReplyHandler.php',
'PhameBlogSearchConduitAPIMethod' => 'applications/phame/conduit/PhameBlogSearchConduitAPIMethod.php',
'PhameBlogSearchEngine' => 'applications/phame/query/PhameBlogSearchEngine.php',
'PhameBlogSite' => 'applications/phame/site/PhameBlogSite.php',
'PhameBlogStatusTransaction' => 'applications/phame/xaction/PhameBlogStatusTransaction.php',
'PhameBlogSubtitleTransaction' => 'applications/phame/xaction/PhameBlogSubtitleTransaction.php',
'PhameBlogTransaction' => 'applications/phame/storage/PhameBlogTransaction.php',
'PhameBlogTransactionQuery' => 'applications/phame/query/PhameBlogTransactionQuery.php',
'PhameBlogTransactionType' => 'applications/phame/xaction/PhameBlogTransactionType.php',
'PhameBlogViewController' => 'applications/phame/controller/blog/PhameBlogViewController.php',
'PhameConstants' => 'applications/phame/constants/PhameConstants.php',
'PhameController' => 'applications/phame/controller/PhameController.php',
'PhameDAO' => 'applications/phame/storage/PhameDAO.php',
'PhameDescriptionView' => 'applications/phame/view/PhameDescriptionView.php',
'PhameDraftListView' => 'applications/phame/view/PhameDraftListView.php',
'PhameHomeController' => 'applications/phame/controller/PhameHomeController.php',
'PhameInheritBlogPolicyRule' => 'applications/phame/policyrule/PhameInheritBlogPolicyRule.php',
'PhameLiveController' => 'applications/phame/controller/PhameLiveController.php',
'PhameNextPostView' => 'applications/phame/view/PhameNextPostView.php',
'PhamePost' => 'applications/phame/storage/PhamePost.php',
'PhamePostArchiveController' => 'applications/phame/controller/post/PhamePostArchiveController.php',
'PhamePostBlogTransaction' => 'applications/phame/xaction/PhamePostBlogTransaction.php',
'PhamePostBodyTransaction' => 'applications/phame/xaction/PhamePostBodyTransaction.php',
'PhamePostController' => 'applications/phame/controller/post/PhamePostController.php',
'PhamePostEditConduitAPIMethod' => 'applications/phame/conduit/PhamePostEditConduitAPIMethod.php',
'PhamePostEditController' => 'applications/phame/controller/post/PhamePostEditController.php',
'PhamePostEditEngine' => 'applications/phame/editor/PhamePostEditEngine.php',
'PhamePostEditEngineLock' => 'applications/phame/editor/PhamePostEditEngineLock.php',
'PhamePostEditor' => 'applications/phame/editor/PhamePostEditor.php',
'PhamePostFerretEngine' => 'applications/phame/search/PhamePostFerretEngine.php',
'PhamePostFulltextEngine' => 'applications/phame/search/PhamePostFulltextEngine.php',
'PhamePostHeaderImageTransaction' => 'applications/phame/xaction/PhamePostHeaderImageTransaction.php',
'PhamePostHeaderPictureController' => 'applications/phame/controller/post/PhamePostHeaderPictureController.php',
'PhamePostHistoryController' => 'applications/phame/controller/post/PhamePostHistoryController.php',
'PhamePostListController' => 'applications/phame/controller/post/PhamePostListController.php',
'PhamePostListView' => 'applications/phame/view/PhamePostListView.php',
'PhamePostMailReceiver' => 'applications/phame/mail/PhamePostMailReceiver.php',
'PhamePostMoveController' => 'applications/phame/controller/post/PhamePostMoveController.php',
'PhamePostPublishController' => 'applications/phame/controller/post/PhamePostPublishController.php',
'PhamePostQuery' => 'applications/phame/query/PhamePostQuery.php',
'PhamePostRemarkupRule' => 'applications/phame/remarkup/PhamePostRemarkupRule.php',
'PhamePostReplyHandler' => 'applications/phame/mail/PhamePostReplyHandler.php',
'PhamePostSearchConduitAPIMethod' => 'applications/phame/conduit/PhamePostSearchConduitAPIMethod.php',
'PhamePostSearchEngine' => 'applications/phame/query/PhamePostSearchEngine.php',
'PhamePostSubtitleTransaction' => 'applications/phame/xaction/PhamePostSubtitleTransaction.php',
'PhamePostTitleTransaction' => 'applications/phame/xaction/PhamePostTitleTransaction.php',
'PhamePostTransaction' => 'applications/phame/storage/PhamePostTransaction.php',
'PhamePostTransactionComment' => 'applications/phame/storage/PhamePostTransactionComment.php',
'PhamePostTransactionQuery' => 'applications/phame/query/PhamePostTransactionQuery.php',
'PhamePostTransactionType' => 'applications/phame/xaction/PhamePostTransactionType.php',
'PhamePostViewController' => 'applications/phame/controller/post/PhamePostViewController.php',
'PhamePostVisibilityTransaction' => 'applications/phame/xaction/PhamePostVisibilityTransaction.php',
'PhameSchemaSpec' => 'applications/phame/storage/PhameSchemaSpec.php',
'PhameSite' => 'applications/phame/site/PhameSite.php',
'PhluxController' => 'applications/phlux/controller/PhluxController.php',
'PhluxDAO' => 'applications/phlux/storage/PhluxDAO.php',
'PhluxEditController' => 'applications/phlux/controller/PhluxEditController.php',
'PhluxListController' => 'applications/phlux/controller/PhluxListController.php',
'PhluxSchemaSpec' => 'applications/phlux/storage/PhluxSchemaSpec.php',
'PhluxTransaction' => 'applications/phlux/storage/PhluxTransaction.php',
'PhluxTransactionQuery' => 'applications/phlux/query/PhluxTransactionQuery.php',
'PhluxVariable' => 'applications/phlux/storage/PhluxVariable.php',
'PhluxVariableEditor' => 'applications/phlux/editor/PhluxVariableEditor.php',
'PhluxVariablePHIDType' => 'applications/phlux/phid/PhluxVariablePHIDType.php',
'PhluxVariableQuery' => 'applications/phlux/query/PhluxVariableQuery.php',
'PhluxViewController' => 'applications/phlux/controller/PhluxViewController.php',
'PholioController' => 'applications/pholio/controller/PholioController.php',
'PholioDAO' => 'applications/pholio/storage/PholioDAO.php',
'PholioDefaultEditCapability' => 'applications/pholio/capability/PholioDefaultEditCapability.php',
'PholioDefaultViewCapability' => 'applications/pholio/capability/PholioDefaultViewCapability.php',
'PholioImage' => 'applications/pholio/storage/PholioImage.php',
'PholioImageDescriptionTransaction' => 'applications/pholio/xaction/PholioImageDescriptionTransaction.php',
'PholioImageFileTransaction' => 'applications/pholio/xaction/PholioImageFileTransaction.php',
'PholioImageNameTransaction' => 'applications/pholio/xaction/PholioImageNameTransaction.php',
'PholioImagePHIDType' => 'applications/pholio/phid/PholioImagePHIDType.php',
'PholioImageQuery' => 'applications/pholio/query/PholioImageQuery.php',
'PholioImageReplaceTransaction' => 'applications/pholio/xaction/PholioImageReplaceTransaction.php',
'PholioImageSequenceTransaction' => 'applications/pholio/xaction/PholioImageSequenceTransaction.php',
'PholioImageTransactionType' => 'applications/pholio/xaction/PholioImageTransactionType.php',
'PholioImageUploadController' => 'applications/pholio/controller/PholioImageUploadController.php',
'PholioInlineController' => 'applications/pholio/controller/PholioInlineController.php',
'PholioInlineListController' => 'applications/pholio/controller/PholioInlineListController.php',
'PholioMock' => 'applications/pholio/storage/PholioMock.php',
'PholioMockArchiveController' => 'applications/pholio/controller/PholioMockArchiveController.php',
'PholioMockAuthorHeraldField' => 'applications/pholio/herald/PholioMockAuthorHeraldField.php',
'PholioMockCommentController' => 'applications/pholio/controller/PholioMockCommentController.php',
'PholioMockDescriptionHeraldField' => 'applications/pholio/herald/PholioMockDescriptionHeraldField.php',
'PholioMockDescriptionTransaction' => 'applications/pholio/xaction/PholioMockDescriptionTransaction.php',
'PholioMockEditController' => 'applications/pholio/controller/PholioMockEditController.php',
'PholioMockEditor' => 'applications/pholio/editor/PholioMockEditor.php',
'PholioMockEmbedView' => 'applications/pholio/view/PholioMockEmbedView.php',
'PholioMockFerretEngine' => 'applications/pholio/search/PholioMockFerretEngine.php',
'PholioMockFulltextEngine' => 'applications/pholio/search/PholioMockFulltextEngine.php',
'PholioMockHasTaskEdgeType' => 'applications/pholio/edge/PholioMockHasTaskEdgeType.php',
'PholioMockHasTaskRelationship' => 'applications/pholio/relationships/PholioMockHasTaskRelationship.php',
'PholioMockHeraldField' => 'applications/pholio/herald/PholioMockHeraldField.php',
'PholioMockHeraldFieldGroup' => 'applications/pholio/herald/PholioMockHeraldFieldGroup.php',
'PholioMockImagesView' => 'applications/pholio/view/PholioMockImagesView.php',
'PholioMockInlineTransaction' => 'applications/pholio/xaction/PholioMockInlineTransaction.php',
'PholioMockListController' => 'applications/pholio/controller/PholioMockListController.php',
'PholioMockMailReceiver' => 'applications/pholio/mail/PholioMockMailReceiver.php',
'PholioMockNameHeraldField' => 'applications/pholio/herald/PholioMockNameHeraldField.php',
'PholioMockNameTransaction' => 'applications/pholio/xaction/PholioMockNameTransaction.php',
'PholioMockPHIDType' => 'applications/pholio/phid/PholioMockPHIDType.php',
'PholioMockQuery' => 'applications/pholio/query/PholioMockQuery.php',
'PholioMockRelationship' => 'applications/pholio/relationships/PholioMockRelationship.php',
'PholioMockRelationshipSource' => 'applications/search/relationship/PholioMockRelationshipSource.php',
'PholioMockSearchEngine' => 'applications/pholio/query/PholioMockSearchEngine.php',
'PholioMockStatusTransaction' => 'applications/pholio/xaction/PholioMockStatusTransaction.php',
'PholioMockThumbGridView' => 'applications/pholio/view/PholioMockThumbGridView.php',
'PholioMockTimelineEngine' => 'applications/pholio/engine/PholioMockTimelineEngine.php',
'PholioMockTransactionType' => 'applications/pholio/xaction/PholioMockTransactionType.php',
'PholioMockViewController' => 'applications/pholio/controller/PholioMockViewController.php',
'PholioRemarkupRule' => 'applications/pholio/remarkup/PholioRemarkupRule.php',
'PholioReplyHandler' => 'applications/pholio/mail/PholioReplyHandler.php',
'PholioSchemaSpec' => 'applications/pholio/storage/PholioSchemaSpec.php',
'PholioTransaction' => 'applications/pholio/storage/PholioTransaction.php',
'PholioTransactionComment' => 'applications/pholio/storage/PholioTransactionComment.php',
'PholioTransactionQuery' => 'applications/pholio/query/PholioTransactionQuery.php',
'PholioTransactionType' => 'applications/pholio/xaction/PholioTransactionType.php',
'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php',
'PholioUploadedImageView' => 'applications/pholio/view/PholioUploadedImageView.php',
'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php',
'PhortuneAccountAddManagerController' => 'applications/phortune/controller/account/PhortuneAccountAddManagerController.php',
'PhortuneAccountBillingAddressTransaction' => 'applications/phortune/xaction/PhortuneAccountBillingAddressTransaction.php',
'PhortuneAccountBillingNameTransaction' => 'applications/phortune/xaction/PhortuneAccountBillingNameTransaction.php',
'PhortuneAccountChargeListController' => 'applications/phortune/controller/account/PhortuneAccountChargeListController.php',
'PhortuneAccountChargesController' => 'applications/phortune/controller/account/PhortuneAccountChargesController.php',
'PhortuneAccountController' => 'applications/phortune/controller/account/PhortuneAccountController.php',
'PhortuneAccountDetailsController' => 'applications/phortune/controller/account/PhortuneAccountDetailsController.php',
'PhortuneAccountEditController' => 'applications/phortune/controller/account/PhortuneAccountEditController.php',
'PhortuneAccountEditEngine' => 'applications/phortune/editor/PhortuneAccountEditEngine.php',
'PhortuneAccountEditor' => 'applications/phortune/editor/PhortuneAccountEditor.php',
'PhortuneAccountEmail' => 'applications/phortune/storage/PhortuneAccountEmail.php',
'PhortuneAccountEmailAddressTransaction' => 'applications/phortune/xaction/PhortuneAccountEmailAddressTransaction.php',
'PhortuneAccountEmailAddressesController' => 'applications/phortune/controller/account/PhortuneAccountEmailAddressesController.php',
'PhortuneAccountEmailEditController' => 'applications/phortune/controller/account/PhortuneAccountEmailEditController.php',
'PhortuneAccountEmailEditEngine' => 'applications/phortune/editor/PhortuneAccountEmailEditEngine.php',
'PhortuneAccountEmailEditor' => 'applications/phortune/editor/PhortuneAccountEmailEditor.php',
'PhortuneAccountEmailPHIDType' => 'applications/phortune/phid/PhortuneAccountEmailPHIDType.php',
'PhortuneAccountEmailQuery' => 'applications/phortune/query/PhortuneAccountEmailQuery.php',
'PhortuneAccountEmailRotateController' => 'applications/phortune/controller/account/PhortuneAccountEmailRotateController.php',
'PhortuneAccountEmailRotateTransaction' => 'applications/phortune/xaction/PhortuneAccountEmailRotateTransaction.php',
'PhortuneAccountEmailStatus' => 'applications/phortune/constants/PhortuneAccountEmailStatus.php',
'PhortuneAccountEmailStatusController' => 'applications/phortune/controller/account/PhortuneAccountEmailStatusController.php',
'PhortuneAccountEmailStatusTransaction' => 'applications/phortune/xaction/PhortuneAccountEmailStatusTransaction.php',
'PhortuneAccountEmailTransaction' => 'applications/phortune/storage/PhortuneAccountEmailTransaction.php',
'PhortuneAccountEmailTransactionQuery' => 'applications/phortune/query/PhortuneAccountEmailTransactionQuery.php',
'PhortuneAccountEmailTransactionType' => 'applications/phortune/xaction/PhortuneAccountEmailTransactionType.php',
'PhortuneAccountEmailViewController' => 'applications/phortune/controller/account/PhortuneAccountEmailViewController.php',
'PhortuneAccountHasMemberEdgeType' => 'applications/phortune/edge/PhortuneAccountHasMemberEdgeType.php',
'PhortuneAccountHasMerchantEdgeType' => 'applications/phortune/edge/PhortuneAccountHasMerchantEdgeType.php',
'PhortuneAccountListController' => 'applications/phortune/controller/account/PhortuneAccountListController.php',
'PhortuneAccountManagersController' => 'applications/phortune/controller/account/PhortuneAccountManagersController.php',
'PhortuneAccountNameTransaction' => 'applications/phortune/xaction/PhortuneAccountNameTransaction.php',
'PhortuneAccountOrderListController' => 'applications/phortune/controller/account/PhortuneAccountOrderListController.php',
'PhortuneAccountOrdersController' => 'applications/phortune/controller/account/PhortuneAccountOrdersController.php',
'PhortuneAccountOverviewController' => 'applications/phortune/controller/account/PhortuneAccountOverviewController.php',
'PhortuneAccountPHIDType' => 'applications/phortune/phid/PhortuneAccountPHIDType.php',
'PhortuneAccountPaymentMethodController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodController.php',
'PhortuneAccountPaymentMethodViewController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodViewController.php',
'PhortuneAccountProfileController' => 'applications/phortune/controller/account/PhortuneAccountProfileController.php',
'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php',
'PhortuneAccountSubscriptionAutopayController' => 'applications/phortune/controller/account/PhortuneAccountSubscriptionAutopayController.php',
'PhortuneAccountSubscriptionController' => 'applications/phortune/controller/account/PhortuneAccountSubscriptionController.php',
'PhortuneAccountSubscriptionViewController' => 'applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php',
'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php',
'PhortuneAccountTransactionQuery' => 'applications/phortune/query/PhortuneAccountTransactionQuery.php',
'PhortuneAccountTransactionType' => 'applications/phortune/xaction/PhortuneAccountTransactionType.php',
'PhortuneAdHocCart' => 'applications/phortune/cart/PhortuneAdHocCart.php',
'PhortuneAdHocProduct' => 'applications/phortune/product/PhortuneAdHocProduct.php',
'PhortuneAddPaymentMethodAction' => 'applications/phortune/action/PhortuneAddPaymentMethodAction.php',
'PhortuneCart' => 'applications/phortune/storage/PhortuneCart.php',
'PhortuneCartAcceptController' => 'applications/phortune/controller/cart/PhortuneCartAcceptController.php',
'PhortuneCartCancelController' => 'applications/phortune/controller/cart/PhortuneCartCancelController.php',
'PhortuneCartCheckoutController' => 'applications/phortune/controller/cart/PhortuneCartCheckoutController.php',
'PhortuneCartController' => 'applications/phortune/controller/cart/PhortuneCartController.php',
'PhortuneCartEditor' => 'applications/phortune/editor/PhortuneCartEditor.php',
'PhortuneCartImplementation' => 'applications/phortune/cart/PhortuneCartImplementation.php',
'PhortuneCartPHIDType' => 'applications/phortune/phid/PhortuneCartPHIDType.php',
'PhortuneCartQuery' => 'applications/phortune/query/PhortuneCartQuery.php',
'PhortuneCartReplyHandler' => 'applications/phortune/mail/PhortuneCartReplyHandler.php',
'PhortuneCartSearchEngine' => 'applications/phortune/query/PhortuneCartSearchEngine.php',
'PhortuneCartTransaction' => 'applications/phortune/storage/PhortuneCartTransaction.php',
'PhortuneCartTransactionQuery' => 'applications/phortune/query/PhortuneCartTransactionQuery.php',
'PhortuneCartUpdateController' => 'applications/phortune/controller/cart/PhortuneCartUpdateController.php',
'PhortuneCartViewController' => 'applications/phortune/controller/cart/PhortuneCartViewController.php',
'PhortuneCartVoidController' => 'applications/phortune/controller/cart/PhortuneCartVoidController.php',
'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php',
'PhortuneChargePHIDType' => 'applications/phortune/phid/PhortuneChargePHIDType.php',
'PhortuneChargeQuery' => 'applications/phortune/query/PhortuneChargeQuery.php',
'PhortuneChargeSearchEngine' => 'applications/phortune/query/PhortuneChargeSearchEngine.php',
'PhortuneChargeTableView' => 'applications/phortune/view/PhortuneChargeTableView.php',
'PhortuneConstants' => 'applications/phortune/constants/PhortuneConstants.php',
'PhortuneController' => 'applications/phortune/controller/PhortuneController.php',
'PhortuneCreditCardForm' => 'applications/phortune/view/PhortuneCreditCardForm.php',
'PhortuneCurrency' => 'applications/phortune/currency/PhortuneCurrency.php',
'PhortuneCurrencySerializer' => 'applications/phortune/currency/PhortuneCurrencySerializer.php',
'PhortuneCurrencyTestCase' => 'applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php',
'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php',
'PhortuneDisplayException' => 'applications/phortune/exception/PhortuneDisplayException.php',
'PhortuneErrCode' => 'applications/phortune/constants/PhortuneErrCode.php',
'PhortuneExternalController' => 'applications/phortune/controller/external/PhortuneExternalController.php',
'PhortuneExternalOrderController' => 'applications/phortune/controller/external/PhortuneExternalOrderController.php',
'PhortuneExternalOverviewController' => 'applications/phortune/controller/external/PhortuneExternalOverviewController.php',
'PhortuneExternalUnsubscribeController' => 'applications/phortune/controller/external/PhortuneExternalUnsubscribeController.php',
'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php',
'PhortuneMemberHasAccountEdgeType' => 'applications/phortune/edge/PhortuneMemberHasAccountEdgeType.php',
'PhortuneMemberHasMerchantEdgeType' => 'applications/phortune/edge/PhortuneMemberHasMerchantEdgeType.php',
'PhortuneMerchant' => 'applications/phortune/storage/PhortuneMerchant.php',
'PhortuneMerchantAddManagerController' => 'applications/phortune/controller/merchant/PhortuneMerchantAddManagerController.php',
'PhortuneMerchantCapability' => 'applications/phortune/capability/PhortuneMerchantCapability.php',
'PhortuneMerchantContactInfoTransaction' => 'applications/phortune/xaction/PhortuneMerchantContactInfoTransaction.php',
'PhortuneMerchantController' => 'applications/phortune/controller/merchant/PhortuneMerchantController.php',
'PhortuneMerchantDescriptionTransaction' => 'applications/phortune/xaction/PhortuneMerchantDescriptionTransaction.php',
'PhortuneMerchantDetailsController' => 'applications/phortune/controller/merchant/PhortuneMerchantDetailsController.php',
'PhortuneMerchantEditController' => 'applications/phortune/controller/merchant/PhortuneMerchantEditController.php',
'PhortuneMerchantEditEngine' => 'applications/phortune/editor/PhortuneMerchantEditEngine.php',
'PhortuneMerchantEditor' => 'applications/phortune/editor/PhortuneMerchantEditor.php',
'PhortuneMerchantHasAccountEdgeType' => 'applications/phortune/edge/PhortuneMerchantHasAccountEdgeType.php',
'PhortuneMerchantHasMemberEdgeType' => 'applications/phortune/edge/PhortuneMerchantHasMemberEdgeType.php',
'PhortuneMerchantInvoiceCreateController' => 'applications/phortune/controller/merchant/PhortuneMerchantInvoiceCreateController.php',
'PhortuneMerchantInvoiceEmailTransaction' => 'applications/phortune/xaction/PhortuneMerchantInvoiceEmailTransaction.php',
'PhortuneMerchantInvoiceFooterTransaction' => 'applications/phortune/xaction/PhortuneMerchantInvoiceFooterTransaction.php',
'PhortuneMerchantListController' => 'applications/phortune/controller/merchant/PhortuneMerchantListController.php',
'PhortuneMerchantManagersController' => 'applications/phortune/controller/merchant/PhortuneMerchantManagersController.php',
'PhortuneMerchantNameTransaction' => 'applications/phortune/xaction/PhortuneMerchantNameTransaction.php',
'PhortuneMerchantOrderListController' => 'applications/phortune/controller/merchant/PhortuneMerchantOrderListController.php',
'PhortuneMerchantOrdersController' => 'applications/phortune/controller/merchant/PhortuneMerchantOrdersController.php',
'PhortuneMerchantOverviewController' => 'applications/phortune/controller/merchant/PhortuneMerchantOverviewController.php',
'PhortuneMerchantPHIDType' => 'applications/phortune/phid/PhortuneMerchantPHIDType.php',
'PhortuneMerchantPictureController' => 'applications/phortune/controller/merchant/PhortuneMerchantPictureController.php',
'PhortuneMerchantPictureTransaction' => 'applications/phortune/xaction/PhortuneMerchantPictureTransaction.php',
'PhortuneMerchantProfileController' => 'applications/phortune/controller/merchant/PhortuneMerchantProfileController.php',
'PhortuneMerchantProviderDisableController' => 'applications/phortune/controller/merchant/PhortuneMerchantProviderDisableController.php',
'PhortuneMerchantProviderEditController' => 'applications/phortune/controller/merchant/PhortuneMerchantProviderEditController.php',
'PhortuneMerchantProviderViewController' => 'applications/phortune/controller/merchant/PhortuneMerchantProviderViewController.php',
'PhortuneMerchantProvidersController' => 'applications/phortune/controller/merchant/PhortuneMerchantProvidersController.php',
'PhortuneMerchantQuery' => 'applications/phortune/query/PhortuneMerchantQuery.php',
'PhortuneMerchantSearchEngine' => 'applications/phortune/query/PhortuneMerchantSearchEngine.php',
'PhortuneMerchantSubscriptionListController' => 'applications/phortune/controller/merchant/PhortuneMerchantSubscriptionListController.php',
'PhortuneMerchantSubscriptionsController' => 'applications/phortune/controller/merchant/PhortuneMerchantSubscriptionsController.php',
'PhortuneMerchantTransaction' => 'applications/phortune/storage/PhortuneMerchantTransaction.php',
'PhortuneMerchantTransactionQuery' => 'applications/phortune/query/PhortuneMerchantTransactionQuery.php',
'PhortuneMerchantTransactionType' => 'applications/phortune/xaction/PhortuneMerchantTransactionType.php',
'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php',
'PhortuneOrderDescriptionView' => 'applications/phortune/view/PhortuneOrderDescriptionView.php',
'PhortuneOrderItemsView' => 'applications/phortune/view/PhortuneOrderItemsView.php',
'PhortuneOrderSummaryView' => 'applications/phortune/view/PhortuneOrderSummaryView.php',
'PhortuneOrderTableView' => 'applications/phortune/view/PhortuneOrderTableView.php',
'PhortuneOrderView' => 'applications/phortune/view/PhortuneOrderView.php',
'PhortunePayPalPaymentProvider' => 'applications/phortune/provider/PhortunePayPalPaymentProvider.php',
'PhortunePaymentMethod' => 'applications/phortune/storage/PhortunePaymentMethod.php',
'PhortunePaymentMethodCreateController' => 'applications/phortune/controller/paymentmethod/PhortunePaymentMethodCreateController.php',
'PhortunePaymentMethodDisableController' => 'applications/phortune/controller/paymentmethod/PhortunePaymentMethodDisableController.php',
'PhortunePaymentMethodEditController' => 'applications/phortune/controller/paymentmethod/PhortunePaymentMethodEditController.php',
'PhortunePaymentMethodEditor' => 'applications/phortune/editor/PhortunePaymentMethodEditor.php',
'PhortunePaymentMethodNameTransaction' => 'applications/phortune/xaction/paymentmethod/PhortunePaymentMethodNameTransaction.php',
'PhortunePaymentMethodPHIDType' => 'applications/phortune/phid/PhortunePaymentMethodPHIDType.php',
'PhortunePaymentMethodPolicyCodex' => 'applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php',
'PhortunePaymentMethodQuery' => 'applications/phortune/query/PhortunePaymentMethodQuery.php',
'PhortunePaymentMethodStatusTransaction' => 'applications/phortune/xaction/paymentmethod/PhortunePaymentMethodStatusTransaction.php',
'PhortunePaymentMethodTransaction' => 'applications/phortune/storage/PhortunePaymentMethodTransaction.php',
'PhortunePaymentMethodTransactionQuery' => 'applications/phortune/query/PhortunePaymentMethodTransactionQuery.php',
'PhortunePaymentMethodTransactionType' => 'applications/phortune/xaction/paymentmethod/PhortunePaymentMethodTransactionType.php',
'PhortunePaymentProvider' => 'applications/phortune/provider/PhortunePaymentProvider.php',
'PhortunePaymentProviderConfig' => 'applications/phortune/storage/PhortunePaymentProviderConfig.php',
'PhortunePaymentProviderConfigEditor' => 'applications/phortune/editor/PhortunePaymentProviderConfigEditor.php',
'PhortunePaymentProviderConfigQuery' => 'applications/phortune/query/PhortunePaymentProviderConfigQuery.php',
'PhortunePaymentProviderConfigTransaction' => 'applications/phortune/storage/PhortunePaymentProviderConfigTransaction.php',
'PhortunePaymentProviderConfigTransactionQuery' => 'applications/phortune/query/PhortunePaymentProviderConfigTransactionQuery.php',
'PhortunePaymentProviderPHIDType' => 'applications/phortune/phid/PhortunePaymentProviderPHIDType.php',
'PhortunePaymentProviderTestCase' => 'applications/phortune/provider/__tests__/PhortunePaymentProviderTestCase.php',
'PhortuneProduct' => 'applications/phortune/storage/PhortuneProduct.php',
'PhortuneProductImplementation' => 'applications/phortune/product/PhortuneProductImplementation.php',
'PhortuneProductListController' => 'applications/phortune/controller/product/PhortuneProductListController.php',
'PhortuneProductPHIDType' => 'applications/phortune/phid/PhortuneProductPHIDType.php',
'PhortuneProductQuery' => 'applications/phortune/query/PhortuneProductQuery.php',
'PhortuneProductViewController' => 'applications/phortune/controller/product/PhortuneProductViewController.php',
'PhortuneProviderActionController' => 'applications/phortune/controller/provider/PhortuneProviderActionController.php',
'PhortunePurchase' => 'applications/phortune/storage/PhortunePurchase.php',
'PhortunePurchasePHIDType' => 'applications/phortune/phid/PhortunePurchasePHIDType.php',
'PhortunePurchaseQuery' => 'applications/phortune/query/PhortunePurchaseQuery.php',
'PhortuneSchemaSpec' => 'applications/phortune/storage/PhortuneSchemaSpec.php',
'PhortuneStripePaymentProvider' => 'applications/phortune/provider/PhortuneStripePaymentProvider.php',
'PhortuneSubscription' => 'applications/phortune/storage/PhortuneSubscription.php',
'PhortuneSubscriptionAutopayTransaction' => 'applications/phortune/xaction/subscription/PhortuneSubscriptionAutopayTransaction.php',
'PhortuneSubscriptionCart' => 'applications/phortune/cart/PhortuneSubscriptionCart.php',
'PhortuneSubscriptionEditor' => 'applications/phortune/editor/PhortuneSubscriptionEditor.php',
'PhortuneSubscriptionImplementation' => 'applications/phortune/subscription/PhortuneSubscriptionImplementation.php',
'PhortuneSubscriptionPHIDType' => 'applications/phortune/phid/PhortuneSubscriptionPHIDType.php',
'PhortuneSubscriptionPolicyCodex' => 'applications/phortune/codex/PhortuneSubscriptionPolicyCodex.php',
'PhortuneSubscriptionProduct' => 'applications/phortune/product/PhortuneSubscriptionProduct.php',
'PhortuneSubscriptionQuery' => 'applications/phortune/query/PhortuneSubscriptionQuery.php',
'PhortuneSubscriptionSearchEngine' => 'applications/phortune/query/PhortuneSubscriptionSearchEngine.php',
'PhortuneSubscriptionTableView' => 'applications/phortune/view/PhortuneSubscriptionTableView.php',
'PhortuneSubscriptionTransaction' => 'applications/phortune/storage/PhortuneSubscriptionTransaction.php',
'PhortuneSubscriptionTransactionQuery' => 'applications/phortune/query/PhortuneSubscriptionTransactionQuery.php',
'PhortuneSubscriptionTransactionType' => 'applications/phortune/xaction/subscription/PhortuneSubscriptionTransactionType.php',
'PhortuneSubscriptionWorker' => 'applications/phortune/worker/PhortuneSubscriptionWorker.php',
'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php',
- 'PhragmentBrowseController' => 'applications/phragment/controller/PhragmentBrowseController.php',
- 'PhragmentCanCreateCapability' => 'applications/phragment/capability/PhragmentCanCreateCapability.php',
- 'PhragmentConduitAPIMethod' => 'applications/phragment/conduit/PhragmentConduitAPIMethod.php',
- 'PhragmentController' => 'applications/phragment/controller/PhragmentController.php',
- 'PhragmentCreateController' => 'applications/phragment/controller/PhragmentCreateController.php',
- 'PhragmentDAO' => 'applications/phragment/storage/PhragmentDAO.php',
- 'PhragmentFragment' => 'applications/phragment/storage/PhragmentFragment.php',
- 'PhragmentFragmentPHIDType' => 'applications/phragment/phid/PhragmentFragmentPHIDType.php',
- 'PhragmentFragmentQuery' => 'applications/phragment/query/PhragmentFragmentQuery.php',
- 'PhragmentFragmentVersion' => 'applications/phragment/storage/PhragmentFragmentVersion.php',
- 'PhragmentFragmentVersionPHIDType' => 'applications/phragment/phid/PhragmentFragmentVersionPHIDType.php',
- 'PhragmentFragmentVersionQuery' => 'applications/phragment/query/PhragmentFragmentVersionQuery.php',
- 'PhragmentGetPatchConduitAPIMethod' => 'applications/phragment/conduit/PhragmentGetPatchConduitAPIMethod.php',
- 'PhragmentHistoryController' => 'applications/phragment/controller/PhragmentHistoryController.php',
- 'PhragmentPatchController' => 'applications/phragment/controller/PhragmentPatchController.php',
- 'PhragmentPatchUtil' => 'applications/phragment/util/PhragmentPatchUtil.php',
- 'PhragmentPolicyController' => 'applications/phragment/controller/PhragmentPolicyController.php',
- 'PhragmentQueryFragmentsConduitAPIMethod' => 'applications/phragment/conduit/PhragmentQueryFragmentsConduitAPIMethod.php',
- 'PhragmentRevertController' => 'applications/phragment/controller/PhragmentRevertController.php',
- 'PhragmentSchemaSpec' => 'applications/phragment/storage/PhragmentSchemaSpec.php',
- 'PhragmentSnapshot' => 'applications/phragment/storage/PhragmentSnapshot.php',
- 'PhragmentSnapshotChild' => 'applications/phragment/storage/PhragmentSnapshotChild.php',
- 'PhragmentSnapshotChildQuery' => 'applications/phragment/query/PhragmentSnapshotChildQuery.php',
- 'PhragmentSnapshotCreateController' => 'applications/phragment/controller/PhragmentSnapshotCreateController.php',
- 'PhragmentSnapshotDeleteController' => 'applications/phragment/controller/PhragmentSnapshotDeleteController.php',
- 'PhragmentSnapshotPHIDType' => 'applications/phragment/phid/PhragmentSnapshotPHIDType.php',
- 'PhragmentSnapshotPromoteController' => 'applications/phragment/controller/PhragmentSnapshotPromoteController.php',
- 'PhragmentSnapshotQuery' => 'applications/phragment/query/PhragmentSnapshotQuery.php',
- 'PhragmentSnapshotViewController' => 'applications/phragment/controller/PhragmentSnapshotViewController.php',
- 'PhragmentUpdateController' => 'applications/phragment/controller/PhragmentUpdateController.php',
- 'PhragmentVersionController' => 'applications/phragment/controller/PhragmentVersionController.php',
- 'PhragmentZIPController' => 'applications/phragment/controller/PhragmentZIPController.php',
'PhrequentConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentConduitAPIMethod.php',
'PhrequentController' => 'applications/phrequent/controller/PhrequentController.php',
'PhrequentCurtainExtension' => 'applications/phrequent/engineextension/PhrequentCurtainExtension.php',
'PhrequentDAO' => 'applications/phrequent/storage/PhrequentDAO.php',
'PhrequentListController' => 'applications/phrequent/controller/PhrequentListController.php',
'PhrequentPopConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentPopConduitAPIMethod.php',
'PhrequentPushConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentPushConduitAPIMethod.php',
'PhrequentSearchEngine' => 'applications/phrequent/query/PhrequentSearchEngine.php',
'PhrequentTimeBlock' => 'applications/phrequent/storage/PhrequentTimeBlock.php',
'PhrequentTimeBlockTestCase' => 'applications/phrequent/storage/__tests__/PhrequentTimeBlockTestCase.php',
'PhrequentTimeSlices' => 'applications/phrequent/storage/PhrequentTimeSlices.php',
'PhrequentTrackController' => 'applications/phrequent/controller/PhrequentTrackController.php',
'PhrequentTrackableInterface' => 'applications/phrequent/interface/PhrequentTrackableInterface.php',
'PhrequentTrackingConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentTrackingConduitAPIMethod.php',
'PhrequentTrackingEditor' => 'applications/phrequent/editor/PhrequentTrackingEditor.php',
'PhrequentUIEventListener' => 'applications/phrequent/event/PhrequentUIEventListener.php',
'PhrequentUserTime' => 'applications/phrequent/storage/PhrequentUserTime.php',
'PhrequentUserTimeQuery' => 'applications/phrequent/query/PhrequentUserTimeQuery.php',
'PhrictionChangeType' => 'applications/phriction/constants/PhrictionChangeType.php',
'PhrictionConduitAPIMethod' => 'applications/phriction/conduit/PhrictionConduitAPIMethod.php',
'PhrictionConstants' => 'applications/phriction/constants/PhrictionConstants.php',
'PhrictionContent' => 'applications/phriction/storage/PhrictionContent.php',
'PhrictionContentPHIDType' => 'applications/phriction/phid/PhrictionContentPHIDType.php',
'PhrictionContentQuery' => 'applications/phriction/query/PhrictionContentQuery.php',
'PhrictionContentSearchConduitAPIMethod' => 'applications/phriction/conduit/PhrictionContentSearchConduitAPIMethod.php',
'PhrictionContentSearchEngine' => 'applications/phriction/query/PhrictionContentSearchEngine.php',
'PhrictionContentSearchEngineAttachment' => 'applications/phriction/engineextension/PhrictionContentSearchEngineAttachment.php',
'PhrictionController' => 'applications/phriction/controller/PhrictionController.php',
'PhrictionCreateConduitAPIMethod' => 'applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php',
'PhrictionDAO' => 'applications/phriction/storage/PhrictionDAO.php',
'PhrictionDatasourceEngineExtension' => 'applications/phriction/engineextension/PhrictionDatasourceEngineExtension.php',
'PhrictionDeleteController' => 'applications/phriction/controller/PhrictionDeleteController.php',
'PhrictionDiffController' => 'applications/phriction/controller/PhrictionDiffController.php',
'PhrictionDocument' => 'applications/phriction/storage/PhrictionDocument.php',
'PhrictionDocumentAuthorHeraldField' => 'applications/phriction/herald/PhrictionDocumentAuthorHeraldField.php',
'PhrictionDocumentContentHeraldField' => 'applications/phriction/herald/PhrictionDocumentContentHeraldField.php',
'PhrictionDocumentContentTransaction' => 'applications/phriction/xaction/PhrictionDocumentContentTransaction.php',
'PhrictionDocumentController' => 'applications/phriction/controller/PhrictionDocumentController.php',
'PhrictionDocumentDatasource' => 'applications/phriction/typeahead/PhrictionDocumentDatasource.php',
'PhrictionDocumentDeleteTransaction' => 'applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php',
'PhrictionDocumentDraftTransaction' => 'applications/phriction/xaction/PhrictionDocumentDraftTransaction.php',
'PhrictionDocumentEditEngine' => 'applications/phriction/editor/PhrictionDocumentEditEngine.php',
'PhrictionDocumentEditTransaction' => 'applications/phriction/xaction/PhrictionDocumentEditTransaction.php',
'PhrictionDocumentFerretEngine' => 'applications/phriction/search/PhrictionDocumentFerretEngine.php',
'PhrictionDocumentFulltextEngine' => 'applications/phriction/search/PhrictionDocumentFulltextEngine.php',
'PhrictionDocumentHeraldAdapter' => 'applications/phriction/herald/PhrictionDocumentHeraldAdapter.php',
'PhrictionDocumentHeraldField' => 'applications/phriction/herald/PhrictionDocumentHeraldField.php',
'PhrictionDocumentHeraldFieldGroup' => 'applications/phriction/herald/PhrictionDocumentHeraldFieldGroup.php',
'PhrictionDocumentMoveAwayTransaction' => 'applications/phriction/xaction/PhrictionDocumentMoveAwayTransaction.php',
'PhrictionDocumentMoveToTransaction' => 'applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php',
'PhrictionDocumentPHIDType' => 'applications/phriction/phid/PhrictionDocumentPHIDType.php',
'PhrictionDocumentPathHeraldField' => 'applications/phriction/herald/PhrictionDocumentPathHeraldField.php',
'PhrictionDocumentPolicyCodex' => 'applications/phriction/codex/PhrictionDocumentPolicyCodex.php',
'PhrictionDocumentPublishTransaction' => 'applications/phriction/xaction/PhrictionDocumentPublishTransaction.php',
'PhrictionDocumentPublishedHeraldField' => 'applications/phriction/herald/PhrictionDocumentPublishedHeraldField.php',
'PhrictionDocumentQuery' => 'applications/phriction/query/PhrictionDocumentQuery.php',
'PhrictionDocumentSearchConduitAPIMethod' => 'applications/phriction/conduit/PhrictionDocumentSearchConduitAPIMethod.php',
'PhrictionDocumentSearchEngine' => 'applications/phriction/query/PhrictionDocumentSearchEngine.php',
'PhrictionDocumentStatus' => 'applications/phriction/constants/PhrictionDocumentStatus.php',
'PhrictionDocumentTitleHeraldField' => 'applications/phriction/herald/PhrictionDocumentTitleHeraldField.php',
'PhrictionDocumentTitleTransaction' => 'applications/phriction/xaction/PhrictionDocumentTitleTransaction.php',
'PhrictionDocumentTransactionType' => 'applications/phriction/xaction/PhrictionDocumentTransactionType.php',
'PhrictionDocumentVersionTransaction' => 'applications/phriction/xaction/PhrictionDocumentVersionTransaction.php',
'PhrictionEditConduitAPIMethod' => 'applications/phriction/conduit/PhrictionEditConduitAPIMethod.php',
'PhrictionEditController' => 'applications/phriction/controller/PhrictionEditController.php',
'PhrictionEditEngineController' => 'applications/phriction/controller/PhrictionEditEngineController.php',
'PhrictionHistoryConduitAPIMethod' => 'applications/phriction/conduit/PhrictionHistoryConduitAPIMethod.php',
'PhrictionHistoryController' => 'applications/phriction/controller/PhrictionHistoryController.php',
'PhrictionInfoConduitAPIMethod' => 'applications/phriction/conduit/PhrictionInfoConduitAPIMethod.php',
'PhrictionListController' => 'applications/phriction/controller/PhrictionListController.php',
'PhrictionMarkupPreviewController' => 'applications/phriction/controller/PhrictionMarkupPreviewController.php',
'PhrictionMoveController' => 'applications/phriction/controller/PhrictionMoveController.php',
'PhrictionNewController' => 'applications/phriction/controller/PhrictionNewController.php',
'PhrictionPublishController' => 'applications/phriction/controller/PhrictionPublishController.php',
'PhrictionRemarkupRule' => 'applications/phriction/markup/PhrictionRemarkupRule.php',
'PhrictionReplyHandler' => 'applications/phriction/mail/PhrictionReplyHandler.php',
'PhrictionSchemaSpec' => 'applications/phriction/storage/PhrictionSchemaSpec.php',
'PhrictionTransaction' => 'applications/phriction/storage/PhrictionTransaction.php',
'PhrictionTransactionComment' => 'applications/phriction/storage/PhrictionTransactionComment.php',
'PhrictionTransactionEditor' => 'applications/phriction/editor/PhrictionTransactionEditor.php',
'PhrictionTransactionQuery' => 'applications/phriction/query/PhrictionTransactionQuery.php',
'PhutilAPCKeyValueCache' => 'infrastructure/cache/PhutilAPCKeyValueCache.php',
'PhutilAmazonAuthAdapter' => 'applications/auth/adapter/PhutilAmazonAuthAdapter.php',
'PhutilAsanaAuthAdapter' => 'applications/auth/adapter/PhutilAsanaAuthAdapter.php',
'PhutilAuthAdapter' => 'applications/auth/adapter/PhutilAuthAdapter.php',
'PhutilAuthConfigurationException' => 'applications/auth/exception/PhutilAuthConfigurationException.php',
'PhutilAuthCredentialException' => 'applications/auth/exception/PhutilAuthCredentialException.php',
'PhutilAuthException' => 'applications/auth/exception/PhutilAuthException.php',
'PhutilAuthUserAbortedException' => 'applications/auth/exception/PhutilAuthUserAbortedException.php',
'PhutilBitbucketAuthAdapter' => 'applications/auth/adapter/PhutilBitbucketAuthAdapter.php',
'PhutilCLikeCodeSnippetContextFreeGrammar' => 'infrastructure/lipsum/code/PhutilCLikeCodeSnippetContextFreeGrammar.php',
'PhutilCalendarAbsoluteDateTime' => 'applications/calendar/parser/data/PhutilCalendarAbsoluteDateTime.php',
'PhutilCalendarContainerNode' => 'applications/calendar/parser/data/PhutilCalendarContainerNode.php',
'PhutilCalendarDateTime' => 'applications/calendar/parser/data/PhutilCalendarDateTime.php',
'PhutilCalendarDateTimeTestCase' => 'applications/calendar/parser/data/__tests__/PhutilCalendarDateTimeTestCase.php',
'PhutilCalendarDocumentNode' => 'applications/calendar/parser/data/PhutilCalendarDocumentNode.php',
'PhutilCalendarDuration' => 'applications/calendar/parser/data/PhutilCalendarDuration.php',
'PhutilCalendarEventNode' => 'applications/calendar/parser/data/PhutilCalendarEventNode.php',
'PhutilCalendarNode' => 'applications/calendar/parser/data/PhutilCalendarNode.php',
'PhutilCalendarProxyDateTime' => 'applications/calendar/parser/data/PhutilCalendarProxyDateTime.php',
'PhutilCalendarRawNode' => 'applications/calendar/parser/data/PhutilCalendarRawNode.php',
'PhutilCalendarRecurrenceList' => 'applications/calendar/parser/data/PhutilCalendarRecurrenceList.php',
'PhutilCalendarRecurrenceRule' => 'applications/calendar/parser/data/PhutilCalendarRecurrenceRule.php',
'PhutilCalendarRecurrenceRuleTestCase' => 'applications/calendar/parser/data/__tests__/PhutilCalendarRecurrenceRuleTestCase.php',
'PhutilCalendarRecurrenceSet' => 'applications/calendar/parser/data/PhutilCalendarRecurrenceSet.php',
'PhutilCalendarRecurrenceSource' => 'applications/calendar/parser/data/PhutilCalendarRecurrenceSource.php',
'PhutilCalendarRecurrenceTestCase' => 'applications/calendar/parser/data/__tests__/PhutilCalendarRecurrenceTestCase.php',
'PhutilCalendarRelativeDateTime' => 'applications/calendar/parser/data/PhutilCalendarRelativeDateTime.php',
'PhutilCalendarRootNode' => 'applications/calendar/parser/data/PhutilCalendarRootNode.php',
'PhutilCalendarUserNode' => 'applications/calendar/parser/data/PhutilCalendarUserNode.php',
'PhutilCodeSnippetContextFreeGrammar' => 'infrastructure/lipsum/code/PhutilCodeSnippetContextFreeGrammar.php',
'PhutilConsoleSyntaxHighlighter' => 'infrastructure/markup/syntax/highlighter/PhutilConsoleSyntaxHighlighter.php',
'PhutilContextFreeGrammar' => 'infrastructure/lipsum/PhutilContextFreeGrammar.php',
'PhutilDaemon' => 'infrastructure/daemon/PhutilDaemon.php',
'PhutilDaemonHandle' => 'infrastructure/daemon/PhutilDaemonHandle.php',
'PhutilDaemonOverseer' => 'infrastructure/daemon/PhutilDaemonOverseer.php',
'PhutilDaemonOverseerModule' => 'infrastructure/daemon/PhutilDaemonOverseerModule.php',
'PhutilDaemonPool' => 'infrastructure/daemon/PhutilDaemonPool.php',
'PhutilDefaultSyntaxHighlighter' => 'infrastructure/markup/syntax/highlighter/PhutilDefaultSyntaxHighlighter.php',
'PhutilDefaultSyntaxHighlighterEngine' => 'infrastructure/markup/syntax/engine/PhutilDefaultSyntaxHighlighterEngine.php',
'PhutilDefaultSyntaxHighlighterEnginePygmentsFuture' => 'infrastructure/markup/syntax/highlighter/pygments/PhutilDefaultSyntaxHighlighterEnginePygmentsFuture.php',
'PhutilDefaultSyntaxHighlighterEngineTestCase' => 'infrastructure/markup/syntax/engine/__tests__/PhutilDefaultSyntaxHighlighterEngineTestCase.php',
'PhutilDirectoryKeyValueCache' => 'infrastructure/cache/PhutilDirectoryKeyValueCache.php',
'PhutilDisqusAuthAdapter' => 'applications/auth/adapter/PhutilDisqusAuthAdapter.php',
'PhutilDivinerSyntaxHighlighter' => 'infrastructure/markup/syntax/highlighter/PhutilDivinerSyntaxHighlighter.php',
'PhutilEmptyAuthAdapter' => 'applications/auth/adapter/PhutilEmptyAuthAdapter.php',
'PhutilFacebookAuthAdapter' => 'applications/auth/adapter/PhutilFacebookAuthAdapter.php',
'PhutilGitHubAuthAdapter' => 'applications/auth/adapter/PhutilGitHubAuthAdapter.php',
'PhutilGoogleAuthAdapter' => 'applications/auth/adapter/PhutilGoogleAuthAdapter.php',
'PhutilICSParser' => 'applications/calendar/parser/ics/PhutilICSParser.php',
'PhutilICSParserException' => 'applications/calendar/parser/ics/PhutilICSParserException.php',
'PhutilICSParserTestCase' => 'applications/calendar/parser/ics/__tests__/PhutilICSParserTestCase.php',
'PhutilICSWriter' => 'applications/calendar/parser/ics/PhutilICSWriter.php',
'PhutilICSWriterTestCase' => 'applications/calendar/parser/ics/__tests__/PhutilICSWriterTestCase.php',
'PhutilInRequestKeyValueCache' => 'infrastructure/cache/PhutilInRequestKeyValueCache.php',
'PhutilInvisibleSyntaxHighlighter' => 'infrastructure/markup/syntax/highlighter/PhutilInvisibleSyntaxHighlighter.php',
'PhutilJIRAAuthAdapter' => 'applications/auth/adapter/PhutilJIRAAuthAdapter.php',
'PhutilJSONFragmentLexerHighlighterTestCase' => 'infrastructure/markup/syntax/highlighter/__tests__/PhutilJSONFragmentLexerHighlighterTestCase.php',
'PhutilJavaCodeSnippetContextFreeGrammar' => 'infrastructure/lipsum/code/PhutilJavaCodeSnippetContextFreeGrammar.php',
'PhutilKeyValueCache' => 'infrastructure/cache/PhutilKeyValueCache.php',
'PhutilKeyValueCacheNamespace' => 'infrastructure/cache/PhutilKeyValueCacheNamespace.php',
'PhutilKeyValueCacheProfiler' => 'infrastructure/cache/PhutilKeyValueCacheProfiler.php',
'PhutilKeyValueCacheProxy' => 'infrastructure/cache/PhutilKeyValueCacheProxy.php',
'PhutilKeyValueCacheStack' => 'infrastructure/cache/PhutilKeyValueCacheStack.php',
'PhutilKeyValueCacheTestCase' => 'infrastructure/cache/__tests__/PhutilKeyValueCacheTestCase.php',
'PhutilLDAPAuthAdapter' => 'applications/auth/adapter/PhutilLDAPAuthAdapter.php',
'PhutilLexerSyntaxHighlighter' => 'infrastructure/markup/syntax/highlighter/PhutilLexerSyntaxHighlighter.php',
'PhutilLipsumContextFreeGrammar' => 'infrastructure/lipsum/PhutilLipsumContextFreeGrammar.php',
'PhutilMarkupEngine' => 'infrastructure/markup/PhutilMarkupEngine.php',
'PhutilMarkupTestCase' => 'infrastructure/markup/__tests__/PhutilMarkupTestCase.php',
'PhutilMemcacheKeyValueCache' => 'infrastructure/cache/PhutilMemcacheKeyValueCache.php',
'PhutilOAuth1AuthAdapter' => 'applications/auth/adapter/PhutilOAuth1AuthAdapter.php',
'PhutilOAuthAuthAdapter' => 'applications/auth/adapter/PhutilOAuthAuthAdapter.php',
'PhutilOnDiskKeyValueCache' => 'infrastructure/cache/PhutilOnDiskKeyValueCache.php',
'PhutilPHPCodeSnippetContextFreeGrammar' => 'infrastructure/lipsum/code/PhutilPHPCodeSnippetContextFreeGrammar.php',
'PhutilPHPFragmentLexerHighlighterTestCase' => 'infrastructure/markup/syntax/highlighter/__tests__/PhutilPHPFragmentLexerHighlighterTestCase.php',
'PhutilPhabricatorAuthAdapter' => 'applications/auth/adapter/PhutilPhabricatorAuthAdapter.php',
'PhutilProseDiff' => 'infrastructure/diff/prose/PhutilProseDiff.php',
'PhutilProseDiffTestCase' => 'infrastructure/diff/prose/__tests__/PhutilProseDiffTestCase.php',
'PhutilProseDifferenceEngine' => 'infrastructure/diff/prose/PhutilProseDifferenceEngine.php',
'PhutilPygmentizeParser' => 'infrastructure/parser/PhutilPygmentizeParser.php',
'PhutilPygmentizeParserTestCase' => 'infrastructure/parser/__tests__/PhutilPygmentizeParserTestCase.php',
'PhutilPygmentsSyntaxHighlighter' => 'infrastructure/markup/syntax/highlighter/PhutilPygmentsSyntaxHighlighter.php',
'PhutilQsprintfInterface' => 'infrastructure/storage/xsprintf/PhutilQsprintfInterface.php',
'PhutilQueryString' => 'infrastructure/storage/xsprintf/PhutilQueryString.php',
'PhutilRainbowSyntaxHighlighter' => 'infrastructure/markup/syntax/highlighter/PhutilRainbowSyntaxHighlighter.php',
'PhutilRealNameContextFreeGrammar' => 'infrastructure/lipsum/PhutilRealNameContextFreeGrammar.php',
'PhutilRemarkupAnchorRule' => 'infrastructure/markup/markuprule/PhutilRemarkupAnchorRule.php',
'PhutilRemarkupBlockInterpreter' => 'infrastructure/markup/blockrule/PhutilRemarkupBlockInterpreter.php',
'PhutilRemarkupBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupBlockRule.php',
'PhutilRemarkupBlockStorage' => 'infrastructure/markup/PhutilRemarkupBlockStorage.php',
'PhutilRemarkupBoldRule' => 'infrastructure/markup/markuprule/PhutilRemarkupBoldRule.php',
'PhutilRemarkupCodeBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupCodeBlockRule.php',
'PhutilRemarkupDefaultBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupDefaultBlockRule.php',
'PhutilRemarkupDelRule' => 'infrastructure/markup/markuprule/PhutilRemarkupDelRule.php',
'PhutilRemarkupDocumentLinkRule' => 'infrastructure/markup/markuprule/PhutilRemarkupDocumentLinkRule.php',
'PhutilRemarkupEngine' => 'infrastructure/markup/remarkup/PhutilRemarkupEngine.php',
'PhutilRemarkupEngineTestCase' => 'infrastructure/markup/remarkup/__tests__/PhutilRemarkupEngineTestCase.php',
'PhutilRemarkupEscapeRemarkupRule' => 'infrastructure/markup/markuprule/PhutilRemarkupEscapeRemarkupRule.php',
'PhutilRemarkupEvalRule' => 'infrastructure/markup/markuprule/PhutilRemarkupEvalRule.php',
'PhutilRemarkupHeaderBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupHeaderBlockRule.php',
'PhutilRemarkupHighlightRule' => 'infrastructure/markup/markuprule/PhutilRemarkupHighlightRule.php',
'PhutilRemarkupHorizontalRuleBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupHorizontalRuleBlockRule.php',
'PhutilRemarkupHyperlinkEngineExtension' => 'infrastructure/markup/markuprule/PhutilRemarkupHyperlinkEngineExtension.php',
'PhutilRemarkupHyperlinkRef' => 'infrastructure/markup/markuprule/PhutilRemarkupHyperlinkRef.php',
'PhutilRemarkupHyperlinkRule' => 'infrastructure/markup/markuprule/PhutilRemarkupHyperlinkRule.php',
'PhutilRemarkupInlineBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupInlineBlockRule.php',
'PhutilRemarkupInterpreterBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupInterpreterBlockRule.php',
'PhutilRemarkupItalicRule' => 'infrastructure/markup/markuprule/PhutilRemarkupItalicRule.php',
'PhutilRemarkupLinebreaksRule' => 'infrastructure/markup/markuprule/PhutilRemarkupLinebreaksRule.php',
'PhutilRemarkupListBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupListBlockRule.php',
'PhutilRemarkupLiteralBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupLiteralBlockRule.php',
'PhutilRemarkupMonospaceRule' => 'infrastructure/markup/markuprule/PhutilRemarkupMonospaceRule.php',
'PhutilRemarkupNoteBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupNoteBlockRule.php',
'PhutilRemarkupQuotedBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupQuotedBlockRule.php',
'PhutilRemarkupQuotesBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupQuotesBlockRule.php',
'PhutilRemarkupReplyBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupReplyBlockRule.php',
'PhutilRemarkupRule' => 'infrastructure/markup/markuprule/PhutilRemarkupRule.php',
'PhutilRemarkupSimpleTableBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupSimpleTableBlockRule.php',
'PhutilRemarkupTableBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupTableBlockRule.php',
'PhutilRemarkupTestInterpreterRule' => 'infrastructure/markup/blockrule/PhutilRemarkupTestInterpreterRule.php',
'PhutilRemarkupUnderlineRule' => 'infrastructure/markup/markuprule/PhutilRemarkupUnderlineRule.php',
'PhutilSafeHTML' => 'infrastructure/markup/PhutilSafeHTML.php',
'PhutilSafeHTMLProducerInterface' => 'infrastructure/markup/PhutilSafeHTMLProducerInterface.php',
'PhutilSafeHTMLTestCase' => 'infrastructure/markup/__tests__/PhutilSafeHTMLTestCase.php',
'PhutilSearchQueryCompiler' => 'applications/search/compiler/PhutilSearchQueryCompiler.php',
'PhutilSearchQueryCompilerSyntaxException' => 'applications/search/compiler/PhutilSearchQueryCompilerSyntaxException.php',
'PhutilSearchQueryCompilerTestCase' => 'applications/search/compiler/__tests__/PhutilSearchQueryCompilerTestCase.php',
'PhutilSearchQueryToken' => 'applications/search/compiler/PhutilSearchQueryToken.php',
'PhutilSearchStemmer' => 'applications/search/compiler/PhutilSearchStemmer.php',
'PhutilSearchStemmerTestCase' => 'applications/search/compiler/__tests__/PhutilSearchStemmerTestCase.php',
'PhutilSlackAuthAdapter' => 'applications/auth/adapter/PhutilSlackAuthAdapter.php',
'PhutilSprite' => 'aphront/sprite/PhutilSprite.php',
'PhutilSpriteSheet' => 'aphront/sprite/PhutilSpriteSheet.php',
'PhutilSyntaxHighlighter' => 'infrastructure/markup/syntax/highlighter/PhutilSyntaxHighlighter.php',
'PhutilSyntaxHighlighterEngine' => 'infrastructure/markup/syntax/engine/PhutilSyntaxHighlighterEngine.php',
'PhutilSyntaxHighlighterException' => 'infrastructure/markup/syntax/highlighter/PhutilSyntaxHighlighterException.php',
'PhutilTranslatedHTMLTestCase' => 'infrastructure/markup/__tests__/PhutilTranslatedHTMLTestCase.php',
'PhutilTwitchAuthAdapter' => 'applications/auth/adapter/PhutilTwitchAuthAdapter.php',
'PhutilTwitterAuthAdapter' => 'applications/auth/adapter/PhutilTwitterAuthAdapter.php',
'PhutilWordPressAuthAdapter' => 'applications/auth/adapter/PhutilWordPressAuthAdapter.php',
'PhutilXHPASTSyntaxHighlighter' => 'infrastructure/markup/syntax/highlighter/PhutilXHPASTSyntaxHighlighter.php',
'PhutilXHPASTSyntaxHighlighterFuture' => 'infrastructure/markup/syntax/highlighter/xhpast/PhutilXHPASTSyntaxHighlighterFuture.php',
'PhutilXHPASTSyntaxHighlighterTestCase' => 'infrastructure/markup/syntax/highlighter/__tests__/PhutilXHPASTSyntaxHighlighterTestCase.php',
'PolicyLockOptionType' => 'applications/policy/config/PolicyLockOptionType.php',
'PonderAddAnswerView' => 'applications/ponder/view/PonderAddAnswerView.php',
'PonderAnswer' => 'applications/ponder/storage/PonderAnswer.php',
'PonderAnswerCommentController' => 'applications/ponder/controller/PonderAnswerCommentController.php',
'PonderAnswerContentTransaction' => 'applications/ponder/xaction/PonderAnswerContentTransaction.php',
'PonderAnswerEditController' => 'applications/ponder/controller/PonderAnswerEditController.php',
'PonderAnswerEditor' => 'applications/ponder/editor/PonderAnswerEditor.php',
'PonderAnswerHistoryController' => 'applications/ponder/controller/PonderAnswerHistoryController.php',
'PonderAnswerMailReceiver' => 'applications/ponder/mail/PonderAnswerMailReceiver.php',
'PonderAnswerPHIDType' => 'applications/ponder/phid/PonderAnswerPHIDType.php',
'PonderAnswerQuery' => 'applications/ponder/query/PonderAnswerQuery.php',
'PonderAnswerQuestionIDTransaction' => 'applications/ponder/xaction/PonderAnswerQuestionIDTransaction.php',
'PonderAnswerReplyHandler' => 'applications/ponder/mail/PonderAnswerReplyHandler.php',
'PonderAnswerSaveController' => 'applications/ponder/controller/PonderAnswerSaveController.php',
'PonderAnswerStatus' => 'applications/ponder/constants/PonderAnswerStatus.php',
'PonderAnswerStatusTransaction' => 'applications/ponder/xaction/PonderAnswerStatusTransaction.php',
'PonderAnswerTransaction' => 'applications/ponder/storage/PonderAnswerTransaction.php',
'PonderAnswerTransactionComment' => 'applications/ponder/storage/PonderAnswerTransactionComment.php',
'PonderAnswerTransactionQuery' => 'applications/ponder/query/PonderAnswerTransactionQuery.php',
'PonderAnswerTransactionType' => 'applications/ponder/xaction/PonderAnswerTransactionType.php',
'PonderAnswerView' => 'applications/ponder/view/PonderAnswerView.php',
'PonderConstants' => 'applications/ponder/constants/PonderConstants.php',
'PonderController' => 'applications/ponder/controller/PonderController.php',
'PonderDAO' => 'applications/ponder/storage/PonderDAO.php',
'PonderDefaultViewCapability' => 'applications/ponder/capability/PonderDefaultViewCapability.php',
'PonderEditor' => 'applications/ponder/editor/PonderEditor.php',
'PonderFooterView' => 'applications/ponder/view/PonderFooterView.php',
'PonderModerateCapability' => 'applications/ponder/capability/PonderModerateCapability.php',
'PonderQuestion' => 'applications/ponder/storage/PonderQuestion.php',
'PonderQuestionAnswerTransaction' => 'applications/ponder/xaction/PonderQuestionAnswerTransaction.php',
'PonderQuestionAnswerWikiTransaction' => 'applications/ponder/xaction/PonderQuestionAnswerWikiTransaction.php',
'PonderQuestionCommentController' => 'applications/ponder/controller/PonderQuestionCommentController.php',
'PonderQuestionContentTransaction' => 'applications/ponder/xaction/PonderQuestionContentTransaction.php',
'PonderQuestionCreateMailReceiver' => 'applications/ponder/mail/PonderQuestionCreateMailReceiver.php',
'PonderQuestionEditController' => 'applications/ponder/controller/PonderQuestionEditController.php',
'PonderQuestionEditEngine' => 'applications/ponder/editor/PonderQuestionEditEngine.php',
'PonderQuestionEditor' => 'applications/ponder/editor/PonderQuestionEditor.php',
'PonderQuestionFerretEngine' => 'applications/ponder/search/PonderQuestionFerretEngine.php',
'PonderQuestionFulltextEngine' => 'applications/ponder/search/PonderQuestionFulltextEngine.php',
'PonderQuestionHistoryController' => 'applications/ponder/controller/PonderQuestionHistoryController.php',
'PonderQuestionListController' => 'applications/ponder/controller/PonderQuestionListController.php',
'PonderQuestionMailReceiver' => 'applications/ponder/mail/PonderQuestionMailReceiver.php',
'PonderQuestionPHIDType' => 'applications/ponder/phid/PonderQuestionPHIDType.php',
'PonderQuestionQuery' => 'applications/ponder/query/PonderQuestionQuery.php',
'PonderQuestionReplyHandler' => 'applications/ponder/mail/PonderQuestionReplyHandler.php',
'PonderQuestionSearchEngine' => 'applications/ponder/query/PonderQuestionSearchEngine.php',
'PonderQuestionStatus' => 'applications/ponder/constants/PonderQuestionStatus.php',
'PonderQuestionStatusController' => 'applications/ponder/controller/PonderQuestionStatusController.php',
'PonderQuestionStatusTransaction' => 'applications/ponder/xaction/PonderQuestionStatusTransaction.php',
'PonderQuestionTitleTransaction' => 'applications/ponder/xaction/PonderQuestionTitleTransaction.php',
'PonderQuestionTransaction' => 'applications/ponder/storage/PonderQuestionTransaction.php',
'PonderQuestionTransactionComment' => 'applications/ponder/storage/PonderQuestionTransactionComment.php',
'PonderQuestionTransactionQuery' => 'applications/ponder/query/PonderQuestionTransactionQuery.php',
'PonderQuestionTransactionType' => 'applications/ponder/xaction/PonderQuestionTransactionType.php',
'PonderQuestionViewController' => 'applications/ponder/controller/PonderQuestionViewController.php',
'PonderRemarkupRule' => 'applications/ponder/remarkup/PonderRemarkupRule.php',
'PonderSchemaSpec' => 'applications/ponder/storage/PonderSchemaSpec.php',
'ProjectAddProjectsEmailCommand' => 'applications/project/command/ProjectAddProjectsEmailCommand.php',
'ProjectBoardTaskCard' => 'applications/project/view/ProjectBoardTaskCard.php',
'ProjectCanLockProjectsCapability' => 'applications/project/capability/ProjectCanLockProjectsCapability.php',
'ProjectColumnSearchConduitAPIMethod' => 'applications/project/conduit/ProjectColumnSearchConduitAPIMethod.php',
'ProjectConduitAPIMethod' => 'applications/project/conduit/ProjectConduitAPIMethod.php',
'ProjectCreateConduitAPIMethod' => 'applications/project/conduit/ProjectCreateConduitAPIMethod.php',
'ProjectCreateProjectsCapability' => 'applications/project/capability/ProjectCreateProjectsCapability.php',
'ProjectDatasourceEngineExtension' => 'applications/project/engineextension/ProjectDatasourceEngineExtension.php',
'ProjectDefaultEditCapability' => 'applications/project/capability/ProjectDefaultEditCapability.php',
'ProjectDefaultJoinCapability' => 'applications/project/capability/ProjectDefaultJoinCapability.php',
'ProjectDefaultViewCapability' => 'applications/project/capability/ProjectDefaultViewCapability.php',
'ProjectEditConduitAPIMethod' => 'applications/project/conduit/ProjectEditConduitAPIMethod.php',
'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php',
'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php',
'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php',
'ProjectReplyHandler' => 'applications/project/mail/ProjectReplyHandler.php',
'ProjectSearchConduitAPIMethod' => 'applications/project/conduit/ProjectSearchConduitAPIMethod.php',
'QueryFormattingTestCase' => 'infrastructure/storage/__tests__/QueryFormattingTestCase.php',
'QueryFuture' => 'infrastructure/storage/future/QueryFuture.php',
- 'ReleephAuthorFieldSpecification' => 'applications/releeph/field/specification/ReleephAuthorFieldSpecification.php',
- 'ReleephBranch' => 'applications/releeph/storage/ReleephBranch.php',
- 'ReleephBranchAccessController' => 'applications/releeph/controller/branch/ReleephBranchAccessController.php',
- 'ReleephBranchCommitFieldSpecification' => 'applications/releeph/field/specification/ReleephBranchCommitFieldSpecification.php',
- 'ReleephBranchController' => 'applications/releeph/controller/branch/ReleephBranchController.php',
- 'ReleephBranchCreateController' => 'applications/releeph/controller/branch/ReleephBranchCreateController.php',
- 'ReleephBranchEditController' => 'applications/releeph/controller/branch/ReleephBranchEditController.php',
- 'ReleephBranchEditor' => 'applications/releeph/editor/ReleephBranchEditor.php',
- 'ReleephBranchHistoryController' => 'applications/releeph/controller/branch/ReleephBranchHistoryController.php',
- 'ReleephBranchNamePreviewController' => 'applications/releeph/controller/branch/ReleephBranchNamePreviewController.php',
- 'ReleephBranchPHIDType' => 'applications/releeph/phid/ReleephBranchPHIDType.php',
- 'ReleephBranchPreviewView' => 'applications/releeph/view/branch/ReleephBranchPreviewView.php',
- 'ReleephBranchQuery' => 'applications/releeph/query/ReleephBranchQuery.php',
- 'ReleephBranchSearchEngine' => 'applications/releeph/query/ReleephBranchSearchEngine.php',
- 'ReleephBranchTemplate' => 'applications/releeph/view/branch/ReleephBranchTemplate.php',
- 'ReleephBranchTransaction' => 'applications/releeph/storage/ReleephBranchTransaction.php',
- 'ReleephBranchTransactionQuery' => 'applications/releeph/query/ReleephBranchTransactionQuery.php',
- 'ReleephBranchViewController' => 'applications/releeph/controller/branch/ReleephBranchViewController.php',
- 'ReleephCommitFinder' => 'applications/releeph/commitfinder/ReleephCommitFinder.php',
- 'ReleephCommitFinderException' => 'applications/releeph/commitfinder/ReleephCommitFinderException.php',
- 'ReleephCommitMessageFieldSpecification' => 'applications/releeph/field/specification/ReleephCommitMessageFieldSpecification.php',
- 'ReleephConduitAPIMethod' => 'applications/releeph/conduit/ReleephConduitAPIMethod.php',
- 'ReleephController' => 'applications/releeph/controller/ReleephController.php',
- 'ReleephDAO' => 'applications/releeph/storage/ReleephDAO.php',
- 'ReleephDefaultFieldSelector' => 'applications/releeph/field/selector/ReleephDefaultFieldSelector.php',
- 'ReleephDependsOnFieldSpecification' => 'applications/releeph/field/specification/ReleephDependsOnFieldSpecification.php',
- 'ReleephDiffChurnFieldSpecification' => 'applications/releeph/field/specification/ReleephDiffChurnFieldSpecification.php',
- 'ReleephDiffMessageFieldSpecification' => 'applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php',
- 'ReleephDiffSizeFieldSpecification' => 'applications/releeph/field/specification/ReleephDiffSizeFieldSpecification.php',
- 'ReleephFieldParseException' => 'applications/releeph/field/exception/ReleephFieldParseException.php',
- 'ReleephFieldSelector' => 'applications/releeph/field/selector/ReleephFieldSelector.php',
- 'ReleephFieldSpecification' => 'applications/releeph/field/specification/ReleephFieldSpecification.php',
- 'ReleephGetBranchesConduitAPIMethod' => 'applications/releeph/conduit/ReleephGetBranchesConduitAPIMethod.php',
- 'ReleephIntentFieldSpecification' => 'applications/releeph/field/specification/ReleephIntentFieldSpecification.php',
- 'ReleephLevelFieldSpecification' => 'applications/releeph/field/specification/ReleephLevelFieldSpecification.php',
- 'ReleephOriginalCommitFieldSpecification' => 'applications/releeph/field/specification/ReleephOriginalCommitFieldSpecification.php',
- 'ReleephProductActionController' => 'applications/releeph/controller/product/ReleephProductActionController.php',
- 'ReleephProductController' => 'applications/releeph/controller/product/ReleephProductController.php',
- 'ReleephProductCreateController' => 'applications/releeph/controller/product/ReleephProductCreateController.php',
- 'ReleephProductEditController' => 'applications/releeph/controller/product/ReleephProductEditController.php',
- 'ReleephProductEditor' => 'applications/releeph/editor/ReleephProductEditor.php',
- 'ReleephProductHistoryController' => 'applications/releeph/controller/product/ReleephProductHistoryController.php',
- 'ReleephProductListController' => 'applications/releeph/controller/product/ReleephProductListController.php',
- 'ReleephProductPHIDType' => 'applications/releeph/phid/ReleephProductPHIDType.php',
- 'ReleephProductQuery' => 'applications/releeph/query/ReleephProductQuery.php',
- 'ReleephProductSearchEngine' => 'applications/releeph/query/ReleephProductSearchEngine.php',
- 'ReleephProductTransaction' => 'applications/releeph/storage/ReleephProductTransaction.php',
- 'ReleephProductTransactionQuery' => 'applications/releeph/query/ReleephProductTransactionQuery.php',
- 'ReleephProductViewController' => 'applications/releeph/controller/product/ReleephProductViewController.php',
- 'ReleephProject' => 'applications/releeph/storage/ReleephProject.php',
- 'ReleephQueryBranchesConduitAPIMethod' => 'applications/releeph/conduit/ReleephQueryBranchesConduitAPIMethod.php',
- 'ReleephQueryProductsConduitAPIMethod' => 'applications/releeph/conduit/ReleephQueryProductsConduitAPIMethod.php',
- 'ReleephQueryRequestsConduitAPIMethod' => 'applications/releeph/conduit/ReleephQueryRequestsConduitAPIMethod.php',
- 'ReleephReasonFieldSpecification' => 'applications/releeph/field/specification/ReleephReasonFieldSpecification.php',
- 'ReleephRequest' => 'applications/releeph/storage/ReleephRequest.php',
- 'ReleephRequestActionController' => 'applications/releeph/controller/request/ReleephRequestActionController.php',
- 'ReleephRequestCommentController' => 'applications/releeph/controller/request/ReleephRequestCommentController.php',
- 'ReleephRequestConduitAPIMethod' => 'applications/releeph/conduit/ReleephRequestConduitAPIMethod.php',
- 'ReleephRequestController' => 'applications/releeph/controller/request/ReleephRequestController.php',
- 'ReleephRequestDifferentialCreateController' => 'applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php',
- 'ReleephRequestEditController' => 'applications/releeph/controller/request/ReleephRequestEditController.php',
- 'ReleephRequestMailReceiver' => 'applications/releeph/mail/ReleephRequestMailReceiver.php',
- 'ReleephRequestPHIDType' => 'applications/releeph/phid/ReleephRequestPHIDType.php',
- 'ReleephRequestQuery' => 'applications/releeph/query/ReleephRequestQuery.php',
- 'ReleephRequestReplyHandler' => 'applications/releeph/mail/ReleephRequestReplyHandler.php',
- 'ReleephRequestSearchEngine' => 'applications/releeph/query/ReleephRequestSearchEngine.php',
- 'ReleephRequestStatus' => 'applications/releeph/constants/ReleephRequestStatus.php',
- 'ReleephRequestTransaction' => 'applications/releeph/storage/ReleephRequestTransaction.php',
- 'ReleephRequestTransactionComment' => 'applications/releeph/storage/ReleephRequestTransactionComment.php',
- 'ReleephRequestTransactionQuery' => 'applications/releeph/query/ReleephRequestTransactionQuery.php',
- 'ReleephRequestTransactionalEditor' => 'applications/releeph/editor/ReleephRequestTransactionalEditor.php',
- 'ReleephRequestTypeaheadControl' => 'applications/releeph/view/request/ReleephRequestTypeaheadControl.php',
- 'ReleephRequestTypeaheadController' => 'applications/releeph/controller/request/ReleephRequestTypeaheadController.php',
- 'ReleephRequestView' => 'applications/releeph/view/ReleephRequestView.php',
- 'ReleephRequestViewController' => 'applications/releeph/controller/request/ReleephRequestViewController.php',
- 'ReleephRequestorFieldSpecification' => 'applications/releeph/field/specification/ReleephRequestorFieldSpecification.php',
- 'ReleephRevisionFieldSpecification' => 'applications/releeph/field/specification/ReleephRevisionFieldSpecification.php',
- 'ReleephSeverityFieldSpecification' => 'applications/releeph/field/specification/ReleephSeverityFieldSpecification.php',
- 'ReleephSummaryFieldSpecification' => 'applications/releeph/field/specification/ReleephSummaryFieldSpecification.php',
- 'ReleephWorkCanPushConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkCanPushConduitAPIMethod.php',
- 'ReleephWorkGetAuthorInfoConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkGetAuthorInfoConduitAPIMethod.php',
- 'ReleephWorkGetBranchCommitMessageConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkGetBranchCommitMessageConduitAPIMethod.php',
- 'ReleephWorkGetBranchConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkGetBranchConduitAPIMethod.php',
- 'ReleephWorkGetCommitMessageConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkGetCommitMessageConduitAPIMethod.php',
- 'ReleephWorkNextRequestConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkNextRequestConduitAPIMethod.php',
- 'ReleephWorkRecordConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkRecordConduitAPIMethod.php',
- 'ReleephWorkRecordPickStatusConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkRecordPickStatusConduitAPIMethod.php',
'RemarkupProcessConduitAPIMethod' => 'applications/remarkup/conduit/RemarkupProcessConduitAPIMethod.php',
+ 'RemarkupValue' => 'applications/remarkup/RemarkupValue.php',
'RepositoryConduitAPIMethod' => 'applications/repository/conduit/RepositoryConduitAPIMethod.php',
'RepositoryQueryConduitAPIMethod' => 'applications/repository/conduit/RepositoryQueryConduitAPIMethod.php',
'ShellLogView' => 'applications/harbormaster/view/ShellLogView.php',
'SlowvoteConduitAPIMethod' => 'applications/slowvote/conduit/SlowvoteConduitAPIMethod.php',
'SlowvoteEmbedView' => 'applications/slowvote/view/SlowvoteEmbedView.php',
'SlowvoteInfoConduitAPIMethod' => 'applications/slowvote/conduit/SlowvoteInfoConduitAPIMethod.php',
+ 'SlowvotePollResponseVisibility' => 'applications/slowvote/constants/SlowvotePollResponseVisibility.php',
+ 'SlowvotePollStatus' => 'applications/slowvote/constants/SlowvotePollStatus.php',
+ 'SlowvotePollVotingMethod' => 'applications/slowvote/constants/SlowvotePollVotingMethod.php',
'SlowvoteRemarkupRule' => 'applications/slowvote/remarkup/SlowvoteRemarkupRule.php',
'SlowvoteSearchConduitAPIMethod' => 'applications/slowvote/conduit/SlowvoteSearchConduitAPIMethod.php',
'SubscriptionListDialogBuilder' => 'applications/subscriptions/view/SubscriptionListDialogBuilder.php',
'SubscriptionListStringBuilder' => 'applications/subscriptions/view/SubscriptionListStringBuilder.php',
'TokenConduitAPIMethod' => 'applications/tokens/conduit/TokenConduitAPIMethod.php',
'TokenGiveConduitAPIMethod' => 'applications/tokens/conduit/TokenGiveConduitAPIMethod.php',
'TokenGivenConduitAPIMethod' => 'applications/tokens/conduit/TokenGivenConduitAPIMethod.php',
'TokenQueryConduitAPIMethod' => 'applications/tokens/conduit/TokenQueryConduitAPIMethod.php',
'TransactionSearchConduitAPIMethod' => 'applications/transactions/conduit/TransactionSearchConduitAPIMethod.php',
'UserConduitAPIMethod' => 'applications/people/conduit/UserConduitAPIMethod.php',
'UserDisableConduitAPIMethod' => 'applications/people/conduit/UserDisableConduitAPIMethod.php',
'UserEditConduitAPIMethod' => 'applications/people/conduit/UserEditConduitAPIMethod.php',
'UserEnableConduitAPIMethod' => 'applications/people/conduit/UserEnableConduitAPIMethod.php',
'UserFindConduitAPIMethod' => 'applications/people/conduit/UserFindConduitAPIMethod.php',
'UserQueryConduitAPIMethod' => 'applications/people/conduit/UserQueryConduitAPIMethod.php',
'UserSearchConduitAPIMethod' => 'applications/people/conduit/UserSearchConduitAPIMethod.php',
'UserWhoAmIConduitAPIMethod' => 'applications/people/conduit/UserWhoAmIConduitAPIMethod.php',
),
'function' => array(
'celerity_generate_unique_node_id' => 'applications/celerity/api.php',
'celerity_get_resource_uri' => 'applications/celerity/api.php',
'hsprintf' => 'infrastructure/markup/render.php',
'javelin_tag' => 'infrastructure/javelin/markup.php',
'phabricator_absolute_datetime' => 'view/viewutils.php',
'phabricator_date' => 'view/viewutils.php',
'phabricator_datetime' => 'view/viewutils.php',
'phabricator_datetimezone' => 'view/viewutils.php',
'phabricator_dual_datetime' => 'view/viewutils.php',
'phabricator_form' => 'infrastructure/javelin/markup.php',
'phabricator_format_local_time' => 'view/viewutils.php',
'phabricator_relative_date' => 'view/viewutils.php',
'phabricator_time' => 'view/viewutils.php',
'phid_get_subtype' => 'applications/phid/utils.php',
'phid_get_type' => 'applications/phid/utils.php',
'phid_group_by_type' => 'applications/phid/utils.php',
'phutil_escape_html' => 'infrastructure/markup/render.php',
'phutil_escape_html_newlines' => 'infrastructure/markup/render.php',
'phutil_implode_html' => 'infrastructure/markup/render.php',
'phutil_safe_html' => 'infrastructure/markup/render.php',
'phutil_tag' => 'infrastructure/markup/render.php',
'phutil_tag_div' => 'infrastructure/markup/render.php',
'qsprintf' => 'infrastructure/storage/xsprintf/qsprintf.php',
'qsprintf_check_scalar_type' => 'infrastructure/storage/xsprintf/qsprintf.php',
'qsprintf_check_type' => 'infrastructure/storage/xsprintf/qsprintf.php',
'queryfx' => 'infrastructure/storage/xsprintf/queryfx.php',
'queryfx_all' => 'infrastructure/storage/xsprintf/queryfx.php',
'queryfx_one' => 'infrastructure/storage/xsprintf/queryfx.php',
'require_celerity_resource' => 'applications/celerity/api.php',
'vqsprintf' => 'infrastructure/storage/xsprintf/qsprintf.php',
'xsprintf_query' => 'infrastructure/storage/xsprintf/qsprintf.php',
),
'xmap' => array(
'AlmanacAddress' => 'Phobject',
'AlmanacBinding' => array(
'AlmanacDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'AlmanacPropertyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorExtendedPolicyInterface',
'PhabricatorConduitResultInterface',
),
'AlmanacBindingDeletePropertyTransaction' => 'AlmanacBindingTransactionType',
'AlmanacBindingDisableController' => 'AlmanacServiceController',
'AlmanacBindingDisableTransaction' => 'AlmanacBindingTransactionType',
'AlmanacBindingEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'AlmanacBindingEditController' => 'AlmanacServiceController',
'AlmanacBindingEditEngine' => 'PhabricatorEditEngine',
'AlmanacBindingEditor' => 'AlmanacEditor',
'AlmanacBindingInterfaceTransaction' => 'AlmanacBindingTransactionType',
'AlmanacBindingPHIDType' => 'PhabricatorPHIDType',
'AlmanacBindingPropertyEditEngine' => 'AlmanacPropertyEditEngine',
'AlmanacBindingQuery' => 'AlmanacQuery',
'AlmanacBindingSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'AlmanacBindingSearchEngine' => 'PhabricatorApplicationSearchEngine',
'AlmanacBindingServiceTransaction' => 'AlmanacBindingTransactionType',
'AlmanacBindingSetPropertyTransaction' => 'AlmanacBindingTransactionType',
'AlmanacBindingTableView' => 'AphrontView',
'AlmanacBindingTransaction' => 'AlmanacModularTransaction',
'AlmanacBindingTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'AlmanacBindingTransactionType' => 'AlmanacTransactionType',
'AlmanacBindingViewController' => 'AlmanacServiceController',
'AlmanacBindingsSearchEngineAttachment' => 'AlmanacSearchEngineAttachment',
'AlmanacCacheEngineExtension' => 'PhabricatorCacheEngineExtension',
'AlmanacClusterDatabaseServiceType' => 'AlmanacClusterServiceType',
'AlmanacClusterRepositoryServiceType' => 'AlmanacClusterServiceType',
'AlmanacClusterServiceType' => 'AlmanacServiceType',
'AlmanacConsoleController' => 'AlmanacController',
'AlmanacController' => 'PhabricatorController',
'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability',
'AlmanacCreateNamespacesCapability' => 'PhabricatorPolicyCapability',
'AlmanacCreateNetworksCapability' => 'PhabricatorPolicyCapability',
'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability',
'AlmanacCustomServiceType' => 'AlmanacServiceType',
'AlmanacDAO' => 'PhabricatorLiskDAO',
'AlmanacDeletePropertyEditField' => 'PhabricatorEditField',
'AlmanacDeletePropertyEditType' => 'PhabricatorEditType',
'AlmanacDevice' => array(
'AlmanacDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorProjectInterface',
'PhabricatorSSHPublicKeyInterface',
'AlmanacPropertyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorNgramsInterface',
'PhabricatorConduitResultInterface',
'PhabricatorExtendedPolicyInterface',
),
'AlmanacDeviceController' => 'AlmanacController',
'AlmanacDeviceDeletePropertyTransaction' => 'AlmanacDeviceTransactionType',
'AlmanacDeviceEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'AlmanacDeviceEditController' => 'AlmanacDeviceController',
'AlmanacDeviceEditEngine' => 'PhabricatorEditEngine',
'AlmanacDeviceEditor' => 'AlmanacEditor',
'AlmanacDeviceListController' => 'AlmanacDeviceController',
'AlmanacDeviceNameNgrams' => 'PhabricatorSearchNgrams',
'AlmanacDeviceNameTransaction' => 'AlmanacDeviceTransactionType',
'AlmanacDevicePHIDType' => 'PhabricatorPHIDType',
'AlmanacDevicePropertyEditEngine' => 'AlmanacPropertyEditEngine',
'AlmanacDeviceQuery' => 'AlmanacQuery',
'AlmanacDeviceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine',
'AlmanacDeviceSetPropertyTransaction' => 'AlmanacDeviceTransactionType',
'AlmanacDeviceStatus' => 'Phobject',
'AlmanacDeviceStatusTransaction' => 'AlmanacDeviceTransactionType',
'AlmanacDeviceTransaction' => 'AlmanacModularTransaction',
'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'AlmanacDeviceTransactionType' => 'AlmanacTransactionType',
'AlmanacDeviceViewController' => 'AlmanacDeviceController',
'AlmanacDrydockPoolServiceType' => 'AlmanacServiceType',
'AlmanacEditor' => 'PhabricatorApplicationTransactionEditor',
'AlmanacInterface' => array(
'AlmanacDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorExtendedPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorConduitResultInterface',
),
'AlmanacInterfaceAddressTransaction' => 'AlmanacInterfaceTransactionType',
'AlmanacInterfaceDatasource' => 'PhabricatorTypeaheadDatasource',
'AlmanacInterfaceDeleteController' => 'AlmanacDeviceController',
'AlmanacInterfaceDestroyTransaction' => 'AlmanacInterfaceTransactionType',
'AlmanacInterfaceDeviceTransaction' => 'AlmanacInterfaceTransactionType',
'AlmanacInterfaceEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'AlmanacInterfaceEditController' => 'AlmanacDeviceController',
'AlmanacInterfaceEditEngine' => 'PhabricatorEditEngine',
'AlmanacInterfaceEditor' => 'AlmanacEditor',
'AlmanacInterfaceNetworkTransaction' => 'AlmanacInterfaceTransactionType',
'AlmanacInterfacePHIDType' => 'PhabricatorPHIDType',
'AlmanacInterfacePortTransaction' => 'AlmanacInterfaceTransactionType',
'AlmanacInterfaceQuery' => 'AlmanacQuery',
'AlmanacInterfaceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'AlmanacInterfaceSearchEngine' => 'PhabricatorApplicationSearchEngine',
'AlmanacInterfaceTableView' => 'AphrontView',
'AlmanacInterfaceTransaction' => 'AlmanacModularTransaction',
'AlmanacInterfaceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'AlmanacInterfaceTransactionType' => 'AlmanacTransactionType',
'AlmanacKeys' => 'Phobject',
'AlmanacManageClusterServicesCapability' => 'PhabricatorPolicyCapability',
'AlmanacManagementRegisterWorkflow' => 'AlmanacManagementWorkflow',
'AlmanacManagementTrustKeyWorkflow' => 'AlmanacManagementWorkflow',
'AlmanacManagementUntrustKeyWorkflow' => 'AlmanacManagementWorkflow',
'AlmanacManagementWorkflow' => 'PhabricatorManagementWorkflow',
'AlmanacModularTransaction' => 'PhabricatorModularTransaction',
'AlmanacNames' => 'Phobject',
'AlmanacNamesTestCase' => 'PhabricatorTestCase',
'AlmanacNamespace' => array(
'AlmanacDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorProjectInterface',
'PhabricatorDestructibleInterface',
'PhabricatorNgramsInterface',
'PhabricatorConduitResultInterface',
),
'AlmanacNamespaceController' => 'AlmanacController',
'AlmanacNamespaceEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'AlmanacNamespaceEditController' => 'AlmanacNamespaceController',
'AlmanacNamespaceEditEngine' => 'PhabricatorEditEngine',
'AlmanacNamespaceEditor' => 'AlmanacEditor',
'AlmanacNamespaceListController' => 'AlmanacNamespaceController',
'AlmanacNamespaceNameNgrams' => 'PhabricatorSearchNgrams',
'AlmanacNamespaceNameTransaction' => 'AlmanacNamespaceTransactionType',
'AlmanacNamespacePHIDType' => 'PhabricatorPHIDType',
'AlmanacNamespaceQuery' => 'AlmanacQuery',
'AlmanacNamespaceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'AlmanacNamespaceSearchEngine' => 'PhabricatorApplicationSearchEngine',
'AlmanacNamespaceTransaction' => 'AlmanacModularTransaction',
'AlmanacNamespaceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'AlmanacNamespaceTransactionType' => 'AlmanacTransactionType',
'AlmanacNamespaceViewController' => 'AlmanacNamespaceController',
'AlmanacNetwork' => array(
'AlmanacDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorNgramsInterface',
'PhabricatorConduitResultInterface',
),
'AlmanacNetworkController' => 'AlmanacController',
'AlmanacNetworkEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'AlmanacNetworkEditController' => 'AlmanacNetworkController',
'AlmanacNetworkEditEngine' => 'PhabricatorEditEngine',
'AlmanacNetworkEditor' => 'AlmanacEditor',
'AlmanacNetworkListController' => 'AlmanacNetworkController',
'AlmanacNetworkNameNgrams' => 'PhabricatorSearchNgrams',
'AlmanacNetworkNameTransaction' => 'AlmanacNetworkTransactionType',
'AlmanacNetworkPHIDType' => 'PhabricatorPHIDType',
'AlmanacNetworkQuery' => 'AlmanacQuery',
'AlmanacNetworkSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'AlmanacNetworkSearchEngine' => 'PhabricatorApplicationSearchEngine',
'AlmanacNetworkTransaction' => 'AlmanacModularTransaction',
'AlmanacNetworkTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'AlmanacNetworkTransactionType' => 'AlmanacTransactionType',
'AlmanacNetworkViewController' => 'AlmanacNetworkController',
'AlmanacPropertiesDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
'AlmanacPropertiesEditEngineExtension' => 'PhabricatorEditEngineExtension',
'AlmanacPropertiesSearchEngineAttachment' => 'AlmanacSearchEngineAttachment',
'AlmanacProperty' => array(
'AlmanacDAO',
'PhabricatorPolicyInterface',
),
'AlmanacPropertyController' => 'AlmanacController',
'AlmanacPropertyDeleteController' => 'AlmanacPropertyController',
'AlmanacPropertyEditController' => 'AlmanacPropertyController',
'AlmanacPropertyEditEngine' => 'PhabricatorEditEngine',
'AlmanacPropertyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'AlmanacQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'AlmanacSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'AlmanacSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'AlmanacService' => array(
'AlmanacDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorProjectInterface',
'AlmanacPropertyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorNgramsInterface',
'PhabricatorConduitResultInterface',
'PhabricatorExtendedPolicyInterface',
),
'AlmanacServiceController' => 'AlmanacController',
'AlmanacServiceDatasource' => 'PhabricatorTypeaheadDatasource',
'AlmanacServiceDeletePropertyTransaction' => 'AlmanacServiceTransactionType',
'AlmanacServiceEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'AlmanacServiceEditController' => 'AlmanacServiceController',
'AlmanacServiceEditEngine' => 'PhabricatorEditEngine',
'AlmanacServiceEditor' => 'AlmanacEditor',
'AlmanacServiceListController' => 'AlmanacServiceController',
'AlmanacServiceNameNgrams' => 'PhabricatorSearchNgrams',
'AlmanacServiceNameTransaction' => 'AlmanacServiceTransactionType',
'AlmanacServicePHIDType' => 'PhabricatorPHIDType',
'AlmanacServicePropertyEditEngine' => 'AlmanacPropertyEditEngine',
'AlmanacServiceQuery' => 'AlmanacQuery',
'AlmanacServiceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'AlmanacServiceSearchEngine' => 'PhabricatorApplicationSearchEngine',
'AlmanacServiceSetPropertyTransaction' => 'AlmanacServiceTransactionType',
'AlmanacServiceTransaction' => 'AlmanacModularTransaction',
'AlmanacServiceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'AlmanacServiceTransactionType' => 'AlmanacTransactionType',
'AlmanacServiceType' => 'Phobject',
'AlmanacServiceTypeDatasource' => 'PhabricatorTypeaheadDatasource',
'AlmanacServiceTypeTestCase' => 'PhabricatorTestCase',
'AlmanacServiceTypeTransaction' => 'AlmanacServiceTransactionType',
'AlmanacServiceViewController' => 'AlmanacServiceController',
'AlmanacSetPropertyEditField' => 'PhabricatorEditField',
'AlmanacSetPropertyEditType' => 'PhabricatorEditType',
'AlmanacTransactionType' => 'PhabricatorModularTransactionType',
'AphlictDropdownDataQuery' => 'Phobject',
'Aphront304Response' => 'AphrontResponse',
'Aphront400Response' => 'AphrontResponse',
'Aphront403Response' => 'AphrontHTMLResponse',
'Aphront404Response' => 'AphrontHTMLResponse',
'AphrontAccessDeniedQueryException' => 'AphrontQueryException',
'AphrontAjaxResponse' => 'AphrontResponse',
'AphrontApplicationConfiguration' => 'Phobject',
'AphrontAutoIDView' => 'AphrontView',
'AphrontBarView' => 'AphrontView',
'AphrontBaseMySQLDatabaseConnection' => 'AphrontDatabaseConnection',
'AphrontBoolHTTPParameterType' => 'AphrontHTTPParameterType',
'AphrontCalendarEventView' => 'AphrontView',
'AphrontCharacterSetQueryException' => 'AphrontQueryException',
'AphrontConnectionLostQueryException' => 'AphrontRecoverableQueryException',
'AphrontConnectionQueryException' => 'AphrontQueryException',
'AphrontController' => 'Phobject',
'AphrontCountQueryException' => 'AphrontQueryException',
'AphrontCursorPagerView' => 'AphrontView',
'AphrontDatabaseConnection' => array(
'Phobject',
'PhutilQsprintfInterface',
),
'AphrontDatabaseTableRef' => array(
'Phobject',
'AphrontDatabaseTableRefInterface',
),
'AphrontDatabaseTransactionState' => 'Phobject',
'AphrontDeadlockQueryException' => 'AphrontRecoverableQueryException',
'AphrontDialogResponse' => 'AphrontResponse',
'AphrontDialogView' => array(
'AphrontView',
'AphrontResponseProducerInterface',
),
'AphrontDuplicateKeyQueryException' => 'AphrontQueryException',
'AphrontEpochHTTPParameterType' => 'AphrontHTTPParameterType',
'AphrontException' => 'Exception',
'AphrontFileHTTPParameterType' => 'AphrontHTTPParameterType',
'AphrontFileResponse' => 'AphrontResponse',
'AphrontFormCheckboxControl' => 'AphrontFormControl',
'AphrontFormControl' => 'AphrontView',
'AphrontFormDateControl' => 'AphrontFormControl',
'AphrontFormDateControlValue' => 'Phobject',
'AphrontFormDividerControl' => 'AphrontFormControl',
'AphrontFormFileControl' => 'AphrontFormControl',
'AphrontFormHandlesControl' => 'AphrontFormControl',
'AphrontFormMarkupControl' => 'AphrontFormControl',
'AphrontFormPasswordControl' => 'AphrontFormControl',
'AphrontFormPolicyControl' => 'AphrontFormControl',
'AphrontFormRadioButtonControl' => 'AphrontFormControl',
'AphrontFormRecaptchaControl' => 'AphrontFormControl',
'AphrontFormSelectControl' => 'AphrontFormControl',
'AphrontFormStaticControl' => 'AphrontFormControl',
'AphrontFormSubmitControl' => 'AphrontFormControl',
'AphrontFormTextAreaControl' => 'AphrontFormControl',
'AphrontFormTextControl' => 'AphrontFormControl',
'AphrontFormTextWithSubmitControl' => 'AphrontFormControl',
'AphrontFormTokenizerControl' => 'AphrontFormControl',
'AphrontFormTypeaheadControl' => 'AphrontFormControl',
'AphrontFormView' => 'AphrontView',
'AphrontGlyphBarView' => 'AphrontBarView',
'AphrontHTMLResponse' => 'AphrontResponse',
'AphrontHTTPHeaderParser' => 'Phobject',
'AphrontHTTPHeaderParserTestCase' => 'PhutilTestCase',
'AphrontHTTPParameterType' => 'Phobject',
'AphrontHTTPProxyResponse' => 'AphrontResponse',
'AphrontHTTPSink' => 'Phobject',
'AphrontHTTPSinkTestCase' => 'PhabricatorTestCase',
'AphrontIntHTTPParameterType' => 'AphrontHTTPParameterType',
'AphrontInvalidCredentialsQueryException' => 'AphrontQueryException',
'AphrontIsolatedDatabaseConnection' => 'AphrontDatabaseConnection',
'AphrontIsolatedDatabaseConnectionTestCase' => 'PhabricatorTestCase',
'AphrontIsolatedHTTPSink' => 'AphrontHTTPSink',
+ 'AphrontJSONHTTPParameterType' => 'AphrontHTTPParameterType',
'AphrontJSONResponse' => 'AphrontResponse',
'AphrontJavelinView' => 'AphrontView',
'AphrontKeyboardShortcutsAvailableView' => 'AphrontView',
'AphrontListFilterView' => 'AphrontView',
'AphrontListHTTPParameterType' => 'AphrontHTTPParameterType',
'AphrontLockTimeoutQueryException' => 'AphrontRecoverableQueryException',
'AphrontMalformedRequestException' => 'AphrontException',
'AphrontMoreView' => 'AphrontView',
'AphrontMultiColumnView' => 'AphrontView',
'AphrontMultipartParser' => 'Phobject',
'AphrontMultipartParserTestCase' => 'PhutilTestCase',
'AphrontMultipartPart' => 'Phobject',
'AphrontMySQLDatabaseConnection' => 'AphrontBaseMySQLDatabaseConnection',
'AphrontMySQLDatabaseConnectionTestCase' => 'PhabricatorTestCase',
'AphrontMySQLiDatabaseConnection' => 'AphrontBaseMySQLDatabaseConnection',
'AphrontNotSupportedQueryException' => 'AphrontQueryException',
'AphrontNullView' => 'AphrontView',
'AphrontObjectMissingQueryException' => 'AphrontQueryException',
'AphrontPHIDHTTPParameterType' => 'AphrontHTTPParameterType',
'AphrontPHIDListHTTPParameterType' => 'AphrontListHTTPParameterType',
'AphrontPHPHTTPSink' => 'AphrontHTTPSink',
'AphrontPageView' => 'AphrontView',
'AphrontParameterQueryException' => 'AphrontQueryException',
'AphrontPlainTextResponse' => 'AphrontResponse',
'AphrontProgressBarView' => 'AphrontBarView',
'AphrontProjectListHTTPParameterType' => 'AphrontListHTTPParameterType',
'AphrontProxyResponse' => array(
'AphrontResponse',
'AphrontResponseProducerInterface',
),
'AphrontQueryException' => 'Exception',
'AphrontQueryTimeoutQueryException' => 'AphrontRecoverableQueryException',
'AphrontRecoverableQueryException' => 'AphrontQueryException',
'AphrontRedirectResponse' => 'AphrontResponse',
'AphrontRedirectResponseTestCase' => 'PhabricatorTestCase',
'AphrontReloadResponse' => 'AphrontRedirectResponse',
+ 'AphrontRemarkupHTTPParameterType' => 'AphrontHTTPParameterType',
'AphrontRequest' => 'Phobject',
'AphrontRequestExceptionHandler' => 'Phobject',
'AphrontRequestStream' => 'Phobject',
'AphrontRequestTestCase' => 'PhabricatorTestCase',
'AphrontResponse' => 'Phobject',
'AphrontRoutingMap' => 'Phobject',
'AphrontRoutingMapTestCase' => 'PhabricatorTestCase',
'AphrontRoutingResult' => 'Phobject',
'AphrontSchemaQueryException' => 'AphrontQueryException',
'AphrontScopedUnguardedWriteCapability' => 'Phobject',
'AphrontSelectHTTPParameterType' => 'AphrontHTTPParameterType',
'AphrontSideNavFilterView' => 'AphrontView',
'AphrontSite' => 'Phobject',
'AphrontStackTraceView' => 'AphrontView',
'AphrontStandaloneHTMLResponse' => 'AphrontHTMLResponse',
'AphrontStringHTTPParameterType' => 'AphrontHTTPParameterType',
'AphrontStringListHTTPParameterType' => 'AphrontListHTTPParameterType',
'AphrontTableView' => 'AphrontView',
'AphrontTagView' => 'AphrontView',
'AphrontTokenizerTemplateView' => 'AphrontView',
'AphrontTypeaheadTemplateView' => 'AphrontView',
'AphrontUnhandledExceptionResponse' => 'AphrontStandaloneHTMLResponse',
'AphrontUserListHTTPParameterType' => 'AphrontListHTTPParameterType',
'AphrontView' => array(
'Phobject',
'PhutilSafeHTMLProducerInterface',
),
'AphrontWebpageResponse' => 'AphrontHTMLResponse',
'AphrontWriteGuard' => 'Phobject',
'ArcanistConduitAPIMethod' => 'ConduitAPIMethod',
'AuditConduitAPIMethod' => 'ConduitAPIMethod',
'AuditQueryConduitAPIMethod' => 'AuditConduitAPIMethod',
'AuthManageProvidersCapability' => 'PhabricatorPolicyCapability',
'BulkParameterType' => 'Phobject',
'BulkPointsParameterType' => 'BulkParameterType',
'BulkRemarkupParameterType' => 'BulkParameterType',
'BulkSelectParameterType' => 'BulkParameterType',
'BulkStringParameterType' => 'BulkParameterType',
'BulkTokenizerParameterType' => 'BulkParameterType',
'CalendarTimeUtil' => 'Phobject',
'CalendarTimeUtilTestCase' => 'PhabricatorTestCase',
'CelerityAPI' => 'Phobject',
'CelerityDarkModePostprocessor' => 'CelerityPostprocessor',
'CelerityDefaultPostprocessor' => 'CelerityPostprocessor',
'CelerityHighContrastPostprocessor' => 'CelerityPostprocessor',
'CelerityLargeFontPostprocessor' => 'CelerityPostprocessor',
'CelerityManagementMapWorkflow' => 'CelerityManagementWorkflow',
'CelerityManagementSyntaxWorkflow' => 'CelerityManagementWorkflow',
'CelerityManagementWorkflow' => 'PhabricatorManagementWorkflow',
'CelerityPhabricatorResourceController' => 'CelerityResourceController',
'CelerityPhabricatorResources' => 'CelerityResourcesOnDisk',
'CelerityPhysicalResources' => 'CelerityResources',
'CelerityPhysicalResourcesTestCase' => 'PhabricatorTestCase',
'CelerityPostprocessor' => 'Phobject',
'CelerityPostprocessorTestCase' => 'PhabricatorTestCase',
'CelerityRedGreenPostprocessor' => 'CelerityPostprocessor',
'CelerityResourceController' => 'PhabricatorController',
'CelerityResourceGraph' => 'AbstractDirectedGraph',
'CelerityResourceMap' => 'Phobject',
'CelerityResourceMapGenerator' => 'Phobject',
'CelerityResourceTransformer' => 'Phobject',
'CelerityResourceTransformerTestCase' => 'PhabricatorTestCase',
'CelerityResources' => 'Phobject',
'CelerityResourcesOnDisk' => 'CelerityPhysicalResources',
'CeleritySpriteGenerator' => 'Phobject',
'CelerityStaticResourceResponse' => 'Phobject',
'ChatLogConduitAPIMethod' => 'ConduitAPIMethod',
'ChatLogQueryConduitAPIMethod' => 'ChatLogConduitAPIMethod',
'ChatLogRecordConduitAPIMethod' => 'ChatLogConduitAPIMethod',
'ConduitAPIDocumentationPage' => 'Phobject',
'ConduitAPIMethod' => array(
'Phobject',
'PhabricatorPolicyInterface',
),
'ConduitAPIMethodTestCase' => 'PhabricatorTestCase',
'ConduitAPIRequest' => 'Phobject',
'ConduitAPIResponse' => 'Phobject',
'ConduitApplicationNotInstalledException' => 'ConduitMethodNotFoundException',
'ConduitBoolParameterType' => 'ConduitParameterType',
'ConduitCall' => 'Phobject',
'ConduitCallTestCase' => 'PhabricatorTestCase',
'ConduitColumnsParameterType' => 'ConduitParameterType',
'ConduitConnectConduitAPIMethod' => 'ConduitAPIMethod',
'ConduitConstantDescription' => 'Phobject',
'ConduitEpochParameterType' => 'ConduitParameterType',
'ConduitException' => 'Exception',
'ConduitGetCapabilitiesConduitAPIMethod' => 'ConduitAPIMethod',
'ConduitGetCertificateConduitAPIMethod' => 'ConduitAPIMethod',
'ConduitIntListParameterType' => 'ConduitListParameterType',
'ConduitIntParameterType' => 'ConduitParameterType',
'ConduitListParameterType' => 'ConduitParameterType',
'ConduitLogGarbageCollector' => 'PhabricatorGarbageCollector',
'ConduitMethodDoesNotExistException' => 'ConduitMethodNotFoundException',
'ConduitMethodNotFoundException' => 'ConduitException',
'ConduitPHIDListParameterType' => 'ConduitListParameterType',
'ConduitPHIDParameterType' => 'ConduitParameterType',
'ConduitParameterType' => 'Phobject',
'ConduitPingConduitAPIMethod' => 'ConduitAPIMethod',
'ConduitPointsParameterType' => 'ConduitParameterType',
'ConduitProjectListParameterType' => 'ConduitListParameterType',
'ConduitQueryConduitAPIMethod' => 'ConduitAPIMethod',
'ConduitResultSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'ConduitSSHWorkflow' => 'PhabricatorSSHWorkflow',
'ConduitStringListParameterType' => 'ConduitListParameterType',
'ConduitStringParameterType' => 'ConduitParameterType',
'ConduitTokenGarbageCollector' => 'PhabricatorGarbageCollector',
'ConduitUserListParameterType' => 'ConduitListParameterType',
'ConduitUserParameterType' => 'ConduitParameterType',
'ConduitWildParameterType' => 'ConduitParameterType',
'ConpherenceColumnViewController' => 'ConpherenceController',
'ConpherenceConduitAPIMethod' => 'ConduitAPIMethod',
'ConpherenceConstants' => 'Phobject',
'ConpherenceController' => 'PhabricatorController',
'ConpherenceCreateThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod',
'ConpherenceDAO' => 'PhabricatorLiskDAO',
'ConpherenceDurableColumnView' => 'AphrontTagView',
'ConpherenceEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'ConpherenceEditEngine' => 'PhabricatorEditEngine',
'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor',
'ConpherenceFulltextQuery' => 'PhabricatorOffsetPagedQuery',
'ConpherenceIndex' => 'ConpherenceDAO',
'ConpherenceLayoutView' => 'AphrontTagView',
'ConpherenceListController' => 'ConpherenceController',
'ConpherenceMenuItemView' => 'AphrontTagView',
'ConpherenceNotificationPanelController' => 'ConpherenceController',
'ConpherenceParticipant' => 'ConpherenceDAO',
'ConpherenceParticipantController' => 'ConpherenceController',
'ConpherenceParticipantCountQuery' => 'PhabricatorOffsetPagedQuery',
'ConpherenceParticipantQuery' => 'PhabricatorOffsetPagedQuery',
'ConpherenceParticipantView' => 'AphrontView',
'ConpherenceQueryThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod',
'ConpherenceQueryTransactionConduitAPIMethod' => 'ConpherenceConduitAPIMethod',
'ConpherenceReplyHandler' => 'PhabricatorMailReplyHandler',
'ConpherenceRoomEditController' => 'ConpherenceController',
'ConpherenceRoomListController' => 'ConpherenceController',
'ConpherenceRoomPictureController' => 'ConpherenceController',
'ConpherenceRoomPreferencesController' => 'ConpherenceController',
'ConpherenceRoomSettings' => 'ConpherenceConstants',
'ConpherenceRoomTestCase' => 'ConpherenceTestCase',
'ConpherenceSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'ConpherenceTestCase' => 'PhabricatorTestCase',
'ConpherenceThread' => array(
'ConpherenceDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorMentionableInterface',
'PhabricatorDestructibleInterface',
'PhabricatorNgramsInterface',
),
'ConpherenceThreadDatasource' => 'PhabricatorTypeaheadDatasource',
'ConpherenceThreadDateMarkerTransaction' => 'ConpherenceThreadTransactionType',
'ConpherenceThreadIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
'ConpherenceThreadListView' => 'AphrontView',
'ConpherenceThreadMailReceiver' => 'PhabricatorObjectMailReceiver',
'ConpherenceThreadMembersPolicyRule' => 'PhabricatorPolicyRule',
'ConpherenceThreadParticipantsTransaction' => 'ConpherenceThreadTransactionType',
'ConpherenceThreadPictureTransaction' => 'ConpherenceThreadTransactionType',
'ConpherenceThreadQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'ConpherenceThreadRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'ConpherenceThreadSearchController' => 'ConpherenceController',
'ConpherenceThreadSearchEngine' => 'PhabricatorApplicationSearchEngine',
'ConpherenceThreadTitleNgrams' => 'PhabricatorSearchNgrams',
'ConpherenceThreadTitleTransaction' => 'ConpherenceThreadTransactionType',
'ConpherenceThreadTopicTransaction' => 'ConpherenceThreadTransactionType',
'ConpherenceThreadTransactionType' => 'PhabricatorModularTransactionType',
'ConpherenceTransaction' => 'PhabricatorModularTransaction',
'ConpherenceTransactionComment' => 'PhabricatorApplicationTransactionComment',
'ConpherenceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'ConpherenceTransactionRenderer' => 'Phobject',
'ConpherenceTransactionView' => 'AphrontView',
'ConpherenceUpdateActions' => 'ConpherenceConstants',
'ConpherenceUpdateController' => 'ConpherenceController',
'ConpherenceUpdateThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod',
'ConpherenceViewController' => 'ConpherenceController',
'CountdownEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'CountdownSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'DarkConsoleController' => 'PhabricatorController',
'DarkConsoleCore' => 'Phobject',
'DarkConsoleDataController' => 'PhabricatorController',
'DarkConsoleErrorLogPlugin' => 'DarkConsolePlugin',
'DarkConsoleErrorLogPluginAPI' => 'Phobject',
'DarkConsoleEventPlugin' => 'DarkConsolePlugin',
'DarkConsoleEventPluginAPI' => 'PhabricatorEventListener',
'DarkConsolePlugin' => 'Phobject',
'DarkConsoleRealtimePlugin' => 'DarkConsolePlugin',
'DarkConsoleRequestPlugin' => 'DarkConsolePlugin',
'DarkConsoleServicesPlugin' => 'DarkConsolePlugin',
'DarkConsoleStartupPlugin' => 'DarkConsolePlugin',
'DarkConsoleXHProfPlugin' => 'DarkConsolePlugin',
'DarkConsoleXHProfPluginAPI' => 'Phobject',
'DifferentialAction' => 'Phobject',
'DifferentialActionEmailCommand' => 'MetaMTAEmailTransactionCommand',
'DifferentialAdjustmentMapTestCase' => 'PhabricatorTestCase',
'DifferentialAffectedPath' => 'DifferentialDAO',
'DifferentialAffectedPathEngine' => 'Phobject',
'DifferentialAsanaRepresentationField' => 'DifferentialCustomField',
'DifferentialAuditorsCommitMessageField' => 'DifferentialCommitMessageCustomField',
'DifferentialAuditorsField' => 'DifferentialStoredCustomField',
'DifferentialBlameRevisionCommitMessageField' => 'DifferentialCommitMessageCustomField',
'DifferentialBlameRevisionField' => 'DifferentialStoredCustomField',
'DifferentialBlockHeraldAction' => 'HeraldAction',
'DifferentialBlockingReviewerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DifferentialBranchField' => 'DifferentialCustomField',
'DifferentialBuildableEngine' => 'HarbormasterBuildableEngine',
'DifferentialChangeDetailMailView' => 'DifferentialMailView',
'DifferentialChangeHeraldFieldGroup' => 'HeraldFieldGroup',
'DifferentialChangeType' => 'Phobject',
'DifferentialChangesSinceLastUpdateField' => 'DifferentialCustomField',
'DifferentialChangeset' => array(
'DifferentialDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorConduitResultInterface',
),
'DifferentialChangesetDetailView' => 'AphrontView',
'DifferentialChangesetEngine' => 'Phobject',
'DifferentialChangesetHTMLRenderer' => 'DifferentialChangesetRenderer',
'DifferentialChangesetListController' => 'DifferentialController',
'DifferentialChangesetListView' => 'AphrontView',
'DifferentialChangesetOneUpMailRenderer' => 'DifferentialChangesetRenderer',
'DifferentialChangesetOneUpRenderer' => 'DifferentialChangesetHTMLRenderer',
'DifferentialChangesetOneUpTestRenderer' => 'DifferentialChangesetTestRenderer',
'DifferentialChangesetPHIDType' => 'PhabricatorPHIDType',
'DifferentialChangesetParser' => 'Phobject',
'DifferentialChangesetParserTestCase' => 'PhabricatorTestCase',
'DifferentialChangesetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'DifferentialChangesetRenderer' => 'Phobject',
'DifferentialChangesetSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'DifferentialChangesetSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DifferentialChangesetTestRenderer' => 'DifferentialChangesetRenderer',
'DifferentialChangesetTwoUpRenderer' => 'DifferentialChangesetHTMLRenderer',
'DifferentialChangesetTwoUpTestRenderer' => 'DifferentialChangesetTestRenderer',
'DifferentialChangesetViewController' => 'DifferentialController',
'DifferentialCloseConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialCommitMessageCustomField' => 'DifferentialCommitMessageField',
'DifferentialCommitMessageField' => 'Phobject',
'DifferentialCommitMessageFieldTestCase' => 'PhabricatorTestCase',
'DifferentialCommitMessageParser' => 'Phobject',
'DifferentialCommitMessageParserTestCase' => 'PhabricatorTestCase',
'DifferentialCommitsField' => 'DifferentialCustomField',
'DifferentialCommitsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'DifferentialConduitAPIMethod' => 'ConduitAPIMethod',
'DifferentialConflictsCommitMessageField' => 'DifferentialCommitMessageField',
'DifferentialConstantsModule' => 'PhabricatorConfigModule',
'DifferentialController' => 'PhabricatorController',
'DifferentialCoreCustomField' => 'DifferentialCustomField',
'DifferentialCreateCommentConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialCreateDiffConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialCreateInlineConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialCreateMailReceiver' => 'PhabricatorApplicationMailReceiver',
'DifferentialCreateRawDiffConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialCreateRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialCustomField' => 'PhabricatorCustomField',
'DifferentialCustomFieldDependsOnParser' => 'PhabricatorCustomFieldMonogramParser',
'DifferentialCustomFieldDependsOnParserTestCase' => 'PhabricatorTestCase',
'DifferentialCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
'DifferentialCustomFieldRevertsParser' => 'PhabricatorCustomFieldMonogramParser',
'DifferentialCustomFieldRevertsParserTestCase' => 'PhabricatorTestCase',
'DifferentialCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
'DifferentialCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage',
'DifferentialDAO' => 'PhabricatorLiskDAO',
'DifferentialDefaultViewCapability' => 'PhabricatorPolicyCapability',
'DifferentialDiff' => array(
'DifferentialDAO',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
'HarbormasterBuildableInterface',
'HarbormasterCircleCIBuildableInterface',
'HarbormasterBuildkiteBuildableInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorDestructibleInterface',
'PhabricatorConduitResultInterface',
),
'DifferentialDiffAffectedFilesHeraldField' => 'DifferentialDiffHeraldField',
'DifferentialDiffAuthorHeraldField' => 'DifferentialDiffHeraldField',
'DifferentialDiffAuthorProjectsHeraldField' => 'DifferentialDiffHeraldField',
'DifferentialDiffContentAddedHeraldField' => 'DifferentialDiffHeraldField',
'DifferentialDiffContentHeraldField' => 'DifferentialDiffHeraldField',
'DifferentialDiffContentRemovedHeraldField' => 'DifferentialDiffHeraldField',
'DifferentialDiffCreateController' => 'DifferentialController',
'DifferentialDiffEditor' => 'PhabricatorApplicationTransactionEditor',
'DifferentialDiffExtractionEngine' => 'Phobject',
'DifferentialDiffHeraldField' => 'HeraldField',
'DifferentialDiffHeraldFieldGroup' => 'HeraldFieldGroup',
'DifferentialDiffInlineCommentQuery' => 'PhabricatorDiffInlineCommentQuery',
'DifferentialDiffPHIDType' => 'PhabricatorPHIDType',
'DifferentialDiffProperty' => 'DifferentialDAO',
'DifferentialDiffQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'DifferentialDiffRepositoryHeraldField' => 'DifferentialDiffHeraldField',
'DifferentialDiffRepositoryProjectsHeraldField' => 'DifferentialDiffHeraldField',
'DifferentialDiffSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'DifferentialDiffSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DifferentialDiffTestCase' => 'PhutilTestCase',
'DifferentialDiffTransaction' => 'PhabricatorApplicationTransaction',
'DifferentialDiffTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'DifferentialDiffViewController' => 'DifferentialController',
'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher',
'DifferentialDraftField' => 'DifferentialCoreCustomField',
'DifferentialExactUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DifferentialFieldParseException' => 'Exception',
'DifferentialFieldValidationException' => 'Exception',
'DifferentialFileTreeEngine' => 'Phobject',
'DifferentialGetAllDiffsConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialGetCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialGetCommitPathsConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialGetDiffConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialGetRawDiffConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialGetRevisionCommentsConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialGetRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialGetWorkingCopy' => 'Phobject',
'DifferentialGitSVNIDCommitMessageField' => 'DifferentialCommitMessageField',
'DifferentialHarbormasterField' => 'DifferentialCustomField',
'DifferentialHeraldStateReasons' => 'HeraldStateReasons',
'DifferentialHiddenComment' => 'DifferentialDAO',
'DifferentialHostField' => 'DifferentialCustomField',
'DifferentialHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'DifferentialHunk' => array(
'DifferentialDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
'DifferentialHunkParser' => 'Phobject',
'DifferentialHunkParserTestCase' => 'PhabricatorTestCase',
'DifferentialHunkQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'DifferentialHunkTestCase' => 'PhutilTestCase',
'DifferentialInlineComment' => 'PhabricatorInlineComment',
'DifferentialInlineCommentEditController' => 'PhabricatorInlineCommentController',
'DifferentialInlineCommentMailView' => 'DifferentialMailView',
'DifferentialJIRAIssuesCommitMessageField' => 'DifferentialCommitMessageCustomField',
'DifferentialJIRAIssuesField' => 'DifferentialStoredCustomField',
'DifferentialLegacyQuery' => 'Phobject',
'DifferentialLineAdjustmentMap' => 'Phobject',
'DifferentialLintField' => 'DifferentialHarbormasterField',
'DifferentialLintStatus' => 'Phobject',
'DifferentialLocalCommitsView' => 'AphrontView',
'DifferentialMailEngineExtension' => 'PhabricatorMailEngineExtension',
'DifferentialMailView' => 'Phobject',
'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField',
'DifferentialNoReviewersDatasource' => 'PhabricatorTypeaheadDatasource',
'DifferentialParseCacheGarbageCollector' => 'PhabricatorGarbageCollector',
'DifferentialParseCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialParseRenderTestCase' => 'PhabricatorTestCase',
'DifferentialPathField' => 'DifferentialCustomField',
'DifferentialProjectReviewersField' => 'DifferentialCustomField',
'DifferentialQueryConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialQueryDiffsConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialRawDiffRenderer' => 'Phobject',
- 'DifferentialReleephRequestFieldSpecification' => 'Phobject',
'DifferentialRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'DifferentialReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'DifferentialRepositoryField' => 'DifferentialCoreCustomField',
'DifferentialRepositoryLookup' => 'Phobject',
'DifferentialRequiredSignaturesField' => 'DifferentialCoreCustomField',
'DifferentialResponsibleDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DifferentialResponsibleUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DifferentialResponsibleViewerFunctionDatasource' => 'PhabricatorTypeaheadDatasource',
'DifferentialRevertPlanCommitMessageField' => 'DifferentialCommitMessageCustomField',
'DifferentialRevertPlanField' => 'DifferentialStoredCustomField',
'DifferentialReviewedByCommitMessageField' => 'DifferentialCommitMessageField',
'DifferentialReviewer' => 'DifferentialDAO',
'DifferentialReviewerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DifferentialReviewerForRevisionEdgeType' => 'PhabricatorEdgeType',
'DifferentialReviewerFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DifferentialReviewerStatus' => 'Phobject',
'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'DifferentialReviewersHeraldAction',
'DifferentialReviewersAddBlockingSelfHeraldAction' => 'DifferentialReviewersHeraldAction',
'DifferentialReviewersAddReviewersHeraldAction' => 'DifferentialReviewersHeraldAction',
'DifferentialReviewersAddSelfHeraldAction' => 'DifferentialReviewersHeraldAction',
'DifferentialReviewersCommitMessageField' => 'DifferentialCommitMessageField',
'DifferentialReviewersField' => 'DifferentialCoreCustomField',
'DifferentialReviewersHeraldAction' => 'HeraldAction',
'DifferentialReviewersSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'DifferentialReviewersView' => 'AphrontView',
'DifferentialRevision' => array(
'DifferentialDAO',
'PhabricatorTokenReceiverInterface',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
'PhabricatorFlaggableInterface',
'PhrequentTrackableInterface',
'HarbormasterBuildableInterface',
'PhabricatorSubscribableInterface',
'PhabricatorCustomFieldInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorTimelineInterface',
'PhabricatorMentionableInterface',
'PhabricatorDestructibleInterface',
'PhabricatorProjectInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
'PhabricatorConduitResultInterface',
'PhabricatorDraftInterface',
),
'DifferentialRevisionAbandonTransaction' => 'DifferentialRevisionActionTransaction',
'DifferentialRevisionAcceptTransaction' => 'DifferentialRevisionReviewTransaction',
'DifferentialRevisionActionTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionAffectedFilesHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionAffectedPathsController' => 'DifferentialController',
'DifferentialRevisionAuthorHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionAuthorPackagesHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionAuthorProjectsHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionAuthorTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionBuildableTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionCloseDetailsController' => 'DifferentialController',
'DifferentialRevisionCloseTransaction' => 'DifferentialRevisionActionTransaction',
'DifferentialRevisionClosedStatusDatasource' => 'PhabricatorTypeaheadDatasource',
'DifferentialRevisionCommandeerTransaction' => 'DifferentialRevisionActionTransaction',
'DifferentialRevisionContentAddedHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionContentHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionContentRemovedHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionControlSystem' => 'Phobject',
'DifferentialRevisionDependedOnByRevisionEdgeType' => 'PhabricatorEdgeType',
'DifferentialRevisionDependsOnRevisionEdgeType' => 'PhabricatorEdgeType',
'DifferentialRevisionDraftEngine' => 'PhabricatorDraftEngine',
'DifferentialRevisionEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'DifferentialRevisionEditController' => 'DifferentialController',
'DifferentialRevisionEditEngine' => 'PhabricatorEditEngine',
'DifferentialRevisionFerretEngine' => 'PhabricatorFerretEngine',
'DifferentialRevisionFulltextEngine' => 'PhabricatorFulltextEngine',
'DifferentialRevisionGraph' => 'PhabricatorObjectGraph',
'DifferentialRevisionHasChildRelationship' => 'DifferentialRevisionRelationship',
'DifferentialRevisionHasCommitEdgeType' => 'PhabricatorEdgeType',
'DifferentialRevisionHasCommitRelationship' => 'DifferentialRevisionRelationship',
'DifferentialRevisionHasParentRelationship' => 'DifferentialRevisionRelationship',
'DifferentialRevisionHasReviewerEdgeType' => 'PhabricatorEdgeType',
'DifferentialRevisionHasTaskEdgeType' => 'PhabricatorEdgeType',
'DifferentialRevisionHasTaskRelationship' => 'DifferentialRevisionRelationship',
'DifferentialRevisionHeraldField' => 'HeraldField',
'DifferentialRevisionHeraldFieldGroup' => 'HeraldFieldGroup',
'DifferentialRevisionHoldDraftTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionIDCommitMessageField' => 'DifferentialCommitMessageField',
'DifferentialRevisionInlineTransaction' => 'PhabricatorModularTransactionType',
'DifferentialRevisionInlinesController' => 'DifferentialController',
'DifferentialRevisionJIRAIssueURIsHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionListController' => 'DifferentialController',
'DifferentialRevisionListView' => 'AphrontView',
'DifferentialRevisionMailReceiver' => 'PhabricatorObjectMailReceiver',
'DifferentialRevisionOpenStatusDatasource' => 'PhabricatorTypeaheadDatasource',
'DifferentialRevisionOperationController' => 'DifferentialController',
'DifferentialRevisionPHIDType' => 'PhabricatorPHIDType',
'DifferentialRevisionPackageHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionPackageOwnerHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionPlanChangesTransaction' => 'DifferentialRevisionActionTransaction',
'DifferentialRevisionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'DifferentialRevisionReclaimTransaction' => 'DifferentialRevisionActionTransaction',
'DifferentialRevisionRejectTransaction' => 'DifferentialRevisionReviewTransaction',
'DifferentialRevisionRelationship' => 'PhabricatorObjectRelationship',
'DifferentialRevisionRelationshipSource' => 'PhabricatorObjectRelationshipSource',
'DifferentialRevisionReopenTransaction' => 'DifferentialRevisionActionTransaction',
'DifferentialRevisionRepositoryHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionRepositoryProjectsHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionRepositoryTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionRequestReviewTransaction' => 'DifferentialRevisionActionTransaction',
'DifferentialRevisionRequiredActionResultBucket' => 'DifferentialRevisionResultBucket',
'DifferentialRevisionResignTransaction' => 'DifferentialRevisionReviewTransaction',
'DifferentialRevisionResultBucket' => 'PhabricatorSearchResultBucket',
'DifferentialRevisionReviewTransaction' => 'DifferentialRevisionActionTransaction',
'DifferentialRevisionReviewersHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionReviewersTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'DifferentialRevisionSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DifferentialRevisionStatus' => 'Phobject',
'DifferentialRevisionStatusDatasource' => 'PhabricatorTypeaheadDatasource',
'DifferentialRevisionStatusFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DifferentialRevisionStatusHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionStatusTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionSummaryHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionSummaryTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionTestPlanHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionTestPlanTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionTimelineEngine' => 'PhabricatorTimelineEngine',
'DifferentialRevisionTitleHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionTitleTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionTransactionType' => 'PhabricatorModularTransactionType',
'DifferentialRevisionUpdateHistoryView' => 'AphrontView',
'DifferentialRevisionUpdateTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionViewController' => 'DifferentialController',
'DifferentialRevisionVoidTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionWrongBuildsTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionWrongStateTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'DifferentialSetDiffPropertyConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialStoredCustomField' => 'DifferentialCustomField',
'DifferentialSubscribersCommitMessageField' => 'DifferentialCommitMessageField',
'DifferentialSummaryCommitMessageField' => 'DifferentialCommitMessageField',
'DifferentialSummaryField' => 'DifferentialCoreCustomField',
'DifferentialTabReplacementTestCase' => 'PhabricatorTestCase',
'DifferentialTagsCommitMessageField' => 'DifferentialCommitMessageField',
'DifferentialTasksCommitMessageField' => 'DifferentialCommitMessageField',
'DifferentialTestPlanCommitMessageField' => 'DifferentialCommitMessageField',
'DifferentialTestPlanField' => 'DifferentialCoreCustomField',
'DifferentialTitleCommitMessageField' => 'DifferentialCommitMessageField',
'DifferentialTransaction' => 'PhabricatorModularTransaction',
'DifferentialTransactionComment' => array(
'PhabricatorApplicationTransactionComment',
'PhabricatorInlineCommentInterface',
),
'DifferentialTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'DifferentialTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'DifferentialTransactionView' => 'PhabricatorApplicationTransactionView',
'DifferentialUnitField' => 'DifferentialCustomField',
'DifferentialUnitStatus' => 'Phobject',
'DifferentialUnitTestResult' => 'Phobject',
'DifferentialUpdateRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialViewState' => array(
'DifferentialDAO',
'PhabricatorPolicyInterface',
),
'DifferentialViewStateGarbageCollector' => 'PhabricatorGarbageCollector',
'DifferentialViewStateQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'DiffusionAuditorDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DiffusionAuditorFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DiffusionAuditorsAddAuditorsHeraldAction' => 'DiffusionAuditorsHeraldAction',
'DiffusionAuditorsAddSelfHeraldAction' => 'DiffusionAuditorsHeraldAction',
'DiffusionAuditorsHeraldAction' => 'HeraldAction',
'DiffusionAuditorsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'DiffusionBlameConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionBlameController' => 'DiffusionController',
'DiffusionBlameQuery' => 'DiffusionQuery',
'DiffusionBlockHeraldAction' => 'HeraldAction',
'DiffusionBranchListView' => 'DiffusionView',
'DiffusionBranchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionBranchTableController' => 'DiffusionController',
'DiffusionBrowseController' => 'DiffusionController',
'DiffusionBrowseQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionBrowseResultSet' => 'Phobject',
'DiffusionBrowseTableView' => 'DiffusionView',
'DiffusionBuildableEngine' => 'HarbormasterBuildableEngine',
'DiffusionCacheEngineExtension' => 'PhabricatorCacheEngineExtension',
'DiffusionCachedResolveRefsQuery' => 'DiffusionLowLevelQuery',
'DiffusionChangeController' => 'DiffusionController',
'DiffusionChangeHeraldFieldGroup' => 'HeraldFieldGroup',
'DiffusionCloneController' => 'DiffusionController',
'DiffusionCloneURIView' => 'AphrontView',
'DiffusionCommandEngine' => 'Phobject',
'DiffusionCommandEngineTestCase' => 'PhabricatorTestCase',
'DiffusionCommitAcceptTransaction' => 'DiffusionCommitAuditTransaction',
'DiffusionCommitActionTransaction' => 'DiffusionCommitTransactionType',
'DiffusionCommitAffectedFilesHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitAuditStatus' => 'Phobject',
'DiffusionCommitAuditTransaction' => 'DiffusionCommitActionTransaction',
'DiffusionCommitAuditorsHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitAuditorsTransaction' => 'DiffusionCommitTransactionType',
'DiffusionCommitAuthorHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitAuthorPackagesHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitAuthorProjectsHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitAutocloseHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitBranchesController' => 'DiffusionController',
'DiffusionCommitBranchesHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitBuildableTransaction' => 'DiffusionCommitTransactionType',
'DiffusionCommitCommitterHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitCommitterPackagesHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitCommitterProjectsHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitConcernTransaction' => 'DiffusionCommitAuditTransaction',
'DiffusionCommitController' => 'DiffusionController',
'DiffusionCommitDiffContentAddedHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitDiffContentHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitDiffContentRemovedHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitDiffEnormousHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitDraftEngine' => 'PhabricatorDraftEngine',
'DiffusionCommitEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'DiffusionCommitEditController' => 'DiffusionController',
'DiffusionCommitEditEngine' => 'PhabricatorEditEngine',
'DiffusionCommitFerretEngine' => 'PhabricatorFerretEngine',
'DiffusionCommitFulltextEngine' => 'PhabricatorFulltextEngine',
'DiffusionCommitGraphView' => 'DiffusionView',
'DiffusionCommitHasPackageEdgeType' => 'PhabricatorEdgeType',
'DiffusionCommitHasRevisionEdgeType' => 'PhabricatorEdgeType',
'DiffusionCommitHasRevisionRelationship' => 'DiffusionCommitRelationship',
'DiffusionCommitHasTaskEdgeType' => 'PhabricatorEdgeType',
'DiffusionCommitHasTaskRelationship' => 'DiffusionCommitRelationship',
'DiffusionCommitHash' => 'Phobject',
'DiffusionCommitHeraldField' => 'HeraldField',
'DiffusionCommitHeraldFieldGroup' => 'HeraldFieldGroup',
'DiffusionCommitHintQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'DiffusionCommitHookEngine' => 'Phobject',
'DiffusionCommitHookRejectException' => 'Exception',
'DiffusionCommitListController' => 'DiffusionController',
'DiffusionCommitMergeHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitMessageHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitPackageAuditHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitPackageHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitPackageOwnerHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitParentsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionCommitQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'DiffusionCommitRef' => 'Phobject',
'DiffusionCommitRelationship' => 'PhabricatorObjectRelationship',
'DiffusionCommitRelationshipSource' => 'PhabricatorObjectRelationshipSource',
'DiffusionCommitRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'DiffusionCommitRemarkupRuleTestCase' => 'PhabricatorTestCase',
'DiffusionCommitRepositoryHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitRepositoryProjectsHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitRequiredActionResultBucket' => 'DiffusionCommitResultBucket',
'DiffusionCommitResignTransaction' => 'DiffusionCommitAuditTransaction',
'DiffusionCommitResultBucket' => 'PhabricatorSearchResultBucket',
'DiffusionCommitRevertedByCommitEdgeType' => 'PhabricatorEdgeType',
'DiffusionCommitRevertsCommitEdgeType' => 'PhabricatorEdgeType',
'DiffusionCommitReviewerHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitRevisionAcceptedHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitRevisionAcceptingReviewersHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitRevisionHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitRevisionQuery' => 'Phobject',
'DiffusionCommitRevisionReviewersHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitRevisionSubscribersHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'DiffusionCommitStateTransaction' => 'DiffusionCommitTransactionType',
'DiffusionCommitTagsController' => 'DiffusionController',
'DiffusionCommitTimelineEngine' => 'PhabricatorTimelineEngine',
'DiffusionCommitTransactionType' => 'PhabricatorModularTransactionType',
'DiffusionCommitVerifyTransaction' => 'DiffusionCommitAuditTransaction',
'DiffusionCommitWrongBuildsHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCompareController' => 'DiffusionController',
'DiffusionConduitAPIMethod' => 'ConduitAPIMethod',
'DiffusionController' => 'PhabricatorController',
'DiffusionCreateRepositoriesCapability' => 'PhabricatorPolicyCapability',
'DiffusionDaemonLockException' => 'Exception',
'DiffusionDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension',
'DiffusionDefaultEditCapability' => 'PhabricatorPolicyCapability',
'DiffusionDefaultPushCapability' => 'PhabricatorPolicyCapability',
'DiffusionDefaultViewCapability' => 'PhabricatorPolicyCapability',
'DiffusionDiffController' => 'DiffusionController',
'DiffusionDiffInlineCommentQuery' => 'PhabricatorDiffInlineCommentQuery',
'DiffusionDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionDocumentController' => 'DiffusionController',
'DiffusionDocumentRenderingEngine' => 'PhabricatorDocumentRenderingEngine',
'DiffusionDoorkeeperCommitFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher',
'DiffusionEmptyResultView' => 'DiffusionView',
'DiffusionExistsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionExternalController' => 'DiffusionController',
'DiffusionExternalSymbolQuery' => 'Phobject',
'DiffusionExternalSymbolsSource' => 'Phobject',
'DiffusionFileContentQuery' => 'DiffusionFileFutureQuery',
'DiffusionFileContentQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionFileFutureQuery' => 'DiffusionQuery',
'DiffusionFindSymbolsConduitAPIMethod' => 'DiffusionConduitAPIMethod',
'DiffusionGetLintMessagesConduitAPIMethod' => 'DiffusionConduitAPIMethod',
'DiffusionGetRecentCommitsByPathConduitAPIMethod' => 'DiffusionConduitAPIMethod',
'DiffusionGitBlameQuery' => 'DiffusionBlameQuery',
'DiffusionGitBranch' => 'Phobject',
'DiffusionGitBranchTestCase' => 'PhabricatorTestCase',
'DiffusionGitCommandEngine' => 'DiffusionCommandEngine',
'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery',
'DiffusionGitLFSAuthenticateWorkflow' => 'DiffusionGitSSHWorkflow',
'DiffusionGitLFSResponse' => 'AphrontResponse',
'DiffusionGitLFSTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType',
'DiffusionGitRawDiffQuery' => 'DiffusionRawDiffQuery',
'DiffusionGitReceivePackSSHWorkflow' => 'DiffusionGitSSHWorkflow',
'DiffusionGitRequest' => 'DiffusionRequest',
'DiffusionGitResponse' => 'AphrontResponse',
'DiffusionGitSSHWorkflow' => array(
'DiffusionSSHWorkflow',
'DiffusionRepositoryClusterEngineLogInterface',
),
'DiffusionGitUploadPackSSHWorkflow' => 'DiffusionGitSSHWorkflow',
'DiffusionGitUploadPackWireProtocol' => 'DiffusionGitWireProtocol',
'DiffusionGitWireProtocol' => 'Phobject',
'DiffusionGitWireProtocolCapabilities' => 'Phobject',
'DiffusionGitWireProtocolRef' => 'Phobject',
'DiffusionGitWireProtocolRefList' => 'Phobject',
'DiffusionHistoryController' => 'DiffusionController',
'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'DiffusionIdentityAssigneeDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DiffusionIdentityAssigneeEditField' => 'PhabricatorTokenizerEditField',
'DiffusionIdentityAssigneeSearchField' => 'PhabricatorSearchTokenizerField',
'DiffusionIdentityEditController' => 'DiffusionController',
'DiffusionIdentityListController' => 'DiffusionController',
'DiffusionIdentityUnassignedDatasource' => 'PhabricatorTypeaheadDatasource',
'DiffusionIdentityViewController' => 'DiffusionController',
'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController',
'DiffusionInternalAncestorsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionInternalCommitSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'DiffusionInternalCommitSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DiffusionInternalGitRawDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionLastModifiedController' => 'DiffusionController',
'DiffusionLastModifiedQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionLintController' => 'DiffusionController',
'DiffusionLintCountQuery' => 'PhabricatorQuery',
'DiffusionLintSaveRunner' => 'Phobject',
'DiffusionLocalRepositoryFilter' => 'Phobject',
'DiffusionLogController' => 'DiffusionController',
'DiffusionLookSoonConduitAPIMethod' => 'DiffusionConduitAPIMethod',
'DiffusionLowLevelCommitFieldsQuery' => 'DiffusionLowLevelQuery',
'DiffusionLowLevelCommitQuery' => 'DiffusionLowLevelQuery',
'DiffusionLowLevelFilesizeQuery' => 'DiffusionLowLevelQuery',
'DiffusionLowLevelGitRefQuery' => 'DiffusionLowLevelQuery',
'DiffusionLowLevelMercurialBranchesQuery' => 'DiffusionLowLevelQuery',
'DiffusionLowLevelMercurialPathsQuery' => 'DiffusionLowLevelQuery',
'DiffusionLowLevelParentsQuery' => 'DiffusionLowLevelQuery',
'DiffusionLowLevelQuery' => 'Phobject',
'DiffusionLowLevelResolveRefsQuery' => 'DiffusionLowLevelQuery',
'DiffusionMercurialBlameQuery' => 'DiffusionBlameQuery',
'DiffusionMercurialCommandEngine' => 'DiffusionCommandEngine',
'DiffusionMercurialCommandEngineTests' => 'PhabricatorTestCase',
'DiffusionMercurialFileContentQuery' => 'DiffusionFileContentQuery',
'DiffusionMercurialFlagInjectionException' => 'Exception',
'DiffusionMercurialRawDiffQuery' => 'DiffusionRawDiffQuery',
'DiffusionMercurialRequest' => 'DiffusionRequest',
'DiffusionMercurialResponse' => 'AphrontResponse',
'DiffusionMercurialSSHWorkflow' => 'DiffusionSSHWorkflow',
'DiffusionMercurialServeSSHWorkflow' => 'DiffusionMercurialSSHWorkflow',
'DiffusionMercurialWireClientSSHProtocolChannel' => 'PhutilProtocolChannel',
'DiffusionMercurialWireProtocol' => 'Phobject',
'DiffusionMercurialWireProtocolTests' => 'PhabricatorTestCase',
'DiffusionMercurialWireSSHTestCase' => 'PhabricatorTestCase',
'DiffusionMergedCommitsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionPathChange' => 'Phobject',
'DiffusionPathChangeQuery' => 'Phobject',
'DiffusionPathCompleteController' => 'DiffusionController',
'DiffusionPathIDQuery' => 'Phobject',
'DiffusionPathQuery' => 'Phobject',
'DiffusionPathQueryTestCase' => 'PhabricatorTestCase',
'DiffusionPathTreeController' => 'DiffusionController',
'DiffusionPathValidateController' => 'DiffusionController',
'DiffusionPatternSearchView' => 'DiffusionView',
'DiffusionPhpExternalSymbolsSource' => 'DiffusionExternalSymbolsSource',
'DiffusionPreCommitContentAffectedFilesHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentAuthorHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentAuthorPackagesHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentAuthorProjectsHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentAuthorRawHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentBranchesHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentCommitterHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentCommitterPackagesHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentCommitterProjectsHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentCommitterRawHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentDiffContentAddedHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentDiffContentHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentDiffContentRemovedHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentDiffEnormousHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentHeraldField' => 'HeraldField',
'DiffusionPreCommitContentMergeHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentMessageHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentPackageHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentPackageOwnerHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentPusherHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentPusherIsCommitterHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentPusherProjectsHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentRepositoryHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentRepositoryProjectsHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentRevisionAcceptedHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentRevisionAcceptingReviewersHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentRevisionHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentRevisionReviewersHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentRevisionSubscribersHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitContentWrongBuildsHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPreCommitRefChangeHeraldField' => 'DiffusionPreCommitRefHeraldField',
'DiffusionPreCommitRefHeraldField' => 'HeraldField',
'DiffusionPreCommitRefHeraldFieldGroup' => 'HeraldFieldGroup',
'DiffusionPreCommitRefNameHeraldField' => 'DiffusionPreCommitRefHeraldField',
'DiffusionPreCommitRefPusherHeraldField' => 'DiffusionPreCommitRefHeraldField',
'DiffusionPreCommitRefPusherProjectsHeraldField' => 'DiffusionPreCommitRefHeraldField',
'DiffusionPreCommitRefRepositoryHeraldField' => 'DiffusionPreCommitRefHeraldField',
'DiffusionPreCommitRefRepositoryProjectsHeraldField' => 'DiffusionPreCommitRefHeraldField',
'DiffusionPreCommitRefTypeHeraldField' => 'DiffusionPreCommitRefHeraldField',
'DiffusionPreCommitUsesGitLFSHeraldField' => 'DiffusionPreCommitContentHeraldField',
'DiffusionPullEventGarbageCollector' => 'PhabricatorGarbageCollector',
'DiffusionPullLogListController' => 'DiffusionLogController',
'DiffusionPullLogListView' => 'AphrontView',
'DiffusionPullLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DiffusionPushCapability' => 'PhabricatorPolicyCapability',
'DiffusionPushEventViewController' => 'DiffusionLogController',
'DiffusionPushLogListController' => 'DiffusionLogController',
'DiffusionPushLogListView' => 'AphrontView',
'DiffusionPythonExternalSymbolsSource' => 'DiffusionExternalSymbolsSource',
'DiffusionQuery' => 'PhabricatorQuery',
'DiffusionQueryCommitsConduitAPIMethod' => 'DiffusionConduitAPIMethod',
'DiffusionQueryConduitAPIMethod' => 'DiffusionConduitAPIMethod',
'DiffusionQueryPathsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionRawDiffQuery' => 'DiffusionFileFutureQuery',
'DiffusionRawDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionReadmeView' => 'DiffusionView',
'DiffusionRefDatasource' => 'PhabricatorTypeaheadDatasource',
'DiffusionRefNotFoundException' => 'Exception',
'DiffusionRefTableController' => 'DiffusionController',
'DiffusionRefsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionRenameHistoryQuery' => 'Phobject',
'DiffusionRepositoryAutomationManagementPanel' => 'DiffusionRepositoryManagementPanel',
'DiffusionRepositoryBasicsManagementPanel' => 'DiffusionRepositoryManagementPanel',
'DiffusionRepositoryBranchesManagementPanel' => 'DiffusionRepositoryManagementPanel',
'DiffusionRepositoryByIDRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'DiffusionRepositoryClusterEngine' => 'Phobject',
'DiffusionRepositoryController' => 'DiffusionController',
'DiffusionRepositoryDatasource' => 'PhabricatorTypeaheadDatasource',
'DiffusionRepositoryDefaultController' => 'DiffusionController',
'DiffusionRepositoryEditActivateController' => 'DiffusionRepositoryManageController',
'DiffusionRepositoryEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'DiffusionRepositoryEditController' => 'DiffusionRepositoryManageController',
'DiffusionRepositoryEditDangerousController' => 'DiffusionRepositoryManageController',
'DiffusionRepositoryEditDeleteController' => 'DiffusionRepositoryManageController',
'DiffusionRepositoryEditEngine' => 'PhabricatorEditEngine',
'DiffusionRepositoryEditEnormousController' => 'DiffusionRepositoryManageController',
'DiffusionRepositoryEditPublishingController' => 'DiffusionRepositoryManageController',
'DiffusionRepositoryEditUpdateController' => 'DiffusionRepositoryManageController',
'DiffusionRepositoryFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DiffusionRepositoryHistoryManagementPanel' => 'DiffusionRepositoryManagementPanel',
'DiffusionRepositoryIdentityDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
'DiffusionRepositoryIdentityEditor' => 'PhabricatorApplicationTransactionEditor',
'DiffusionRepositoryIdentityEngine' => 'Phobject',
'DiffusionRepositoryIdentitySearchEngine' => 'PhabricatorApplicationSearchEngine',
'DiffusionRepositoryLimitsManagementPanel' => 'DiffusionRepositoryManagementPanel',
'DiffusionRepositoryListController' => 'DiffusionController',
'DiffusionRepositoryManageController' => 'DiffusionController',
'DiffusionRepositoryManagePanelsController' => 'DiffusionRepositoryManageController',
'DiffusionRepositoryManagementBuildsPanelGroup' => 'DiffusionRepositoryManagementPanelGroup',
'DiffusionRepositoryManagementIntegrationsPanelGroup' => 'DiffusionRepositoryManagementPanelGroup',
'DiffusionRepositoryManagementMainPanelGroup' => 'DiffusionRepositoryManagementPanelGroup',
'DiffusionRepositoryManagementOtherPanelGroup' => 'DiffusionRepositoryManagementPanelGroup',
'DiffusionRepositoryManagementPanel' => 'Phobject',
'DiffusionRepositoryManagementPanelGroup' => 'Phobject',
'DiffusionRepositoryMetricsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'DiffusionRepositoryPath' => 'Phobject',
'DiffusionRepositoryPoliciesManagementPanel' => 'DiffusionRepositoryManagementPanel',
'DiffusionRepositoryProfilePictureController' => 'DiffusionController',
'DiffusionRepositoryRef' => 'Phobject',
'DiffusionRepositoryRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'DiffusionRepositorySearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'DiffusionRepositoryStagingManagementPanel' => 'DiffusionRepositoryManagementPanel',
'DiffusionRepositoryStorageManagementPanel' => 'DiffusionRepositoryManagementPanel',
'DiffusionRepositorySubversionManagementPanel' => 'DiffusionRepositoryManagementPanel',
'DiffusionRepositorySymbolsManagementPanel' => 'DiffusionRepositoryManagementPanel',
'DiffusionRepositoryTag' => 'Phobject',
'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryManageController',
'DiffusionRepositoryURICredentialController' => 'DiffusionController',
'DiffusionRepositoryURIDisableController' => 'DiffusionController',
'DiffusionRepositoryURIEditController' => 'DiffusionController',
'DiffusionRepositoryURIViewController' => 'DiffusionController',
'DiffusionRepositoryURIsIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
'DiffusionRepositoryURIsManagementPanel' => 'DiffusionRepositoryManagementPanel',
'DiffusionRepositoryURIsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'DiffusionRequest' => 'Phobject',
'DiffusionResolveRefsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionResolveUserQuery' => 'Phobject',
'DiffusionSSHWorkflow' => 'PhabricatorSSHWorkflow',
'DiffusionSearchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionServeController' => 'DiffusionController',
'DiffusionServiceRef' => 'Phobject',
'DiffusionSetPasswordSettingsPanel' => 'PhabricatorSettingsPanel',
'DiffusionSetupException' => 'Exception',
'DiffusionSourceHyperlinkEngineExtension' => 'PhabricatorRemarkupHyperlinkEngineExtension',
'DiffusionSourceLinkRemarkupRule' => 'PhutilRemarkupRule',
'DiffusionSourceLinkView' => 'AphrontView',
'DiffusionSubversionCommandEngine' => 'DiffusionCommandEngine',
'DiffusionSubversionSSHWorkflow' => 'DiffusionSSHWorkflow',
'DiffusionSubversionServeSSHWorkflow' => 'DiffusionSubversionSSHWorkflow',
'DiffusionSubversionWireProtocol' => 'Phobject',
'DiffusionSubversionWireProtocolTestCase' => 'PhabricatorTestCase',
'DiffusionSvnBlameQuery' => 'DiffusionBlameQuery',
'DiffusionSvnFileContentQuery' => 'DiffusionFileContentQuery',
'DiffusionSvnRawDiffQuery' => 'DiffusionRawDiffQuery',
'DiffusionSvnRequest' => 'DiffusionRequest',
'DiffusionSymbolController' => 'DiffusionController',
'DiffusionSymbolDatasource' => 'PhabricatorTypeaheadDatasource',
'DiffusionSymbolQuery' => 'PhabricatorOffsetPagedQuery',
'DiffusionSyncLogListController' => 'DiffusionLogController',
'DiffusionSyncLogListView' => 'AphrontView',
'DiffusionSyncLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DiffusionTagListController' => 'DiffusionController',
'DiffusionTagListView' => 'DiffusionView',
'DiffusionTaggedRepositoriesFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DiffusionTagsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionURIEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'DiffusionURIEditEngine' => 'PhabricatorEditEngine',
'DiffusionURIEditor' => 'PhabricatorApplicationTransactionEditor',
'DiffusionURITestCase' => 'PhutilTestCase',
'DiffusionUpdateCoverageConduitAPIMethod' => 'DiffusionConduitAPIMethod',
'DiffusionUpdateObjectAfterCommitWorker' => 'PhabricatorWorker',
'DiffusionView' => 'AphrontView',
'DivinerArticleAtomizer' => 'DivinerAtomizer',
'DivinerAtom' => 'Phobject',
'DivinerAtomCache' => 'DivinerDiskCache',
'DivinerAtomController' => 'DivinerController',
'DivinerAtomListController' => 'DivinerController',
'DivinerAtomPHIDType' => 'PhabricatorPHIDType',
'DivinerAtomQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'DivinerAtomRef' => 'Phobject',
'DivinerAtomSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DivinerAtomizeWorkflow' => 'DivinerWorkflow',
'DivinerAtomizer' => 'Phobject',
'DivinerBookController' => 'DivinerController',
'DivinerBookDatasource' => 'PhabricatorTypeaheadDatasource',
'DivinerBookEditController' => 'DivinerController',
'DivinerBookItemView' => 'AphrontTagView',
'DivinerBookPHIDType' => 'PhabricatorPHIDType',
'DivinerBookQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'DivinerController' => 'PhabricatorController',
'DivinerDAO' => 'PhabricatorLiskDAO',
'DivinerDefaultEditCapability' => 'PhabricatorPolicyCapability',
'DivinerDefaultRenderer' => 'DivinerRenderer',
'DivinerDefaultViewCapability' => 'PhabricatorPolicyCapability',
'DivinerDiskCache' => 'Phobject',
'DivinerFileAtomizer' => 'DivinerAtomizer',
'DivinerFindController' => 'DivinerController',
'DivinerGenerateWorkflow' => 'DivinerWorkflow',
'DivinerLiveAtom' => 'DivinerDAO',
'DivinerLiveBook' => array(
'DivinerDAO',
'PhabricatorPolicyInterface',
'PhabricatorProjectInterface',
'PhabricatorDestructibleInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorFulltextInterface',
),
'DivinerLiveBookEditor' => 'PhabricatorApplicationTransactionEditor',
'DivinerLiveBookFulltextEngine' => 'PhabricatorFulltextEngine',
'DivinerLiveBookTransaction' => 'PhabricatorApplicationTransaction',
'DivinerLiveBookTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'DivinerLivePublisher' => 'DivinerPublisher',
'DivinerLiveSymbol' => array(
'DivinerDAO',
'PhabricatorPolicyInterface',
'PhabricatorMarkupInterface',
'PhabricatorDestructibleInterface',
'PhabricatorFulltextInterface',
),
'DivinerLiveSymbolFulltextEngine' => 'PhabricatorFulltextEngine',
'DivinerMainController' => 'DivinerController',
'DivinerPHPAtomizer' => 'DivinerAtomizer',
'DivinerParameterTableView' => 'AphrontTagView',
'DivinerPublishCache' => 'DivinerDiskCache',
'DivinerPublisher' => 'Phobject',
'DivinerRenderer' => 'Phobject',
'DivinerReturnTableView' => 'AphrontTagView',
'DivinerSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'DivinerSectionView' => 'AphrontTagView',
'DivinerStaticPublisher' => 'DivinerPublisher',
'DivinerSymbolRemarkupRule' => 'PhutilRemarkupRule',
'DivinerWorkflow' => 'PhabricatorManagementWorkflow',
'DoorkeeperAsanaFeedWorker' => 'DoorkeeperFeedWorker',
'DoorkeeperBridge' => 'Phobject',
'DoorkeeperBridgeAsana' => 'DoorkeeperBridge',
'DoorkeeperBridgeGitHub' => 'DoorkeeperBridge',
'DoorkeeperBridgeGitHubIssue' => 'DoorkeeperBridgeGitHub',
'DoorkeeperBridgeGitHubUser' => 'DoorkeeperBridgeGitHub',
'DoorkeeperBridgeJIRA' => 'DoorkeeperBridge',
'DoorkeeperBridgeJIRATestCase' => 'PhabricatorTestCase',
'DoorkeeperBridgedObjectCurtainExtension' => 'PHUICurtainExtension',
'DoorkeeperDAO' => 'PhabricatorLiskDAO',
'DoorkeeperExternalObject' => array(
'DoorkeeperDAO',
'PhabricatorPolicyInterface',
),
'DoorkeeperExternalObjectPHIDType' => 'PhabricatorPHIDType',
'DoorkeeperExternalObjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'DoorkeeperFeedStoryPublisher' => 'Phobject',
'DoorkeeperFeedWorker' => 'FeedPushWorker',
'DoorkeeperHyperlinkEngineExtension' => 'PhabricatorRemarkupHyperlinkEngineExtension',
'DoorkeeperImportEngine' => 'Phobject',
'DoorkeeperJIRAFeedWorker' => 'DoorkeeperFeedWorker',
'DoorkeeperMissingLinkException' => 'Exception',
'DoorkeeperObjectRef' => 'Phobject',
'DoorkeeperSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'DoorkeeperTagView' => 'AphrontView',
'DoorkeeperTagsController' => 'PhabricatorController',
'DoorkeeperURIRef' => 'Phobject',
'DrydockAcquiredBrokenResourceException' => 'Exception',
'DrydockAlmanacServiceHostBlueprintImplementation' => 'DrydockBlueprintImplementation',
'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface',
'DrydockAuthorization' => array(
'DrydockDAO',
'PhabricatorPolicyInterface',
'PhabricatorConduitResultInterface',
),
'DrydockAuthorizationAuthorizeController' => 'DrydockController',
'DrydockAuthorizationListController' => 'DrydockController',
'DrydockAuthorizationListView' => 'AphrontView',
'DrydockAuthorizationPHIDType' => 'PhabricatorPHIDType',
'DrydockAuthorizationQuery' => 'DrydockQuery',
'DrydockAuthorizationSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'DrydockAuthorizationSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DrydockAuthorizationViewController' => 'DrydockController',
'DrydockBlueprint' => array(
'DrydockDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorCustomFieldInterface',
'PhabricatorNgramsInterface',
'PhabricatorProjectInterface',
'PhabricatorConduitResultInterface',
),
'DrydockBlueprintController' => 'DrydockController',
'DrydockBlueprintCoreCustomField' => array(
'DrydockBlueprintCustomField',
'PhabricatorStandardCustomFieldInterface',
),
'DrydockBlueprintCustomField' => 'PhabricatorCustomField',
'DrydockBlueprintDatasource' => 'PhabricatorTypeaheadDatasource',
'DrydockBlueprintDisableController' => 'DrydockBlueprintController',
'DrydockBlueprintDisableTransaction' => 'DrydockBlueprintTransactionType',
'DrydockBlueprintEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'DrydockBlueprintEditController' => 'DrydockBlueprintController',
'DrydockBlueprintEditEngine' => 'PhabricatorEditEngine',
'DrydockBlueprintEditor' => 'PhabricatorApplicationTransactionEditor',
'DrydockBlueprintImplementation' => 'Phobject',
'DrydockBlueprintImplementationTestCase' => 'PhabricatorTestCase',
'DrydockBlueprintListController' => 'DrydockBlueprintController',
'DrydockBlueprintNameNgrams' => 'PhabricatorSearchNgrams',
'DrydockBlueprintNameTransaction' => 'DrydockBlueprintTransactionType',
'DrydockBlueprintPHIDType' => 'PhabricatorPHIDType',
'DrydockBlueprintQuery' => 'DrydockQuery',
'DrydockBlueprintSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'DrydockBlueprintSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DrydockBlueprintTransaction' => 'PhabricatorModularTransaction',
'DrydockBlueprintTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'DrydockBlueprintTransactionType' => 'PhabricatorModularTransactionType',
'DrydockBlueprintTypeTransaction' => 'DrydockBlueprintTransactionType',
'DrydockBlueprintViewController' => 'DrydockBlueprintController',
'DrydockCommand' => array(
'DrydockDAO',
'PhabricatorPolicyInterface',
),
'DrydockCommandError' => 'Phobject',
'DrydockCommandInterface' => 'DrydockInterface',
'DrydockCommandQuery' => 'DrydockQuery',
'DrydockConsoleController' => 'DrydockController',
'DrydockController' => 'PhabricatorController',
'DrydockCreateBlueprintsCapability' => 'PhabricatorPolicyCapability',
'DrydockDAO' => 'PhabricatorLiskDAO',
'DrydockDefaultEditCapability' => 'PhabricatorPolicyCapability',
'DrydockDefaultViewCapability' => 'PhabricatorPolicyCapability',
'DrydockFilesystemInterface' => 'DrydockInterface',
'DrydockInterface' => 'Phobject',
'DrydockLandRepositoryOperation' => 'DrydockRepositoryOperationType',
'DrydockLease' => array(
'DrydockDAO',
'PhabricatorPolicyInterface',
'PhabricatorConduitResultInterface',
),
'DrydockLeaseAcquiredLogType' => 'DrydockLogType',
'DrydockLeaseActivatedLogType' => 'DrydockLogType',
'DrydockLeaseActivationFailureLogType' => 'DrydockLogType',
'DrydockLeaseActivationYieldLogType' => 'DrydockLogType',
'DrydockLeaseAllocationFailureLogType' => 'DrydockLogType',
'DrydockLeaseController' => 'DrydockController',
'DrydockLeaseDatasource' => 'PhabricatorTypeaheadDatasource',
'DrydockLeaseDestroyedLogType' => 'DrydockLogType',
'DrydockLeaseListController' => 'DrydockLeaseController',
'DrydockLeaseListView' => 'AphrontView',
'DrydockLeaseNoAuthorizationsLogType' => 'DrydockLogType',
'DrydockLeaseNoBlueprintsLogType' => 'DrydockLogType',
'DrydockLeasePHIDType' => 'PhabricatorPHIDType',
'DrydockLeaseQuery' => 'DrydockQuery',
'DrydockLeaseQueuedLogType' => 'DrydockLogType',
'DrydockLeaseReacquireLogType' => 'DrydockLogType',
'DrydockLeaseReclaimLogType' => 'DrydockLogType',
'DrydockLeaseReleaseController' => 'DrydockLeaseController',
'DrydockLeaseReleasedLogType' => 'DrydockLogType',
'DrydockLeaseSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'DrydockLeaseSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DrydockLeaseStatus' => 'PhabricatorObjectStatus',
'DrydockLeaseUpdateWorker' => 'DrydockWorker',
'DrydockLeaseViewController' => 'DrydockLeaseController',
+ 'DrydockLeaseWaitingForActivationLogType' => 'DrydockLogType',
+ 'DrydockLeaseWaitingForReclamationLogType' => 'DrydockLogType',
'DrydockLeaseWaitingForResourcesLogType' => 'DrydockLogType',
'DrydockLog' => array(
'DrydockDAO',
'PhabricatorPolicyInterface',
),
'DrydockLogController' => 'DrydockController',
'DrydockLogGarbageCollector' => 'PhabricatorGarbageCollector',
'DrydockLogListController' => 'DrydockLogController',
'DrydockLogListView' => 'AphrontView',
'DrydockLogQuery' => 'DrydockQuery',
'DrydockLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DrydockLogType' => 'Phobject',
'DrydockManagementCommandWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementReclaimWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementReleaseLeaseWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementReleaseResourceWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementUpdateLeaseWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementUpdateResourceWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow',
'DrydockObjectAuthorizationView' => 'AphrontView',
'DrydockOperationWorkLogType' => 'DrydockLogType',
'DrydockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'DrydockRepositoryOperation' => array(
'DrydockDAO',
'PhabricatorPolicyInterface',
),
'DrydockRepositoryOperationController' => 'DrydockController',
'DrydockRepositoryOperationDismissController' => 'DrydockRepositoryOperationController',
'DrydockRepositoryOperationListController' => 'DrydockRepositoryOperationController',
'DrydockRepositoryOperationPHIDType' => 'PhabricatorPHIDType',
'DrydockRepositoryOperationQuery' => 'DrydockQuery',
'DrydockRepositoryOperationSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DrydockRepositoryOperationStatusController' => 'DrydockRepositoryOperationController',
'DrydockRepositoryOperationStatusView' => 'AphrontView',
'DrydockRepositoryOperationType' => 'Phobject',
'DrydockRepositoryOperationUpdateWorker' => 'DrydockWorker',
'DrydockRepositoryOperationViewController' => 'DrydockRepositoryOperationController',
'DrydockResource' => array(
'DrydockDAO',
'PhabricatorPolicyInterface',
'PhabricatorConduitResultInterface',
),
'DrydockResourceActivationFailureLogType' => 'DrydockLogType',
'DrydockResourceActivationYieldLogType' => 'DrydockLogType',
'DrydockResourceAllocationFailureLogType' => 'DrydockLogType',
'DrydockResourceController' => 'DrydockController',
'DrydockResourceDatasource' => 'PhabricatorTypeaheadDatasource',
'DrydockResourceListController' => 'DrydockResourceController',
'DrydockResourceListView' => 'AphrontView',
'DrydockResourceLockException' => 'Exception',
'DrydockResourcePHIDType' => 'PhabricatorPHIDType',
'DrydockResourceQuery' => 'DrydockQuery',
'DrydockResourceReclaimLogType' => 'DrydockLogType',
'DrydockResourceReleaseController' => 'DrydockResourceController',
'DrydockResourceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'DrydockResourceSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DrydockResourceStatus' => 'PhabricatorObjectStatus',
'DrydockResourceUpdateWorker' => 'DrydockWorker',
'DrydockResourceViewController' => 'DrydockResourceController',
'DrydockSFTPFilesystemInterface' => 'DrydockFilesystemInterface',
'DrydockSSHCommandInterface' => 'DrydockCommandInterface',
'DrydockSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'DrydockSlotLock' => 'DrydockDAO',
'DrydockSlotLockException' => 'Exception',
'DrydockSlotLockFailureLogType' => 'DrydockLogType',
'DrydockTestRepositoryOperation' => 'DrydockRepositoryOperationType',
'DrydockTextLogType' => 'DrydockLogType',
'DrydockWebrootInterface' => 'DrydockInterface',
'DrydockWorker' => 'PhabricatorWorker',
'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation',
'EdgeSearchConduitAPIMethod' => 'ConduitAPIMethod',
'FeedConduitAPIMethod' => 'ConduitAPIMethod',
- 'FeedPublishConduitAPIMethod' => 'FeedConduitAPIMethod',
'FeedPublisherHTTPWorker' => 'FeedPushWorker',
'FeedPublisherWorker' => 'FeedPushWorker',
'FeedPushWorker' => 'PhabricatorWorker',
'FeedQueryConduitAPIMethod' => 'FeedConduitAPIMethod',
'FeedStoryNotificationGarbageCollector' => 'PhabricatorGarbageCollector',
'FerretConfigurableSearchFunction' => 'FerretSearchFunction',
'FerretSearchFunction' => 'Phobject',
'FileAllocateConduitAPIMethod' => 'FileConduitAPIMethod',
'FileConduitAPIMethod' => 'ConduitAPIMethod',
'FileCreateMailReceiver' => 'PhabricatorApplicationMailReceiver',
'FileDeletionWorker' => 'PhabricatorWorker',
'FileDownloadConduitAPIMethod' => 'FileConduitAPIMethod',
'FileInfoConduitAPIMethod' => 'FileConduitAPIMethod',
'FileMailReceiver' => 'PhabricatorObjectMailReceiver',
'FileQueryChunksConduitAPIMethod' => 'FileConduitAPIMethod',
'FileReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'FileTypeIcon' => 'Phobject',
'FileUploadChunkConduitAPIMethod' => 'FileConduitAPIMethod',
'FileUploadConduitAPIMethod' => 'FileConduitAPIMethod',
'FileUploadHashConduitAPIMethod' => 'FileConduitAPIMethod',
'FilesDefaultViewCapability' => 'PhabricatorPolicyCapability',
'FlagConduitAPIMethod' => 'ConduitAPIMethod',
'FlagDeleteConduitAPIMethod' => 'FlagConduitAPIMethod',
'FlagEditConduitAPIMethod' => 'FlagConduitAPIMethod',
'FlagQueryConduitAPIMethod' => 'FlagConduitAPIMethod',
'FuelComponentView' => 'FuelView',
'FuelGridCellView' => 'FuelComponentView',
'FuelGridRowView' => 'FuelView',
'FuelGridView' => 'FuelComponentView',
'FuelHandleListItemView' => 'FuelView',
'FuelHandleListView' => 'FuelComponentView',
'FuelMapItemView' => 'AphrontView',
'FuelMapView' => 'FuelComponentView',
'FuelMenuItemView' => 'FuelView',
'FuelMenuView' => 'FuelComponentView',
'FuelView' => 'AphrontView',
'FundBacker' => array(
'FundDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
),
'FundBackerCart' => 'PhortuneCartImplementation',
'FundBackerEditor' => 'PhabricatorApplicationTransactionEditor',
'FundBackerListController' => 'FundController',
'FundBackerPHIDType' => 'PhabricatorPHIDType',
'FundBackerProduct' => 'PhortuneProductImplementation',
'FundBackerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'FundBackerRefundTransaction' => 'FundBackerTransactionType',
'FundBackerSearchEngine' => 'PhabricatorApplicationSearchEngine',
'FundBackerStatusTransaction' => 'FundBackerTransactionType',
'FundBackerTransaction' => 'PhabricatorModularTransaction',
'FundBackerTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'FundBackerTransactionType' => 'PhabricatorModularTransactionType',
'FundController' => 'PhabricatorController',
'FundCreateInitiativesCapability' => 'PhabricatorPolicyCapability',
'FundDAO' => 'PhabricatorLiskDAO',
'FundDefaultViewCapability' => 'PhabricatorPolicyCapability',
'FundInitiative' => array(
'FundDAO',
'PhabricatorPolicyInterface',
'PhabricatorProjectInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorSubscribableInterface',
'PhabricatorMentionableInterface',
'PhabricatorFlaggableInterface',
'PhabricatorTokenReceiverInterface',
'PhabricatorDestructibleInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
),
'FundInitiativeBackController' => 'FundController',
'FundInitiativeBackerTransaction' => 'FundInitiativeTransactionType',
'FundInitiativeCloseController' => 'FundController',
'FundInitiativeDescriptionTransaction' => 'FundInitiativeTransactionType',
'FundInitiativeEditController' => 'FundController',
'FundInitiativeEditEngine' => 'PhabricatorEditEngine',
'FundInitiativeEditor' => 'PhabricatorApplicationTransactionEditor',
'FundInitiativeFerretEngine' => 'PhabricatorFerretEngine',
'FundInitiativeFulltextEngine' => 'PhabricatorFulltextEngine',
'FundInitiativeListController' => 'FundController',
'FundInitiativeMerchantTransaction' => 'FundInitiativeTransactionType',
'FundInitiativeNameTransaction' => 'FundInitiativeTransactionType',
'FundInitiativePHIDType' => 'PhabricatorPHIDType',
'FundInitiativeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'FundInitiativeRefundTransaction' => 'FundInitiativeTransactionType',
'FundInitiativeRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'FundInitiativeReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'FundInitiativeRisksTransaction' => 'FundInitiativeTransactionType',
'FundInitiativeSearchEngine' => 'PhabricatorApplicationSearchEngine',
'FundInitiativeStatusTransaction' => 'FundInitiativeTransactionType',
'FundInitiativeTransaction' => 'PhabricatorModularTransaction',
'FundInitiativeTransactionComment' => 'PhabricatorApplicationTransactionComment',
'FundInitiativeTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'FundInitiativeTransactionType' => 'PhabricatorModularTransactionType',
'FundInitiativeViewController' => 'FundController',
'FundSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'HarbormasterAbortOlderBuildsBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterArcLintBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterArcUnitBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterArtifact' => 'Phobject',
'HarbormasterArtifactSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'HarbormasterArtifactSearchEngine' => 'PhabricatorApplicationSearchEngine',
'HarbormasterAutotargetsTestCase' => 'PhabricatorTestCase',
'HarbormasterBuild' => array(
'HarbormasterDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorConduitResultInterface',
'PhabricatorDestructibleInterface',
),
'HarbormasterBuildAbortedException' => 'Exception',
'HarbormasterBuildActionController' => 'HarbormasterController',
'HarbormasterBuildArcanistAutoplan' => 'HarbormasterBuildAutoplan',
'HarbormasterBuildArtifact' => array(
'HarbormasterDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorConduitResultInterface',
),
'HarbormasterBuildArtifactPHIDType' => 'PhabricatorPHIDType',
'HarbormasterBuildArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildAutoplan' => 'Phobject',
'HarbormasterBuildDependencyDatasource' => 'PhabricatorTypeaheadDatasource',
'HarbormasterBuildEditAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'HarbormasterBuildEditEngine' => 'PhabricatorEditEngine',
'HarbormasterBuildEngine' => 'Phobject',
'HarbormasterBuildFailureException' => 'Exception',
'HarbormasterBuildGraph' => 'AbstractDirectedGraph',
'HarbormasterBuildInitiatorDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'HarbormasterBuildLintMessage' => 'HarbormasterDAO',
'HarbormasterBuildListController' => 'HarbormasterController',
'HarbormasterBuildLog' => array(
'HarbormasterDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorConduitResultInterface',
),
'HarbormasterBuildLogChunk' => 'HarbormasterDAO',
'HarbormasterBuildLogChunkIterator' => 'PhutilBufferedIterator',
'HarbormasterBuildLogDownloadController' => 'HarbormasterController',
'HarbormasterBuildLogPHIDType' => 'PhabricatorPHIDType',
'HarbormasterBuildLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildLogRenderController' => 'HarbormasterController',
'HarbormasterBuildLogSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'HarbormasterBuildLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
'HarbormasterBuildLogTestCase' => 'PhabricatorTestCase',
'HarbormasterBuildLogView' => 'AphrontView',
'HarbormasterBuildLogViewController' => 'HarbormasterController',
'HarbormasterBuildMessage' => array(
'HarbormasterDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
'HarbormasterBuildMessageAbortTransaction' => 'HarbormasterBuildMessageTransaction',
'HarbormasterBuildMessagePauseTransaction' => 'HarbormasterBuildMessageTransaction',
'HarbormasterBuildMessageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildMessageRestartTransaction' => 'HarbormasterBuildMessageTransaction',
'HarbormasterBuildMessageResumeTransaction' => 'HarbormasterBuildMessageTransaction',
'HarbormasterBuildMessageTransaction' => 'HarbormasterBuildTransactionType',
'HarbormasterBuildPHIDType' => 'PhabricatorPHIDType',
'HarbormasterBuildPlan' => array(
'HarbormasterDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorSubscribableInterface',
'PhabricatorNgramsInterface',
'PhabricatorConduitResultInterface',
'PhabricatorProjectInterface',
'PhabricatorPolicyCodexInterface',
),
'HarbormasterBuildPlanBehavior' => 'Phobject',
'HarbormasterBuildPlanBehaviorOption' => 'Phobject',
'HarbormasterBuildPlanBehaviorTransaction' => 'HarbormasterBuildPlanTransactionType',
'HarbormasterBuildPlanDatasource' => 'PhabricatorTypeaheadDatasource',
'HarbormasterBuildPlanDefaultEditCapability' => 'PhabricatorPolicyCapability',
'HarbormasterBuildPlanDefaultViewCapability' => 'PhabricatorPolicyCapability',
'HarbormasterBuildPlanEditAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'HarbormasterBuildPlanEditEngine' => 'PhabricatorEditEngine',
'HarbormasterBuildPlanEditor' => 'PhabricatorApplicationTransactionEditor',
'HarbormasterBuildPlanNameNgrams' => 'PhabricatorSearchNgrams',
'HarbormasterBuildPlanNameTransaction' => 'HarbormasterBuildPlanTransactionType',
'HarbormasterBuildPlanPHIDType' => 'PhabricatorPHIDType',
'HarbormasterBuildPlanPolicyCodex' => 'PhabricatorPolicyCodex',
'HarbormasterBuildPlanQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildPlanSearchAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'HarbormasterBuildPlanSearchEngine' => 'PhabricatorApplicationSearchEngine',
'HarbormasterBuildPlanStatusTransaction' => 'HarbormasterBuildPlanTransactionType',
'HarbormasterBuildPlanTransaction' => 'PhabricatorModularTransaction',
'HarbormasterBuildPlanTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'HarbormasterBuildPlanTransactionType' => 'PhabricatorModularTransactionType',
'HarbormasterBuildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildRequest' => 'Phobject',
'HarbormasterBuildSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'HarbormasterBuildSearchEngine' => 'PhabricatorApplicationSearchEngine',
'HarbormasterBuildStatus' => 'Phobject',
'HarbormasterBuildStatusDatasource' => 'PhabricatorTypeaheadDatasource',
'HarbormasterBuildStep' => array(
'HarbormasterDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorCustomFieldInterface',
'PhabricatorConduitResultInterface',
),
'HarbormasterBuildStepCoreCustomField' => array(
'HarbormasterBuildStepCustomField',
'PhabricatorStandardCustomFieldInterface',
),
'HarbormasterBuildStepCustomField' => 'PhabricatorCustomField',
'HarbormasterBuildStepEditAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'HarbormasterBuildStepEditEngine' => 'PhabricatorEditEngine',
'HarbormasterBuildStepEditor' => 'PhabricatorApplicationTransactionEditor',
'HarbormasterBuildStepGroup' => 'Phobject',
'HarbormasterBuildStepImplementation' => 'Phobject',
'HarbormasterBuildStepImplementationTestCase' => 'PhabricatorTestCase',
'HarbormasterBuildStepPHIDType' => 'PhabricatorPHIDType',
'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildStepSearchAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'HarbormasterBuildStepSearchEngine' => 'PhabricatorApplicationSearchEngine',
'HarbormasterBuildStepTransaction' => 'PhabricatorApplicationTransaction',
'HarbormasterBuildStepTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'HarbormasterBuildTarget' => array(
'HarbormasterDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorConduitResultInterface',
),
'HarbormasterBuildTargetPHIDType' => 'PhabricatorPHIDType',
'HarbormasterBuildTargetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildTargetSearchEngine' => 'PhabricatorApplicationSearchEngine',
'HarbormasterBuildTransaction' => 'PhabricatorModularTransaction',
'HarbormasterBuildTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'HarbormasterBuildTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'HarbormasterBuildTransactionType' => 'PhabricatorModularTransactionType',
'HarbormasterBuildUnitMessage' => array(
'HarbormasterDAO',
'PhabricatorPolicyInterface',
),
'HarbormasterBuildUnitMessageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildView' => 'AphrontView',
'HarbormasterBuildViewController' => 'HarbormasterController',
'HarbormasterBuildWorker' => 'HarbormasterWorker',
'HarbormasterBuildable' => array(
'HarbormasterDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'HarbormasterBuildableInterface',
'PhabricatorConduitResultInterface',
'PhabricatorDestructibleInterface',
),
'HarbormasterBuildableActionController' => 'HarbormasterController',
'HarbormasterBuildableEditAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'HarbormasterBuildableEditEngine' => 'PhabricatorEditEngine',
'HarbormasterBuildableEngine' => 'Phobject',
'HarbormasterBuildableListController' => 'HarbormasterController',
'HarbormasterBuildableMessageTransaction' => 'HarbormasterBuildableTransactionType',
'HarbormasterBuildablePHIDType' => 'PhabricatorPHIDType',
'HarbormasterBuildableQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildableSearchAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'HarbormasterBuildableSearchEngine' => 'PhabricatorApplicationSearchEngine',
'HarbormasterBuildableStatus' => 'Phobject',
'HarbormasterBuildableTransaction' => 'PhabricatorModularTransaction',
'HarbormasterBuildableTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'HarbormasterBuildableTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'HarbormasterBuildableTransactionType' => 'PhabricatorModularTransactionType',
'HarbormasterBuildableViewController' => 'HarbormasterController',
'HarbormasterBuildkiteBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterBuildkiteHookController' => 'HarbormasterController',
'HarbormasterBuiltinBuildStepGroup' => 'HarbormasterBuildStepGroup',
'HarbormasterCircleCIBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterCircleCIHookController' => 'HarbormasterController',
'HarbormasterConduitAPIMethod' => 'ConduitAPIMethod',
'HarbormasterControlBuildStepGroup' => 'HarbormasterBuildStepGroup',
'HarbormasterController' => 'PhabricatorController',
'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
'HarbormasterCreatePlansCapability' => 'PhabricatorPolicyCapability',
'HarbormasterDAO' => 'PhabricatorLiskDAO',
'HarbormasterDrydockBuildStepGroup' => 'HarbormasterBuildStepGroup',
'HarbormasterDrydockCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterDrydockLeaseArtifact' => 'HarbormasterArtifact',
'HarbormasterExecFuture' => 'Future',
'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup',
'HarbormasterFileArtifact' => 'HarbormasterArtifact',
'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterHostArtifact' => 'HarbormasterDrydockLeaseArtifact',
'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterLintMessagesController' => 'HarbormasterController',
'HarbormasterLintPropertyView' => 'AphrontView',
'HarbormasterLogWorker' => 'HarbormasterWorker',
'HarbormasterManagementArchiveLogsWorkflow' => 'HarbormasterManagementWorkflow',
'HarbormasterManagementBuildWorkflow' => 'HarbormasterManagementWorkflow',
'HarbormasterManagementPublishWorkflow' => 'HarbormasterManagementWorkflow',
'HarbormasterManagementRebuildLogWorkflow' => 'HarbormasterManagementWorkflow',
'HarbormasterManagementRestartWorkflow' => 'HarbormasterManagementWorkflow',
'HarbormasterManagementUpdateWorkflow' => 'HarbormasterManagementWorkflow',
'HarbormasterManagementWorkflow' => 'PhabricatorManagementWorkflow',
'HarbormasterManagementWriteLogWorkflow' => 'HarbormasterManagementWorkflow',
'HarbormasterMessageException' => 'Exception',
'HarbormasterMessageType' => 'Phobject',
'HarbormasterObject' => 'HarbormasterDAO',
'HarbormasterOtherBuildStepGroup' => 'HarbormasterBuildStepGroup',
'HarbormasterPlanBehaviorController' => 'HarbormasterPlanController',
'HarbormasterPlanController' => 'HarbormasterController',
'HarbormasterPlanDisableController' => 'HarbormasterPlanController',
'HarbormasterPlanEditController' => 'HarbormasterPlanController',
'HarbormasterPlanListController' => 'HarbormasterPlanController',
'HarbormasterPlanRunController' => 'HarbormasterPlanController',
'HarbormasterPlanViewController' => 'HarbormasterPlanController',
'HarbormasterPrototypeBuildStepGroup' => 'HarbormasterBuildStepGroup',
- 'HarbormasterPublishFragmentBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterQueryAutotargetsConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
'HarbormasterQueryBuildablesConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
'HarbormasterQueryBuildsConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
'HarbormasterQueryBuildsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'HarbormasterRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'HarbormasterRunBuildPlansHeraldAction' => 'HeraldAction',
'HarbormasterSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'HarbormasterScratchTable' => 'HarbormasterDAO',
'HarbormasterSendMessageConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
'HarbormasterSleepBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterStepAddController' => 'HarbormasterPlanController',
'HarbormasterStepDeleteController' => 'HarbormasterPlanController',
'HarbormasterStepEditController' => 'HarbormasterPlanController',
'HarbormasterStepViewController' => 'HarbormasterPlanController',
'HarbormasterString' => 'HarbormasterDAO',
'HarbormasterTargetEngine' => 'Phobject',
'HarbormasterTargetSearchAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'HarbormasterTargetWorker' => 'HarbormasterWorker',
'HarbormasterTestBuildStepGroup' => 'HarbormasterBuildStepGroup',
'HarbormasterThrowExceptionBuildStep' => 'HarbormasterBuildStepImplementation',
'HarbormasterUIEventListener' => 'PhabricatorEventListener',
'HarbormasterURIArtifact' => 'HarbormasterArtifact',
'HarbormasterUnitMessageListController' => 'HarbormasterController',
'HarbormasterUnitMessageViewController' => 'HarbormasterController',
'HarbormasterUnitPropertyView' => 'AphrontView',
'HarbormasterUnitStatus' => 'Phobject',
'HarbormasterUnitSummaryView' => 'AphrontView',
'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterWaitForPreviousBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterWorker' => 'PhabricatorWorker',
'HarbormasterWorkingCopyArtifact' => 'HarbormasterDrydockLeaseArtifact',
'HeraldActingUserField' => 'HeraldField',
'HeraldAction' => 'Phobject',
'HeraldActionGroup' => 'HeraldGroup',
'HeraldActionRecord' => 'HeraldDAO',
'HeraldAdapter' => 'Phobject',
'HeraldAdapterDatasource' => 'PhabricatorTypeaheadDatasource',
'HeraldAlwaysField' => 'HeraldField',
'HeraldAnotherRuleField' => 'HeraldField',
'HeraldApplicationActionGroup' => 'HeraldActionGroup',
'HeraldApplyTranscript' => 'Phobject',
'HeraldBasicFieldGroup' => 'HeraldFieldGroup',
'HeraldBoolFieldValue' => 'HeraldFieldValue',
'HeraldBuildableState' => 'HeraldState',
'HeraldCallWebhookAction' => 'HeraldAction',
'HeraldCommentAction' => 'HeraldAction',
'HeraldCommentContentField' => 'HeraldField',
'HeraldCommitAdapter' => array(
'HeraldAdapter',
'HarbormasterBuildableAdapterInterface',
),
'HeraldCondition' => 'HeraldDAO',
'HeraldConditionResult' => 'HeraldTranscriptResult',
'HeraldConditionTranscript' => 'Phobject',
'HeraldContentSourceField' => 'HeraldField',
'HeraldController' => 'PhabricatorController',
'HeraldCoreStateReasons' => 'HeraldStateReasons',
'HeraldCreateWebhooksCapability' => 'PhabricatorPolicyCapability',
'HeraldDAO' => 'PhabricatorLiskDAO',
'HeraldDeprecatedFieldGroup' => 'HeraldFieldGroup',
'HeraldDifferentialAdapter' => 'HeraldAdapter',
'HeraldDifferentialDiffAdapter' => 'HeraldDifferentialAdapter',
'HeraldDifferentialRevisionAdapter' => array(
'HeraldDifferentialAdapter',
'HarbormasterBuildableAdapterInterface',
),
'HeraldDisableController' => 'HeraldController',
'HeraldDoNothingAction' => 'HeraldAction',
'HeraldEditFieldGroup' => 'HeraldFieldGroup',
'HeraldEffect' => 'Phobject',
'HeraldEmptyFieldValue' => 'HeraldFieldValue',
'HeraldEngine' => 'Phobject',
'HeraldExactProjectsField' => 'HeraldField',
'HeraldField' => 'Phobject',
'HeraldFieldGroup' => 'HeraldGroup',
'HeraldFieldTestCase' => 'PhutilTestCase',
'HeraldFieldValue' => 'Phobject',
'HeraldGroup' => 'Phobject',
'HeraldInvalidActionException' => 'Exception',
'HeraldInvalidConditionException' => 'Exception',
'HeraldMailableState' => 'HeraldState',
'HeraldManageGlobalRulesCapability' => 'PhabricatorPolicyCapability',
'HeraldManagementWorkflow' => 'PhabricatorManagementWorkflow',
'HeraldManiphestTaskAdapter' => 'HeraldAdapter',
'HeraldNewController' => 'HeraldController',
'HeraldNewObjectField' => 'HeraldField',
'HeraldNotifyActionGroup' => 'HeraldActionGroup',
'HeraldObjectTranscript' => 'Phobject',
'HeraldPhameBlogAdapter' => 'HeraldAdapter',
'HeraldPhamePostAdapter' => 'HeraldAdapter',
'HeraldPholioMockAdapter' => 'HeraldAdapter',
'HeraldPonderQuestionAdapter' => 'HeraldAdapter',
'HeraldPreCommitAdapter' => 'HeraldAdapter',
'HeraldPreCommitContentAdapter' => 'HeraldPreCommitAdapter',
'HeraldPreCommitRefAdapter' => 'HeraldPreCommitAdapter',
'HeraldPreventActionGroup' => 'HeraldActionGroup',
'HeraldProjectsField' => 'PhabricatorProjectTagsField',
'HeraldRecursiveConditionsException' => 'Exception',
'HeraldRelatedFieldGroup' => 'HeraldFieldGroup',
'HeraldRemarkupFieldValue' => 'HeraldFieldValue',
'HeraldRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'HeraldRule' => array(
'HeraldDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorFlaggableInterface',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorIndexableInterface',
'PhabricatorSubscribableInterface',
),
'HeraldRuleActionAffectsObjectEdgeType' => 'PhabricatorEdgeType',
'HeraldRuleAdapter' => 'HeraldAdapter',
'HeraldRuleAdapterField' => 'HeraldRuleField',
'HeraldRuleController' => 'HeraldController',
'HeraldRuleDatasource' => 'PhabricatorTypeaheadDatasource',
'HeraldRuleDisableTransaction' => 'HeraldRuleTransactionType',
'HeraldRuleEditTransaction' => 'HeraldRuleTransactionType',
'HeraldRuleEditor' => 'PhabricatorApplicationTransactionEditor',
'HeraldRuleEvaluationException' => 'Exception',
'HeraldRuleField' => 'HeraldField',
'HeraldRuleFieldGroup' => 'HeraldFieldGroup',
'HeraldRuleIndexEngineExtension' => 'PhabricatorEdgeIndexEngineExtension',
'HeraldRuleListController' => 'HeraldController',
'HeraldRuleListView' => 'AphrontView',
'HeraldRuleManagementWorkflow' => 'HeraldManagementWorkflow',
'HeraldRuleNameTransaction' => 'HeraldRuleTransactionType',
'HeraldRulePHIDType' => 'PhabricatorPHIDType',
'HeraldRuleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HeraldRuleReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'HeraldRuleResult' => 'HeraldTranscriptResult',
'HeraldRuleSearchEngine' => 'PhabricatorApplicationSearchEngine',
'HeraldRuleSerializer' => 'Phobject',
'HeraldRuleTestCase' => 'PhabricatorTestCase',
'HeraldRuleTransaction' => 'PhabricatorModularTransaction',
'HeraldRuleTransactionType' => 'PhabricatorModularTransactionType',
'HeraldRuleTranscript' => 'Phobject',
'HeraldRuleTypeConfig' => 'Phobject',
'HeraldRuleTypeDatasource' => 'PhabricatorTypeaheadDatasource',
'HeraldRuleTypeField' => 'HeraldRuleField',
'HeraldRuleViewController' => 'HeraldController',
'HeraldSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'HeraldSelectFieldValue' => 'HeraldFieldValue',
'HeraldSpaceField' => 'HeraldField',
'HeraldState' => 'Phobject',
'HeraldStateReasons' => 'Phobject',
'HeraldSubscribersField' => 'HeraldField',
'HeraldSupportActionGroup' => 'HeraldActionGroup',
'HeraldSupportFieldGroup' => 'HeraldFieldGroup',
'HeraldTestConsoleController' => 'HeraldController',
'HeraldTestManagementWorkflow' => 'HeraldManagementWorkflow',
'HeraldTextFieldValue' => 'HeraldFieldValue',
'HeraldTokenizerFieldValue' => 'HeraldFieldValue',
'HeraldTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'HeraldTransactionsFieldGroup' => 'HeraldFieldGroup',
'HeraldTranscript' => array(
'HeraldDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
'HeraldTranscriptController' => 'HeraldController',
'HeraldTranscriptDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
'HeraldTranscriptGarbageCollector' => 'PhabricatorGarbageCollector',
'HeraldTranscriptListController' => 'HeraldController',
'HeraldTranscriptPHIDType' => 'PhabricatorPHIDType',
'HeraldTranscriptQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HeraldTranscriptResult' => 'Phobject',
'HeraldTranscriptSearchEngine' => 'PhabricatorApplicationSearchEngine',
'HeraldTranscriptTestCase' => 'PhabricatorTestCase',
'HeraldUtilityActionGroup' => 'HeraldActionGroup',
'HeraldWebhook' => array(
'HeraldDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorDestructibleInterface',
'PhabricatorProjectInterface',
),
'HeraldWebhookCallManagementWorkflow' => 'HeraldWebhookManagementWorkflow',
'HeraldWebhookController' => 'HeraldController',
'HeraldWebhookDatasource' => 'PhabricatorTypeaheadDatasource',
'HeraldWebhookEditController' => 'HeraldWebhookController',
'HeraldWebhookEditEngine' => 'PhabricatorEditEngine',
'HeraldWebhookEditor' => 'PhabricatorApplicationTransactionEditor',
'HeraldWebhookKeyController' => 'HeraldWebhookController',
'HeraldWebhookListController' => 'HeraldWebhookController',
'HeraldWebhookManagementWorkflow' => 'PhabricatorManagementWorkflow',
'HeraldWebhookNameTransaction' => 'HeraldWebhookTransactionType',
'HeraldWebhookPHIDType' => 'PhabricatorPHIDType',
'HeraldWebhookQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HeraldWebhookRequest' => array(
'HeraldDAO',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
),
'HeraldWebhookRequestGarbageCollector' => 'PhabricatorGarbageCollector',
'HeraldWebhookRequestListView' => 'AphrontView',
'HeraldWebhookRequestPHIDType' => 'PhabricatorPHIDType',
'HeraldWebhookRequestQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HeraldWebhookSearchEngine' => 'PhabricatorApplicationSearchEngine',
'HeraldWebhookStatusTransaction' => 'HeraldWebhookTransactionType',
'HeraldWebhookTestController' => 'HeraldWebhookController',
'HeraldWebhookTransaction' => 'PhabricatorModularTransaction',
'HeraldWebhookTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'HeraldWebhookTransactionType' => 'PhabricatorModularTransactionType',
'HeraldWebhookURITransaction' => 'HeraldWebhookTransactionType',
'HeraldWebhookViewController' => 'HeraldWebhookController',
'HeraldWebhookWorker' => 'PhabricatorWorker',
'Javelin' => 'Phobject',
'LegalpadController' => 'PhabricatorController',
'LegalpadCreateDocumentsCapability' => 'PhabricatorPolicyCapability',
'LegalpadDAO' => 'PhabricatorLiskDAO',
'LegalpadDefaultEditCapability' => 'PhabricatorPolicyCapability',
'LegalpadDefaultViewCapability' => 'PhabricatorPolicyCapability',
'LegalpadDocument' => array(
'LegalpadDAO',
'PhabricatorPolicyInterface',
'PhabricatorSubscribableInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorDestructibleInterface',
),
'LegalpadDocumentBody' => array(
'LegalpadDAO',
'PhabricatorMarkupInterface',
),
'LegalpadDocumentDatasource' => 'PhabricatorTypeaheadDatasource',
'LegalpadDocumentDoneController' => 'LegalpadController',
'LegalpadDocumentEditController' => 'LegalpadController',
'LegalpadDocumentEditEngine' => 'PhabricatorEditEngine',
'LegalpadDocumentEditor' => 'PhabricatorApplicationTransactionEditor',
'LegalpadDocumentListController' => 'LegalpadController',
'LegalpadDocumentManageController' => 'LegalpadController',
'LegalpadDocumentPreambleTransaction' => 'LegalpadDocumentTransactionType',
'LegalpadDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'LegalpadDocumentRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'LegalpadDocumentRequireSignatureTransaction' => 'LegalpadDocumentTransactionType',
'LegalpadDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine',
'LegalpadDocumentSignController' => 'LegalpadController',
'LegalpadDocumentSignature' => array(
'LegalpadDAO',
'PhabricatorPolicyInterface',
),
'LegalpadDocumentSignatureAddController' => 'LegalpadController',
'LegalpadDocumentSignatureListController' => 'LegalpadController',
'LegalpadDocumentSignatureQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'LegalpadDocumentSignatureSearchEngine' => 'PhabricatorApplicationSearchEngine',
'LegalpadDocumentSignatureTypeTransaction' => 'LegalpadDocumentTransactionType',
'LegalpadDocumentSignatureVerificationController' => 'LegalpadController',
'LegalpadDocumentSignatureViewController' => 'LegalpadController',
'LegalpadDocumentTextTransaction' => 'LegalpadDocumentTransactionType',
'LegalpadDocumentTitleTransaction' => 'LegalpadDocumentTransactionType',
'LegalpadDocumentTransactionType' => 'PhabricatorModularTransactionType',
'LegalpadMailReceiver' => 'PhabricatorObjectMailReceiver',
'LegalpadObjectNeedsSignatureEdgeType' => 'PhabricatorEdgeType',
'LegalpadReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'LegalpadRequireSignatureHeraldAction' => 'HeraldAction',
'LegalpadSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'LegalpadSignatureNeededByObjectEdgeType' => 'PhabricatorEdgeType',
'LegalpadTransaction' => 'PhabricatorModularTransaction',
'LegalpadTransactionComment' => 'PhabricatorApplicationTransactionComment',
'LegalpadTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'LiskChunkTestCase' => 'PhabricatorTestCase',
'LiskDAO' => array(
'Phobject',
'AphrontDatabaseTableRefInterface',
),
'LiskDAOTestCase' => 'PhabricatorTestCase',
'LiskEphemeralObjectException' => 'Exception',
'LiskFixtureTestCase' => 'PhabricatorTestCase',
'LiskIsolationTestCase' => 'PhabricatorTestCase',
'LiskIsolationTestDAO' => 'LiskDAO',
'LiskIsolationTestDAOException' => 'Exception',
'LiskMigrationIterator' => 'PhutilBufferedIterator',
'LiskRawMigrationIterator' => 'PhutilBufferedIterator',
'MacroConduitAPIMethod' => 'ConduitAPIMethod',
'MacroCreateMemeConduitAPIMethod' => 'MacroConduitAPIMethod',
'MacroEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'MacroEmojiExample' => 'PhabricatorUIExample',
'MacroQueryConduitAPIMethod' => 'MacroConduitAPIMethod',
'ManiphestAssignEmailCommand' => 'ManiphestEmailCommand',
'ManiphestAssigneeDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'ManiphestBulkEditCapability' => 'PhabricatorPolicyCapability',
'ManiphestBulkEditController' => 'ManiphestController',
'ManiphestClaimEmailCommand' => 'ManiphestEmailCommand',
'ManiphestCloseEmailCommand' => 'ManiphestEmailCommand',
'ManiphestConduitAPIMethod' => 'ConduitAPIMethod',
'ManiphestConfiguredCustomField' => array(
'ManiphestCustomField',
'PhabricatorStandardCustomFieldInterface',
),
'ManiphestConstants' => 'Phobject',
'ManiphestController' => 'PhabricatorController',
'ManiphestCreateMailReceiver' => 'PhabricatorApplicationMailReceiver',
'ManiphestCreateTaskConduitAPIMethod' => 'ManiphestConduitAPIMethod',
'ManiphestCustomField' => 'PhabricatorCustomField',
'ManiphestCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
'ManiphestCustomFieldStatusParser' => 'PhabricatorCustomFieldMonogramParser',
'ManiphestCustomFieldStatusParserTestCase' => 'PhabricatorTestCase',
'ManiphestCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
'ManiphestCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage',
'ManiphestDAO' => 'PhabricatorLiskDAO',
'ManiphestDefaultEditCapability' => 'PhabricatorPolicyCapability',
'ManiphestDefaultViewCapability' => 'PhabricatorPolicyCapability',
'ManiphestEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'ManiphestEditEngine' => 'PhabricatorEditEngine',
'ManiphestEmailCommand' => 'MetaMTAEmailTransactionCommand',
'ManiphestGetTaskTransactionsConduitAPIMethod' => 'ManiphestConduitAPIMethod',
'ManiphestHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'ManiphestInfoConduitAPIMethod' => 'ManiphestConduitAPIMethod',
'ManiphestMailEngineExtension' => 'PhabricatorMailEngineExtension',
'ManiphestNameIndex' => 'ManiphestDAO',
'ManiphestPointsConfigType' => 'PhabricatorJSONConfigType',
'ManiphestPrioritiesConfigType' => 'PhabricatorJSONConfigType',
'ManiphestPriorityEmailCommand' => 'ManiphestEmailCommand',
'ManiphestPrioritySearchConduitAPIMethod' => 'ManiphestConduitAPIMethod',
'ManiphestProjectNameFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
'ManiphestQueryConduitAPIMethod' => 'ManiphestConduitAPIMethod',
'ManiphestQueryStatusesConduitAPIMethod' => 'ManiphestConduitAPIMethod',
'ManiphestRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'ManiphestReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'ManiphestReportController' => 'ManiphestController',
'ManiphestSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'ManiphestSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'ManiphestStatusEmailCommand' => 'ManiphestEmailCommand',
'ManiphestStatusSearchConduitAPIMethod' => 'ManiphestConduitAPIMethod',
'ManiphestStatusesConfigType' => 'PhabricatorJSONConfigType',
'ManiphestSubtypesConfigType' => 'PhabricatorJSONConfigType',
'ManiphestTask' => array(
'ManiphestDAO',
'PhabricatorSubscribableInterface',
'PhabricatorMarkupInterface',
'PhabricatorPolicyInterface',
'PhabricatorTokenReceiverInterface',
'PhabricatorFlaggableInterface',
'PhabricatorMentionableInterface',
'PhrequentTrackableInterface',
'PhabricatorCustomFieldInterface',
'PhabricatorDestructibleInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorProjectInterface',
'PhabricatorSpacesInterface',
'PhabricatorConduitResultInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
'DoorkeeperBridgedObjectInterface',
'PhabricatorEditEngineSubtypeInterface',
'PhabricatorEditEngineLockableInterface',
'PhabricatorEditEngineMFAInterface',
'PhabricatorPolicyCodexInterface',
'PhabricatorUnlockableInterface',
),
'ManiphestTaskAssignHeraldAction' => 'HeraldAction',
'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction',
'ManiphestTaskAssignSelfHeraldAction' => 'ManiphestTaskAssignHeraldAction',
'ManiphestTaskAssigneeHeraldField' => 'ManiphestTaskHeraldField',
'ManiphestTaskAttachTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskAuthorHeraldField' => 'ManiphestTaskHeraldField',
'ManiphestTaskAuthorPolicyRule' => 'PhabricatorPolicyRule',
'ManiphestTaskBulkEngine' => 'PhabricatorBulkEngine',
'ManiphestTaskCloseAsDuplicateRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskClosedStatusDatasource' => 'PhabricatorTypeaheadDatasource',
'ManiphestTaskCoverImageTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskDependedOnByTaskEdgeType' => 'PhabricatorEdgeType',
'ManiphestTaskDependsOnTaskEdgeType' => 'PhabricatorEdgeType',
'ManiphestTaskDescriptionHeraldField' => 'ManiphestTaskHeraldField',
'ManiphestTaskDescriptionTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskDetailController' => 'ManiphestController',
'ManiphestTaskEdgeTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskEditController' => 'ManiphestController',
'ManiphestTaskEditEngineLock' => 'PhabricatorEditEngineLock',
'ManiphestTaskFerretEngine' => 'PhabricatorFerretEngine',
'ManiphestTaskFulltextEngine' => 'PhabricatorFulltextEngine',
'ManiphestTaskGraph' => 'PhabricatorObjectGraph',
'ManiphestTaskGraphController' => 'ManiphestController',
'ManiphestTaskHasCommitEdgeType' => 'PhabricatorEdgeType',
'ManiphestTaskHasCommitRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskHasDuplicateTaskEdgeType' => 'PhabricatorEdgeType',
'ManiphestTaskHasMockEdgeType' => 'PhabricatorEdgeType',
'ManiphestTaskHasMockRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskHasParentRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskHasRevisionEdgeType' => 'PhabricatorEdgeType',
'ManiphestTaskHasRevisionRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskHasSubtaskRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskHeraldField' => 'HeraldField',
'ManiphestTaskHeraldFieldGroup' => 'HeraldFieldGroup',
'ManiphestTaskIsDuplicateOfTaskEdgeType' => 'PhabricatorEdgeType',
'ManiphestTaskListController' => 'ManiphestController',
'ManiphestTaskListHTTPParameterType' => 'AphrontListHTTPParameterType',
'ManiphestTaskListView' => 'ManiphestView',
'ManiphestTaskMFAEngine' => 'PhabricatorEditEngineMFAEngine',
'ManiphestTaskMailReceiver' => 'PhabricatorObjectMailReceiver',
'ManiphestTaskMergeInRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskMergedFromTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskMergedIntoTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskOpenStatusDatasource' => 'PhabricatorTypeaheadDatasource',
'ManiphestTaskOwnerTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskPHIDResolver' => 'PhabricatorPHIDResolver',
'ManiphestTaskPHIDType' => 'PhabricatorPHIDType',
'ManiphestTaskParentTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskPoints' => 'Phobject',
'ManiphestTaskPointsTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskPolicyCodex' => 'PhabricatorPolicyCodex',
'ManiphestTaskPriority' => 'ManiphestConstants',
'ManiphestTaskPriorityDatasource' => 'PhabricatorTypeaheadDatasource',
'ManiphestTaskPriorityHeraldAction' => 'HeraldAction',
'ManiphestTaskPriorityHeraldField' => 'ManiphestTaskHeraldField',
'ManiphestTaskPriorityTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'ManiphestTaskRelationship' => 'PhabricatorObjectRelationship',
'ManiphestTaskRelationshipSource' => 'PhabricatorObjectRelationshipSource',
'ManiphestTaskResultListView' => 'ManiphestView',
'ManiphestTaskSearchEngine' => 'PhabricatorApplicationSearchEngine',
'ManiphestTaskStatus' => 'ManiphestConstants',
'ManiphestTaskStatusDatasource' => 'PhabricatorTypeaheadDatasource',
'ManiphestTaskStatusFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'ManiphestTaskStatusHeraldAction' => 'HeraldAction',
'ManiphestTaskStatusHeraldField' => 'ManiphestTaskHeraldField',
'ManiphestTaskStatusTestCase' => 'PhabricatorTestCase',
'ManiphestTaskStatusTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskSubpriorityTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskSubtaskController' => 'ManiphestController',
'ManiphestTaskSubtypeDatasource' => 'PhabricatorTypeaheadDatasource',
'ManiphestTaskTitleHeraldField' => 'ManiphestTaskHeraldField',
'ManiphestTaskTitleTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskTransactionType' => 'PhabricatorModularTransactionType',
'ManiphestTaskUnblockTransaction' => 'ManiphestTaskTransactionType',
'ManiphestTaskUnlockEngine' => 'PhabricatorUnlockEngine',
'ManiphestTransaction' => 'PhabricatorModularTransaction',
'ManiphestTransactionComment' => 'PhabricatorApplicationTransactionComment',
'ManiphestTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'ManiphestTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'ManiphestUpdateConduitAPIMethod' => 'ManiphestConduitAPIMethod',
'ManiphestView' => 'AphrontView',
'MetaMTAEmailTransactionCommand' => 'Phobject',
'MetaMTAEmailTransactionCommandTestCase' => 'PhabricatorTestCase',
'MetaMTAMailReceivedGarbageCollector' => 'PhabricatorGarbageCollector',
'MetaMTAMailSentGarbageCollector' => 'PhabricatorGarbageCollector',
'MetaMTAReceivedMailStatus' => 'Phobject',
'MultimeterContext' => 'MultimeterDimension',
'MultimeterControl' => 'Phobject',
'MultimeterController' => 'PhabricatorController',
'MultimeterDAO' => 'PhabricatorLiskDAO',
'MultimeterDimension' => 'MultimeterDAO',
'MultimeterEvent' => 'MultimeterDAO',
'MultimeterEventGarbageCollector' => 'PhabricatorGarbageCollector',
'MultimeterHost' => 'MultimeterDimension',
'MultimeterLabel' => 'MultimeterDimension',
'MultimeterSampleController' => 'MultimeterController',
'MultimeterViewer' => 'MultimeterDimension',
'NuanceCommandImplementation' => 'Phobject',
'NuanceConduitAPIMethod' => 'ConduitAPIMethod',
'NuanceConsoleController' => 'NuanceController',
'NuanceContentSource' => 'PhabricatorContentSource',
'NuanceController' => 'PhabricatorController',
'NuanceDAO' => 'PhabricatorLiskDAO',
'NuanceFormItemType' => 'NuanceItemType',
'NuanceGitHubEventItemType' => 'NuanceItemType',
'NuanceGitHubImportCursor' => 'NuanceImportCursor',
'NuanceGitHubIssuesImportCursor' => 'NuanceGitHubImportCursor',
'NuanceGitHubRawEvent' => 'Phobject',
'NuanceGitHubRawEventTestCase' => 'PhabricatorTestCase',
'NuanceGitHubRepositoryImportCursor' => 'NuanceGitHubImportCursor',
'NuanceGitHubRepositorySourceDefinition' => 'NuanceSourceDefinition',
'NuanceImportCursor' => 'Phobject',
'NuanceImportCursorData' => array(
'NuanceDAO',
'PhabricatorPolicyInterface',
),
'NuanceImportCursorDataQuery' => 'NuanceQuery',
'NuanceImportCursorPHIDType' => 'PhabricatorPHIDType',
'NuanceItem' => array(
'NuanceDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
),
'NuanceItemActionController' => 'NuanceController',
'NuanceItemCommand' => array(
'NuanceDAO',
'PhabricatorPolicyInterface',
),
'NuanceItemCommandQuery' => 'NuanceQuery',
'NuanceItemCommandSpec' => 'Phobject',
'NuanceItemCommandTransaction' => 'NuanceItemTransactionType',
'NuanceItemController' => 'NuanceController',
'NuanceItemEditor' => 'PhabricatorApplicationTransactionEditor',
'NuanceItemListController' => 'NuanceItemController',
'NuanceItemManageController' => 'NuanceController',
'NuanceItemOwnerTransaction' => 'NuanceItemTransactionType',
'NuanceItemPHIDType' => 'PhabricatorPHIDType',
'NuanceItemPropertyTransaction' => 'NuanceItemTransactionType',
'NuanceItemQuery' => 'NuanceQuery',
'NuanceItemQueueTransaction' => 'NuanceItemTransactionType',
'NuanceItemRequestorTransaction' => 'NuanceItemTransactionType',
'NuanceItemSearchEngine' => 'PhabricatorApplicationSearchEngine',
'NuanceItemSourceTransaction' => 'NuanceItemTransactionType',
'NuanceItemStatusTransaction' => 'NuanceItemTransactionType',
'NuanceItemTransaction' => 'NuanceTransaction',
'NuanceItemTransactionComment' => 'PhabricatorApplicationTransactionComment',
'NuanceItemTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'NuanceItemTransactionType' => 'PhabricatorModularTransactionType',
'NuanceItemType' => 'Phobject',
'NuanceItemUpdateWorker' => 'NuanceWorker',
'NuanceItemViewController' => 'NuanceController',
'NuanceManagementImportWorkflow' => 'NuanceManagementWorkflow',
'NuanceManagementUpdateWorkflow' => 'NuanceManagementWorkflow',
'NuanceManagementWorkflow' => 'PhabricatorManagementWorkflow',
'NuancePhabricatorFormSourceDefinition' => 'NuanceSourceDefinition',
'NuanceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'NuanceQueue' => array(
'NuanceDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
),
'NuanceQueueController' => 'NuanceController',
'NuanceQueueDatasource' => 'PhabricatorTypeaheadDatasource',
'NuanceQueueEditController' => 'NuanceQueueController',
'NuanceQueueEditEngine' => 'PhabricatorEditEngine',
'NuanceQueueEditor' => 'PhabricatorApplicationTransactionEditor',
'NuanceQueueListController' => 'NuanceQueueController',
'NuanceQueueNameTransaction' => 'NuanceQueueTransactionType',
'NuanceQueuePHIDType' => 'PhabricatorPHIDType',
'NuanceQueueQuery' => 'NuanceQuery',
'NuanceQueueSearchEngine' => 'PhabricatorApplicationSearchEngine',
'NuanceQueueTransaction' => 'NuanceTransaction',
'NuanceQueueTransactionComment' => 'PhabricatorApplicationTransactionComment',
'NuanceQueueTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'NuanceQueueTransactionType' => 'PhabricatorModularTransactionType',
'NuanceQueueViewController' => 'NuanceQueueController',
'NuanceQueueWorkController' => 'NuanceQueueController',
'NuanceSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'NuanceSource' => array(
'NuanceDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorNgramsInterface',
),
'NuanceSourceActionController' => 'NuanceController',
'NuanceSourceController' => 'NuanceController',
'NuanceSourceDefaultEditCapability' => 'PhabricatorPolicyCapability',
'NuanceSourceDefaultQueueTransaction' => 'NuanceSourceTransactionType',
'NuanceSourceDefaultViewCapability' => 'PhabricatorPolicyCapability',
'NuanceSourceDefinition' => 'Phobject',
'NuanceSourceDefinitionTestCase' => 'PhabricatorTestCase',
'NuanceSourceEditController' => 'NuanceSourceController',
'NuanceSourceEditEngine' => 'PhabricatorEditEngine',
'NuanceSourceEditor' => 'PhabricatorApplicationTransactionEditor',
'NuanceSourceListController' => 'NuanceSourceController',
'NuanceSourceManageCapability' => 'PhabricatorPolicyCapability',
'NuanceSourceNameNgrams' => 'PhabricatorSearchNgrams',
'NuanceSourceNameTransaction' => 'NuanceSourceTransactionType',
'NuanceSourcePHIDType' => 'PhabricatorPHIDType',
'NuanceSourceQuery' => 'NuanceQuery',
'NuanceSourceSearchEngine' => 'PhabricatorApplicationSearchEngine',
'NuanceSourceTransaction' => 'NuanceTransaction',
'NuanceSourceTransactionComment' => 'PhabricatorApplicationTransactionComment',
'NuanceSourceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'NuanceSourceTransactionType' => 'PhabricatorModularTransactionType',
'NuanceSourceViewController' => 'NuanceSourceController',
'NuanceTransaction' => 'PhabricatorModularTransaction',
'NuanceTrashCommand' => 'NuanceCommandImplementation',
'NuanceWorker' => 'PhabricatorWorker',
'OwnersConduitAPIMethod' => 'ConduitAPIMethod',
'OwnersEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'OwnersPackageReplyHandler' => 'PhabricatorMailReplyHandler',
'OwnersQueryConduitAPIMethod' => 'OwnersConduitAPIMethod',
'OwnersSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PHIDConduitAPIMethod' => 'ConduitAPIMethod',
'PHIDInfoConduitAPIMethod' => 'PHIDConduitAPIMethod',
'PHIDLookupConduitAPIMethod' => 'PHIDConduitAPIMethod',
'PHIDQueryConduitAPIMethod' => 'PHIDConduitAPIMethod',
'PHUI' => 'Phobject',
'PHUIActionPanelExample' => 'PhabricatorUIExample',
'PHUIActionPanelView' => 'AphrontTagView',
'PHUIApplicationMenuView' => 'Phobject',
'PHUIBadgeBoxView' => 'AphrontTagView',
'PHUIBadgeExample' => 'PhabricatorUIExample',
'PHUIBadgeMiniView' => 'AphrontTagView',
'PHUIBadgeView' => 'AphrontTagView',
'PHUIBigInfoExample' => 'PhabricatorUIExample',
'PHUIBigInfoView' => 'AphrontTagView',
'PHUIBoxExample' => 'PhabricatorUIExample',
'PHUIBoxView' => 'AphrontTagView',
'PHUIButtonBarExample' => 'PhabricatorUIExample',
'PHUIButtonBarView' => 'AphrontTagView',
'PHUIButtonExample' => 'PhabricatorUIExample',
'PHUIButtonView' => 'AphrontTagView',
'PHUICMSView' => 'AphrontTagView',
'PHUICalendarDayView' => 'AphrontView',
'PHUICalendarListView' => 'AphrontTagView',
'PHUICalendarMonthView' => 'AphrontView',
'PHUICalendarWeekView' => 'AphrontView',
'PHUICalendarWidgetView' => 'AphrontTagView',
'PHUIColor' => 'Phobject',
'PHUIColorPalletteExample' => 'PhabricatorUIExample',
'PHUICrumbView' => 'AphrontView',
'PHUICrumbsView' => 'AphrontView',
'PHUICurtainExtension' => 'Phobject',
'PHUICurtainObjectRefListView' => 'AphrontTagView',
'PHUICurtainObjectRefView' => 'AphrontTagView',
'PHUICurtainPanelView' => 'AphrontTagView',
'PHUICurtainView' => 'AphrontTagView',
'PHUIDiffGraphView' => 'Phobject',
'PHUIDiffGraphViewTestCase' => 'PhabricatorTestCase',
'PHUIDiffInlineCommentDetailView' => 'PHUIDiffInlineCommentView',
'PHUIDiffInlineCommentEditView' => 'PHUIDiffInlineCommentView',
'PHUIDiffInlineCommentPreviewListView' => 'AphrontView',
'PHUIDiffInlineCommentRowScaffold' => 'AphrontView',
'PHUIDiffInlineCommentTableScaffold' => 'AphrontView',
'PHUIDiffInlineCommentUndoView' => 'PHUIDiffInlineCommentView',
'PHUIDiffInlineCommentView' => 'AphrontView',
'PHUIDiffInlineThreader' => 'Phobject',
'PHUIDiffOneUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold',
'PHUIDiffRevealIconView' => 'AphrontView',
'PHUIDiffTableOfContentsItemView' => 'AphrontView',
'PHUIDiffTableOfContentsListView' => 'AphrontView',
'PHUIDiffTwoUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold',
'PHUIDocumentSummaryView' => 'AphrontTagView',
'PHUIDocumentView' => 'AphrontTagView',
'PHUIFeedStoryExample' => 'PhabricatorUIExample',
'PHUIFeedStoryView' => 'AphrontView',
'PHUIFormDividerControl' => 'AphrontFormControl',
'PHUIFormFileControl' => 'AphrontFormControl',
'PHUIFormFreeformDateControl' => 'AphrontFormControl',
'PHUIFormIconSetControl' => 'AphrontFormControl',
'PHUIFormInsetView' => 'AphrontView',
'PHUIFormLayoutView' => 'AphrontView',
'PHUIFormNumberControl' => 'AphrontFormControl',
'PHUIFormTimerControl' => 'AphrontFormControl',
'PHUIFormationColumnDynamicView' => 'PHUIFormationColumnView',
'PHUIFormationColumnItem' => 'Phobject',
'PHUIFormationColumnView' => 'AphrontAutoIDView',
'PHUIFormationContentView' => 'PHUIFormationColumnView',
'PHUIFormationExpanderView' => 'AphrontAutoIDView',
'PHUIFormationFlankView' => 'PHUIFormationColumnDynamicView',
'PHUIFormationResizerView' => 'PHUIFormationColumnView',
'PHUIFormationView' => 'AphrontAutoIDView',
'PHUIHandleListView' => 'AphrontTagView',
'PHUIHandleTagListView' => 'AphrontTagView',
'PHUIHandleView' => 'AphrontView',
'PHUIHeadThingView' => 'AphrontTagView',
'PHUIHeaderView' => 'AphrontTagView',
'PHUIHomeView' => 'AphrontTagView',
'PHUIHovercardUIExample' => 'PhabricatorUIExample',
'PHUIHovercardView' => 'AphrontTagView',
'PHUIIconCircleView' => 'AphrontTagView',
'PHUIIconExample' => 'PhabricatorUIExample',
'PHUIIconView' => 'AphrontTagView',
'PHUIImageMaskExample' => 'PhabricatorUIExample',
'PHUIImageMaskView' => 'AphrontTagView',
'PHUIInfoExample' => 'PhabricatorUIExample',
'PHUIInfoView' => 'AphrontTagView',
'PHUIInvisibleCharacterTestCase' => 'PhabricatorTestCase',
'PHUIInvisibleCharacterView' => 'AphrontView',
'PHUILauncherView' => 'AphrontTagView',
'PHUILeftRightExample' => 'PhabricatorUIExample',
'PHUILeftRightView' => 'AphrontTagView',
'PHUILinkView' => 'AphrontTagView',
'PHUIListExample' => 'PhabricatorUIExample',
'PHUIListItemView' => 'AphrontTagView',
'PHUIListView' => 'AphrontTagView',
'PHUIListViewTestCase' => 'PhabricatorTestCase',
'PHUIObjectBoxView' => 'AphrontTagView',
'PHUIObjectItemListExample' => 'PhabricatorUIExample',
'PHUIObjectItemListView' => 'AphrontTagView',
'PHUIObjectItemView' => 'AphrontTagView',
'PHUIPagerView' => 'AphrontView',
'PHUIPinboardItemView' => 'AphrontView',
'PHUIPinboardView' => 'AphrontView',
'PHUIPolicySectionView' => 'AphrontTagView',
'PHUIPropertyGroupView' => 'AphrontTagView',
'PHUIPropertyListExample' => 'PhabricatorUIExample',
'PHUIPropertyListView' => 'AphrontView',
'PHUIRemarkupImageView' => 'AphrontView',
'PHUIRemarkupPreviewPanel' => 'AphrontTagView',
'PHUIRemarkupView' => 'AphrontView',
'PHUISegmentBarSegmentView' => 'AphrontTagView',
'PHUISegmentBarView' => 'AphrontTagView',
'PHUISpacesNamespaceContextView' => 'AphrontView',
'PHUIStatusItemView' => 'AphrontTagView',
'PHUIStatusListView' => 'AphrontTagView',
'PHUITabGroupView' => 'AphrontTagView',
'PHUITabView' => 'AphrontTagView',
'PHUITagExample' => 'PhabricatorUIExample',
'PHUITagView' => 'AphrontTagView',
'PHUITimelineEventView' => 'AphrontView',
'PHUITimelineExample' => 'PhabricatorUIExample',
'PHUITimelineView' => 'AphrontView',
'PHUITwoColumnView' => 'AphrontTagView',
'PHUITypeaheadExample' => 'PhabricatorUIExample',
'PHUIUserAvailabilityView' => 'AphrontTagView',
'PHUIWorkboardView' => 'AphrontTagView',
'PHUIWorkpanelView' => 'AphrontTagView',
'PHUIXComponentsExample' => 'PhabricatorUIExample',
'PassphraseAbstractKey' => 'Phobject',
'PassphraseConduitAPIMethod' => 'ConduitAPIMethod',
'PassphraseController' => 'PhabricatorController',
'PassphraseCredential' => array(
'PassphraseDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorFlaggableInterface',
'PhabricatorSubscribableInterface',
'PhabricatorDestructibleInterface',
'PhabricatorSpacesInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
),
'PassphraseCredentialAuthorPolicyRule' => 'PhabricatorPolicyRule',
'PassphraseCredentialConduitController' => 'PassphraseController',
'PassphraseCredentialConduitTransaction' => 'PassphraseCredentialTransactionType',
'PassphraseCredentialControl' => 'AphrontFormControl',
'PassphraseCredentialCreateController' => 'PassphraseController',
'PassphraseCredentialDescriptionTransaction' => 'PassphraseCredentialTransactionType',
'PassphraseCredentialDestroyController' => 'PassphraseController',
'PassphraseCredentialDestroyTransaction' => 'PassphraseCredentialTransactionType',
'PassphraseCredentialEditController' => 'PassphraseController',
'PassphraseCredentialFerretEngine' => 'PhabricatorFerretEngine',
'PassphraseCredentialFulltextEngine' => 'PhabricatorFulltextEngine',
'PassphraseCredentialListController' => 'PassphraseController',
'PassphraseCredentialLockController' => 'PassphraseController',
'PassphraseCredentialLockTransaction' => 'PassphraseCredentialTransactionType',
'PassphraseCredentialLookedAtTransaction' => 'PassphraseCredentialTransactionType',
'PassphraseCredentialNameTransaction' => 'PassphraseCredentialTransactionType',
'PassphraseCredentialPHIDType' => 'PhabricatorPHIDType',
'PassphraseCredentialPublicController' => 'PassphraseController',
'PassphraseCredentialQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PassphraseCredentialRevealController' => 'PassphraseController',
'PassphraseCredentialSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PassphraseCredentialSecretIDTransaction' => 'PassphraseCredentialTransactionType',
'PassphraseCredentialTransaction' => 'PhabricatorModularTransaction',
'PassphraseCredentialTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'PassphraseCredentialTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PassphraseCredentialTransactionType' => 'PhabricatorModularTransactionType',
'PassphraseCredentialType' => 'Phobject',
'PassphraseCredentialTypeTestCase' => 'PhabricatorTestCase',
'PassphraseCredentialUsernameTransaction' => 'PassphraseCredentialTransactionType',
'PassphraseCredentialViewController' => 'PassphraseController',
'PassphraseDAO' => 'PhabricatorLiskDAO',
'PassphraseDefaultEditCapability' => 'PhabricatorPolicyCapability',
'PassphraseDefaultViewCapability' => 'PhabricatorPolicyCapability',
'PassphraseNoteCredentialType' => 'PassphraseCredentialType',
'PassphrasePasswordCredentialType' => 'PassphraseCredentialType',
'PassphrasePasswordKey' => 'PassphraseAbstractKey',
'PassphraseQueryConduitAPIMethod' => 'PassphraseConduitAPIMethod',
'PassphraseRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'PassphraseSSHGeneratedKeyCredentialType' => 'PassphraseSSHPrivateKeyCredentialType',
'PassphraseSSHKey' => 'PassphraseAbstractKey',
'PassphraseSSHPrivateKeyCredentialType' => 'PassphraseCredentialType',
'PassphraseSSHPrivateKeyFileCredentialType' => 'PassphraseSSHPrivateKeyCredentialType',
'PassphraseSSHPrivateKeyTextCredentialType' => 'PassphraseSSHPrivateKeyCredentialType',
'PassphraseSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PassphraseSecret' => 'PassphraseDAO',
'PassphraseTokenCredentialType' => 'PassphraseCredentialType',
'PasteConduitAPIMethod' => 'ConduitAPIMethod',
'PasteCreateConduitAPIMethod' => 'PasteConduitAPIMethod',
'PasteCreateMailReceiver' => 'PhabricatorApplicationMailReceiver',
'PasteDefaultEditCapability' => 'PhabricatorPolicyCapability',
'PasteDefaultViewCapability' => 'PhabricatorPolicyCapability',
'PasteEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PasteEmbedView' => 'AphrontView',
'PasteInfoConduitAPIMethod' => 'PasteConduitAPIMethod',
'PasteLanguageSelectDatasource' => 'PhabricatorTypeaheadDatasource',
'PasteMailReceiver' => 'PhabricatorObjectMailReceiver',
'PasteQueryConduitAPIMethod' => 'PasteConduitAPIMethod',
'PasteReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PasteSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PeopleBrowseUserDirectoryCapability' => 'PhabricatorPolicyCapability',
'PeopleCreateUsersCapability' => 'PhabricatorPolicyCapability',
'PeopleDisableUsersCapability' => 'PhabricatorPolicyCapability',
'PeopleHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'PeopleMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension',
'PeopleUserLogGarbageCollector' => 'PhabricatorGarbageCollector',
'Phabricator404Controller' => 'PhabricatorController',
'PhabricatorAWSConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorAWSSESFuture' => 'PhutilAWSFuture',
'PhabricatorAccessControlTestCase' => 'PhabricatorTestCase',
'PhabricatorAccessLog' => 'Phobject',
'PhabricatorAccessLogConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorAccessibilitySetting' => 'PhabricatorSelectSetting',
'PhabricatorAccumulateChartFunction' => 'PhabricatorHigherOrderChartFunction',
'PhabricatorActionListView' => 'AphrontTagView',
'PhabricatorActionView' => 'AphrontView',
'PhabricatorActivitySettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorAddEmailUserLogType' => 'PhabricatorUserLogType',
'PhabricatorAddMultifactorUserLogType' => 'PhabricatorUserLogType',
'PhabricatorAdministratorsPolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorAjaxRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorAlmanacApplication' => 'PhabricatorApplication',
'PhabricatorAmazonAuthProvider' => 'PhabricatorOAuth2AuthProvider',
'PhabricatorAmazonSNSFuture' => 'PhutilAWSFuture',
'PhabricatorAnchorTestCase' => 'PhabricatorTestCase',
'PhabricatorAnchorView' => 'AphrontView',
'PhabricatorAphlictManagementDebugWorkflow' => 'PhabricatorAphlictManagementWorkflow',
'PhabricatorAphlictManagementNotifyWorkflow' => 'PhabricatorAphlictManagementWorkflow',
'PhabricatorAphlictManagementRestartWorkflow' => 'PhabricatorAphlictManagementWorkflow',
'PhabricatorAphlictManagementStartWorkflow' => 'PhabricatorAphlictManagementWorkflow',
'PhabricatorAphlictManagementStatusWorkflow' => 'PhabricatorAphlictManagementWorkflow',
'PhabricatorAphlictManagementStopWorkflow' => 'PhabricatorAphlictManagementWorkflow',
'PhabricatorAphlictManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorAphlictSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorAphrontBarUIExample' => 'PhabricatorUIExample',
'PhabricatorAphrontViewTestCase' => 'PhabricatorTestCase',
'PhabricatorAppSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorApplication' => array(
'PhabricatorLiskDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
),
'PhabricatorApplicationApplicationPHIDType' => 'PhabricatorPHIDType',
'PhabricatorApplicationApplicationTransaction' => 'PhabricatorModularTransaction',
'PhabricatorApplicationApplicationTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorApplicationConfigOptions' => 'Phobject',
'PhabricatorApplicationConfigurationPanel' => 'Phobject',
'PhabricatorApplicationConfigurationPanelTestCase' => 'PhabricatorTestCase',
'PhabricatorApplicationDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorApplicationDetailViewController' => 'PhabricatorApplicationsController',
'PhabricatorApplicationEditController' => 'PhabricatorApplicationsController',
'PhabricatorApplicationEditEngine' => 'PhabricatorEditEngine',
'PhabricatorApplicationEditHTTPParameterHelpView' => 'AphrontView',
'PhabricatorApplicationEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorApplicationEmailCommandsController' => 'PhabricatorApplicationsController',
'PhabricatorApplicationMailReceiver' => 'PhabricatorMailReceiver',
'PhabricatorApplicationObjectMailEngineExtension' => 'PhabricatorMailEngineExtension',
'PhabricatorApplicationPanelController' => 'PhabricatorApplicationsController',
'PhabricatorApplicationPolicyChangeTransaction' => 'PhabricatorApplicationTransactionType',
'PhabricatorApplicationProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorApplicationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorApplicationSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorApplicationSearchController' => 'PhabricatorSearchBaseController',
'PhabricatorApplicationSearchEngine' => 'Phobject',
'PhabricatorApplicationSearchEngineTestCase' => 'PhabricatorTestCase',
'PhabricatorApplicationSearchResultView' => 'Phobject',
'PhabricatorApplicationTestCase' => 'PhabricatorTestCase',
'PhabricatorApplicationTransaction' => array(
'PhabricatorLiskDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorApplicationTransactionComment' => array(
'PhabricatorLiskDAO',
'PhabricatorMarkupInterface',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorApplicationTransactionCommentEditController' => 'PhabricatorApplicationTransactionController',
'PhabricatorApplicationTransactionCommentEditor' => 'PhabricatorEditor',
'PhabricatorApplicationTransactionCommentHistoryController' => 'PhabricatorApplicationTransactionController',
'PhabricatorApplicationTransactionCommentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorApplicationTransactionCommentQuoteController' => 'PhabricatorApplicationTransactionController',
'PhabricatorApplicationTransactionCommentRawController' => 'PhabricatorApplicationTransactionController',
'PhabricatorApplicationTransactionCommentRemoveController' => 'PhabricatorApplicationTransactionController',
'PhabricatorApplicationTransactionCommentView' => 'AphrontView',
'PhabricatorApplicationTransactionController' => 'PhabricatorController',
'PhabricatorApplicationTransactionDetailController' => 'PhabricatorApplicationTransactionController',
'PhabricatorApplicationTransactionDetailView' => 'AphrontView',
'PhabricatorApplicationTransactionEditor' => 'PhabricatorEditor',
'PhabricatorApplicationTransactionFeedStory' => 'PhabricatorFeedStory',
'PhabricatorApplicationTransactionJSONDiffDetailView' => 'PhabricatorApplicationTransactionDetailView',
'PhabricatorApplicationTransactionNoEffectException' => 'Exception',
'PhabricatorApplicationTransactionNoEffectResponse' => 'AphrontProxyResponse',
'PhabricatorApplicationTransactionPublishWorker' => 'PhabricatorWorker',
'PhabricatorApplicationTransactionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorApplicationTransactionRemarkupPreviewController' => 'PhabricatorApplicationTransactionController',
'PhabricatorApplicationTransactionReplyHandler' => 'PhabricatorMailReplyHandler',
'PhabricatorApplicationTransactionResponse' => 'AphrontProxyResponse',
'PhabricatorApplicationTransactionShowOlderController' => 'PhabricatorApplicationTransactionController',
'PhabricatorApplicationTransactionStructureException' => 'Exception',
'PhabricatorApplicationTransactionTemplatedCommentQuery' => 'PhabricatorApplicationTransactionCommentQuery',
'PhabricatorApplicationTransactionTextDiffDetailView' => 'PhabricatorApplicationTransactionDetailView',
'PhabricatorApplicationTransactionTransactionPHIDType' => 'PhabricatorPHIDType',
'PhabricatorApplicationTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorApplicationTransactionValidationError' => 'Phobject',
'PhabricatorApplicationTransactionValidationException' => 'Exception',
'PhabricatorApplicationTransactionValidationResponse' => 'AphrontProxyResponse',
'PhabricatorApplicationTransactionValueController' => 'PhabricatorApplicationTransactionController',
'PhabricatorApplicationTransactionView' => 'AphrontView',
'PhabricatorApplicationTransactionWarningException' => 'Exception',
'PhabricatorApplicationTransactionWarningResponse' => 'AphrontProxyResponse',
'PhabricatorApplicationUninstallController' => 'PhabricatorApplicationsController',
'PhabricatorApplicationUninstallTransaction' => 'PhabricatorApplicationTransactionType',
'PhabricatorApplicationsApplication' => 'PhabricatorApplication',
'PhabricatorApplicationsController' => 'PhabricatorController',
'PhabricatorApplicationsListController' => 'PhabricatorApplicationsController',
'PhabricatorApplyEditField' => 'PhabricatorEditField',
'PhabricatorAsanaAuthProvider' => array(
'PhabricatorOAuth2AuthProvider',
'DoorkeeperRemarkupURIInterface',
),
'PhabricatorAsanaConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorAsanaSubtaskHasObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorAsanaTaskHasObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorAudioDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorAuditActionConstants' => 'Phobject',
'PhabricatorAuditApplication' => 'PhabricatorApplication',
'PhabricatorAuditCommentEditor' => 'PhabricatorEditor',
'PhabricatorAuditController' => 'PhabricatorController',
'PhabricatorAuditEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorAuditInlineComment' => 'PhabricatorInlineComment',
'PhabricatorAuditMailReceiver' => 'PhabricatorObjectMailReceiver',
'PhabricatorAuditManagementDeleteWorkflow' => 'PhabricatorAuditManagementWorkflow',
'PhabricatorAuditManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorAuditReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PhabricatorAuditRequestStatus' => 'Phobject',
'PhabricatorAuditSynchronizeManagementWorkflow' => 'PhabricatorAuditManagementWorkflow',
'PhabricatorAuditTransaction' => 'PhabricatorModularTransaction',
'PhabricatorAuditTransactionComment' => array(
'PhabricatorApplicationTransactionComment',
'PhabricatorInlineCommentInterface',
),
'PhabricatorAuditTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorAuditTransactionView' => 'PhabricatorApplicationTransactionView',
'PhabricatorAuditUpdateOwnersManagementWorkflow' => 'PhabricatorAuditManagementWorkflow',
'PhabricatorAuthAccountView' => 'AphrontView',
'PhabricatorAuthApplication' => 'PhabricatorApplication',
'PhabricatorAuthAuthFactorPHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthAuthFactorProviderPHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthAuthProviderPHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthCSRFEngine' => 'Phobject',
'PhabricatorAuthChallenge' => array(
'PhabricatorAuthDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorAuthChallengeGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorAuthChallengePHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthChallengeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthChallengeStatusController' => 'PhabricatorAuthController',
'PhabricatorAuthChallengeUpdate' => 'Phobject',
'PhabricatorAuthChangePasswordAction' => 'PhabricatorSystemAction',
'PhabricatorAuthChangeUsernameMessageType' => 'PhabricatorAuthMessageType',
'PhabricatorAuthConduitAPIMethod' => 'ConduitAPIMethod',
'PhabricatorAuthConduitTokenRevoker' => 'PhabricatorAuthRevoker',
'PhabricatorAuthConfirmLinkController' => 'PhabricatorAuthController',
'PhabricatorAuthContactNumber' => array(
'PhabricatorAuthDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorEditEngineMFAInterface',
),
'PhabricatorAuthContactNumberController' => 'PhabricatorAuthController',
'PhabricatorAuthContactNumberDisableController' => 'PhabricatorAuthContactNumberController',
'PhabricatorAuthContactNumberEditController' => 'PhabricatorAuthContactNumberController',
'PhabricatorAuthContactNumberEditEngine' => 'PhabricatorEditEngine',
'PhabricatorAuthContactNumberEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorAuthContactNumberMFAEngine' => 'PhabricatorEditEngineMFAEngine',
'PhabricatorAuthContactNumberNumberTransaction' => 'PhabricatorAuthContactNumberTransactionType',
'PhabricatorAuthContactNumberPHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthContactNumberPrimaryController' => 'PhabricatorAuthContactNumberController',
'PhabricatorAuthContactNumberPrimaryTransaction' => 'PhabricatorAuthContactNumberTransactionType',
'PhabricatorAuthContactNumberQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthContactNumberStatusTransaction' => 'PhabricatorAuthContactNumberTransactionType',
'PhabricatorAuthContactNumberTestController' => 'PhabricatorAuthContactNumberController',
'PhabricatorAuthContactNumberTransaction' => 'PhabricatorModularTransaction',
'PhabricatorAuthContactNumberTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorAuthContactNumberTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorAuthContactNumberViewController' => 'PhabricatorAuthContactNumberController',
'PhabricatorAuthController' => 'PhabricatorController',
'PhabricatorAuthDAO' => 'PhabricatorLiskDAO',
'PhabricatorAuthDisableController' => 'PhabricatorAuthProviderConfigController',
'PhabricatorAuthDowngradeSessionController' => 'PhabricatorAuthController',
'PhabricatorAuthEditController' => 'PhabricatorAuthProviderConfigController',
'PhabricatorAuthEmailLoginAction' => 'PhabricatorSystemAction',
'PhabricatorAuthEmailLoginMessageType' => 'PhabricatorAuthMessageType',
'PhabricatorAuthEmailSetPasswordMessageType' => 'PhabricatorAuthMessageType',
'PhabricatorAuthFactor' => 'Phobject',
'PhabricatorAuthFactorConfig' => array(
'PhabricatorAuthDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorAuthFactorConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthFactorProvider' => array(
'PhabricatorAuthDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
'PhabricatorEditEngineMFAInterface',
),
'PhabricatorAuthFactorProviderController' => 'PhabricatorAuthProviderController',
'PhabricatorAuthFactorProviderDuoCredentialTransaction' => 'PhabricatorAuthFactorProviderTransactionType',
'PhabricatorAuthFactorProviderDuoEnrollTransaction' => 'PhabricatorAuthFactorProviderTransactionType',
'PhabricatorAuthFactorProviderDuoHostnameTransaction' => 'PhabricatorAuthFactorProviderTransactionType',
'PhabricatorAuthFactorProviderDuoUsernamesTransaction' => 'PhabricatorAuthFactorProviderTransactionType',
'PhabricatorAuthFactorProviderEditController' => 'PhabricatorAuthFactorProviderController',
'PhabricatorAuthFactorProviderEditEngine' => 'PhabricatorEditEngine',
'PhabricatorAuthFactorProviderEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorAuthFactorProviderEnrollMessageTransaction' => 'PhabricatorAuthFactorProviderTransactionType',
'PhabricatorAuthFactorProviderListController' => 'PhabricatorAuthProviderController',
'PhabricatorAuthFactorProviderMFAEngine' => 'PhabricatorEditEngineMFAEngine',
'PhabricatorAuthFactorProviderMessageController' => 'PhabricatorAuthFactorProviderController',
'PhabricatorAuthFactorProviderNameTransaction' => 'PhabricatorAuthFactorProviderTransactionType',
'PhabricatorAuthFactorProviderQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthFactorProviderStatus' => 'Phobject',
'PhabricatorAuthFactorProviderStatusTransaction' => 'PhabricatorAuthFactorProviderTransactionType',
'PhabricatorAuthFactorProviderTransaction' => 'PhabricatorModularTransaction',
'PhabricatorAuthFactorProviderTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorAuthFactorProviderTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorAuthFactorProviderViewController' => 'PhabricatorAuthFactorProviderController',
'PhabricatorAuthFactorResult' => 'Phobject',
'PhabricatorAuthFactorTestCase' => 'PhabricatorTestCase',
'PhabricatorAuthFinishController' => 'PhabricatorAuthController',
'PhabricatorAuthHMACKey' => 'PhabricatorAuthDAO',
'PhabricatorAuthHighSecurityRequiredException' => 'Exception',
'PhabricatorAuthHighSecurityToken' => 'Phobject',
'PhabricatorAuthInvite' => array(
'PhabricatorUserDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorAuthInviteAccountException' => 'PhabricatorAuthInviteDialogException',
'PhabricatorAuthInviteAction' => 'Phobject',
'PhabricatorAuthInviteActionTableView' => 'AphrontView',
'PhabricatorAuthInviteController' => 'PhabricatorAuthController',
'PhabricatorAuthInviteDialogException' => 'PhabricatorAuthInviteException',
'PhabricatorAuthInviteEngine' => 'Phobject',
'PhabricatorAuthInviteException' => 'Exception',
'PhabricatorAuthInviteInvalidException' => 'PhabricatorAuthInviteDialogException',
'PhabricatorAuthInviteLoginException' => 'PhabricatorAuthInviteDialogException',
'PhabricatorAuthInvitePHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthInviteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthInviteRegisteredException' => 'PhabricatorAuthInviteException',
'PhabricatorAuthInviteSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorAuthInviteTestCase' => 'PhabricatorTestCase',
'PhabricatorAuthInviteVerifyException' => 'PhabricatorAuthInviteDialogException',
'PhabricatorAuthInviteWorker' => 'PhabricatorWorker',
'PhabricatorAuthLinkController' => 'PhabricatorAuthController',
'PhabricatorAuthLinkMessageType' => 'PhabricatorAuthMessageType',
'PhabricatorAuthListController' => 'PhabricatorAuthProviderConfigController',
'PhabricatorAuthLoginController' => 'PhabricatorAuthController',
'PhabricatorAuthLoginMessageType' => 'PhabricatorAuthMessageType',
'PhabricatorAuthLogoutConduitAPIMethod' => 'PhabricatorAuthConduitAPIMethod',
'PhabricatorAuthMFAEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorAuthMFASyncTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType',
'PhabricatorAuthMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension',
'PhabricatorAuthManagementCachePKCS8Workflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementLDAPWorkflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementListFactorsWorkflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementListMFAProvidersWorkflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementLockWorkflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementRecoverWorkflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementRefreshWorkflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementRevokeWorkflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementStripWorkflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementTrustOAuthClientWorkflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementUnlimitWorkflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementUnlockWorkflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementUntrustOAuthClientWorkflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementVerifyWorkflow' => 'PhabricatorAuthManagementWorkflow',
'PhabricatorAuthManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorAuthMessage' => array(
'PhabricatorAuthDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorAuthMessageController' => 'PhabricatorAuthProviderController',
'PhabricatorAuthMessageEditController' => 'PhabricatorAuthMessageController',
'PhabricatorAuthMessageEditEngine' => 'PhabricatorEditEngine',
'PhabricatorAuthMessageEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorAuthMessageListController' => 'PhabricatorAuthProviderController',
'PhabricatorAuthMessagePHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthMessageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthMessageTextTransaction' => 'PhabricatorAuthMessageTransactionType',
'PhabricatorAuthMessageTransaction' => 'PhabricatorModularTransaction',
'PhabricatorAuthMessageTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorAuthMessageTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorAuthMessageType' => 'Phobject',
'PhabricatorAuthMessageViewController' => 'PhabricatorAuthMessageController',
'PhabricatorAuthNeedsApprovalController' => 'PhabricatorAuthController',
'PhabricatorAuthNeedsMultiFactorController' => 'PhabricatorAuthController',
'PhabricatorAuthNewController' => 'PhabricatorAuthProviderConfigController',
'PhabricatorAuthNewFactorAction' => 'PhabricatorSystemAction',
'PhabricatorAuthOldOAuthRedirectController' => 'PhabricatorAuthController',
'PhabricatorAuthOneTimeLoginController' => 'PhabricatorAuthController',
'PhabricatorAuthOneTimeLoginTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType',
'PhabricatorAuthPassword' => array(
'PhabricatorAuthDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorApplicationTransactionInterface',
),
'PhabricatorAuthPasswordEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorAuthPasswordEngine' => 'Phobject',
'PhabricatorAuthPasswordException' => 'Exception',
'PhabricatorAuthPasswordPHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthPasswordQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthPasswordResetTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType',
'PhabricatorAuthPasswordRevokeTransaction' => 'PhabricatorAuthPasswordTransactionType',
'PhabricatorAuthPasswordRevoker' => 'PhabricatorAuthRevoker',
'PhabricatorAuthPasswordTestCase' => 'PhabricatorTestCase',
'PhabricatorAuthPasswordTransaction' => 'PhabricatorModularTransaction',
'PhabricatorAuthPasswordTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorAuthPasswordTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorAuthPasswordUpgradeTransaction' => 'PhabricatorAuthPasswordTransactionType',
'PhabricatorAuthProvider' => 'Phobject',
'PhabricatorAuthProviderConfig' => array(
'PhabricatorAuthDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorAuthProviderConfigController' => 'PhabricatorAuthProviderController',
'PhabricatorAuthProviderConfigEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorAuthProviderConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthProviderConfigTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorAuthProviderConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorAuthProviderController' => 'PhabricatorAuthController',
'PhabricatorAuthProviderViewController' => 'PhabricatorAuthProviderConfigController',
'PhabricatorAuthProvidersGuidanceContext' => 'PhabricatorGuidanceContext',
'PhabricatorAuthProvidersGuidanceEngineExtension' => 'PhabricatorGuidanceEngineExtension',
'PhabricatorAuthQueryPublicKeysConduitAPIMethod' => 'PhabricatorAuthConduitAPIMethod',
'PhabricatorAuthRegisterController' => 'PhabricatorAuthController',
'PhabricatorAuthRevokeTokenController' => 'PhabricatorAuthController',
'PhabricatorAuthRevoker' => 'Phobject',
'PhabricatorAuthSSHKey' => array(
'PhabricatorAuthDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorApplicationTransactionInterface',
),
'PhabricatorAuthSSHKeyController' => 'PhabricatorAuthController',
'PhabricatorAuthSSHKeyEditController' => 'PhabricatorAuthSSHKeyController',
'PhabricatorAuthSSHKeyEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorAuthSSHKeyGenerateController' => 'PhabricatorAuthSSHKeyController',
'PhabricatorAuthSSHKeyListController' => 'PhabricatorAuthSSHKeyController',
'PhabricatorAuthSSHKeyPHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthSSHKeyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthSSHKeyReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PhabricatorAuthSSHKeyRevokeController' => 'PhabricatorAuthSSHKeyController',
'PhabricatorAuthSSHKeySearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorAuthSSHKeyTableView' => 'AphrontView',
'PhabricatorAuthSSHKeyTestCase' => 'PhabricatorTestCase',
'PhabricatorAuthSSHKeyTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorAuthSSHKeyTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorAuthSSHKeyViewController' => 'PhabricatorAuthSSHKeyController',
'PhabricatorAuthSSHPrivateKey' => 'Phobject',
'PhabricatorAuthSSHPrivateKeyException' => 'Exception',
'PhabricatorAuthSSHPrivateKeyFormatException' => 'PhabricatorAuthSSHPrivateKeyException',
'PhabricatorAuthSSHPrivateKeyIncorrectPassphraseException' => 'PhabricatorAuthSSHPrivateKeyPassphraseException',
'PhabricatorAuthSSHPrivateKeyMissingPassphraseException' => 'PhabricatorAuthSSHPrivateKeyPassphraseException',
'PhabricatorAuthSSHPrivateKeyPassphraseException' => 'PhabricatorAuthSSHPrivateKeyException',
'PhabricatorAuthSSHPrivateKeySurplusPassphraseException' => 'PhabricatorAuthSSHPrivateKeyPassphraseException',
'PhabricatorAuthSSHPrivateKeyUnknownException' => 'PhabricatorAuthSSHPrivateKeyException',
'PhabricatorAuthSSHPublicKey' => 'Phobject',
'PhabricatorAuthSSHRevoker' => 'PhabricatorAuthRevoker',
'PhabricatorAuthSession' => array(
'PhabricatorAuthDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorAuthSessionEngine' => 'Phobject',
'PhabricatorAuthSessionEngineExtension' => 'Phobject',
'PhabricatorAuthSessionEngineExtensionModule' => 'PhabricatorConfigModule',
'PhabricatorAuthSessionGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorAuthSessionInfo' => 'Phobject',
'PhabricatorAuthSessionPHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthSessionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthSessionRevoker' => 'PhabricatorAuthRevoker',
'PhabricatorAuthSetExternalController' => 'PhabricatorAuthController',
'PhabricatorAuthSetPasswordController' => 'PhabricatorAuthController',
'PhabricatorAuthSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorAuthStartController' => 'PhabricatorAuthController',
'PhabricatorAuthTemporaryToken' => array(
'PhabricatorAuthDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorAuthTemporaryTokenGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorAuthTemporaryTokenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthTemporaryTokenRevoker' => 'PhabricatorAuthRevoker',
'PhabricatorAuthTemporaryTokenType' => 'Phobject',
'PhabricatorAuthTemporaryTokenTypeModule' => 'PhabricatorConfigModule',
'PhabricatorAuthTerminateSessionController' => 'PhabricatorAuthController',
'PhabricatorAuthTestSMSAction' => 'PhabricatorSystemAction',
'PhabricatorAuthTryEmailLoginAction' => 'PhabricatorSystemAction',
'PhabricatorAuthTryFactorAction' => 'PhabricatorSystemAction',
'PhabricatorAuthTryPasswordAction' => 'PhabricatorSystemAction',
'PhabricatorAuthTryPasswordWithoutCAPTCHAAction' => 'PhabricatorSystemAction',
'PhabricatorAuthUnlinkController' => 'PhabricatorAuthController',
'PhabricatorAuthValidateController' => 'PhabricatorAuthController',
'PhabricatorAuthWaitForApprovalMessageType' => 'PhabricatorAuthMessageType',
'PhabricatorAuthWelcomeMailMessageType' => 'PhabricatorAuthMessageType',
'PhabricatorAuthenticationConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorAutoEventListener' => 'PhabricatorEventListener',
'PhabricatorBadgesApplication' => 'PhabricatorApplication',
'PhabricatorBadgesArchiveController' => 'PhabricatorBadgesController',
'PhabricatorBadgesAward' => array(
'PhabricatorBadgesDAO',
'PhabricatorDestructibleInterface',
'PhabricatorPolicyInterface',
),
'PhabricatorBadgesAwardController' => 'PhabricatorBadgesController',
'PhabricatorBadgesAwardQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorBadgesAwardTestDataGenerator' => 'PhabricatorTestDataGenerator',
'PhabricatorBadgesBadge' => array(
'PhabricatorBadgesDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorSubscribableInterface',
'PhabricatorFlaggableInterface',
'PhabricatorDestructibleInterface',
'PhabricatorConduitResultInterface',
'PhabricatorNgramsInterface',
),
'PhabricatorBadgesBadgeAwardTransaction' => 'PhabricatorBadgesBadgeTransactionType',
'PhabricatorBadgesBadgeDescriptionTransaction' => 'PhabricatorBadgesBadgeTransactionType',
'PhabricatorBadgesBadgeFlavorTransaction' => 'PhabricatorBadgesBadgeTransactionType',
'PhabricatorBadgesBadgeIconTransaction' => 'PhabricatorBadgesBadgeTransactionType',
'PhabricatorBadgesBadgeNameNgrams' => 'PhabricatorSearchNgrams',
'PhabricatorBadgesBadgeNameTransaction' => 'PhabricatorBadgesBadgeTransactionType',
'PhabricatorBadgesBadgeQualityTransaction' => 'PhabricatorBadgesBadgeTransactionType',
'PhabricatorBadgesBadgeRevokeTransaction' => 'PhabricatorBadgesBadgeTransactionType',
'PhabricatorBadgesBadgeStatusTransaction' => 'PhabricatorBadgesBadgeTransactionType',
'PhabricatorBadgesBadgeTestDataGenerator' => 'PhabricatorTestDataGenerator',
'PhabricatorBadgesBadgeTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorBadgesCommentController' => 'PhabricatorBadgesController',
'PhabricatorBadgesController' => 'PhabricatorController',
'PhabricatorBadgesCreateCapability' => 'PhabricatorPolicyCapability',
'PhabricatorBadgesDAO' => 'PhabricatorLiskDAO',
'PhabricatorBadgesDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorBadgesDefaultEditCapability' => 'PhabricatorPolicyCapability',
'PhabricatorBadgesEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PhabricatorBadgesEditController' => 'PhabricatorBadgesController',
'PhabricatorBadgesEditEngine' => 'PhabricatorEditEngine',
'PhabricatorBadgesEditRecipientsController' => 'PhabricatorBadgesController',
'PhabricatorBadgesEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorBadgesIconSet' => 'PhabricatorIconSet',
'PhabricatorBadgesListController' => 'PhabricatorBadgesController',
'PhabricatorBadgesLootContextFreeGrammar' => 'PhutilContextFreeGrammar',
'PhabricatorBadgesMailReceiver' => 'PhabricatorObjectMailReceiver',
'PhabricatorBadgesPHIDType' => 'PhabricatorPHIDType',
'PhabricatorBadgesProfileController' => 'PhabricatorController',
'PhabricatorBadgesQuality' => 'Phobject',
'PhabricatorBadgesQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorBadgesRecipientsController' => 'PhabricatorBadgesProfileController',
'PhabricatorBadgesRecipientsListView' => 'AphrontView',
'PhabricatorBadgesRemoveRecipientsController' => 'PhabricatorBadgesController',
'PhabricatorBadgesReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PhabricatorBadgesSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorBadgesSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhabricatorBadgesSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorBadgesTransaction' => 'PhabricatorModularTransaction',
'PhabricatorBadgesTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PhabricatorBadgesTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorBadgesViewController' => 'PhabricatorBadgesProfileController',
'PhabricatorBarePageView' => 'AphrontPageView',
'PhabricatorBaseURISetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorBcryptPasswordHasher' => 'PhabricatorPasswordHasher',
'PhabricatorBinariesSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorBitbucketAuthProvider' => 'PhabricatorOAuth1AuthProvider',
'PhabricatorBoardColumnsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorBoardLayoutEngine' => 'Phobject',
'PhabricatorBoardRenderingEngine' => 'Phobject',
'PhabricatorBoardResponseEngine' => 'Phobject',
'PhabricatorBoolConfigType' => 'PhabricatorTextConfigType',
'PhabricatorBoolEditField' => 'PhabricatorEditField',
'PhabricatorBoolMailStamp' => 'PhabricatorMailStamp',
'PhabricatorBritishEnglishTranslation' => 'PhutilTranslation',
'PhabricatorBuiltinDraftEngine' => 'PhabricatorDraftEngine',
'PhabricatorBuiltinFileCachePurger' => 'PhabricatorCachePurger',
'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList',
'PhabricatorBulkContentSource' => 'PhabricatorContentSource',
'PhabricatorBulkEditGroup' => 'Phobject',
'PhabricatorBulkEngine' => 'Phobject',
'PhabricatorBulkManagementExportWorkflow' => 'PhabricatorBulkManagementWorkflow',
'PhabricatorBulkManagementMakeSilentWorkflow' => 'PhabricatorBulkManagementWorkflow',
'PhabricatorBulkManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorCSVExportFormat' => 'PhabricatorExportFormat',
'PhabricatorCacheDAO' => 'PhabricatorLiskDAO',
'PhabricatorCacheEngine' => 'Phobject',
'PhabricatorCacheEngineExtension' => 'Phobject',
'PhabricatorCacheGeneralGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorCacheManagementPurgeWorkflow' => 'PhabricatorCacheManagementWorkflow',
'PhabricatorCacheManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorCacheMarkupGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorCachePurger' => 'Phobject',
'PhabricatorCacheSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorCacheSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorCacheSpec' => 'Phobject',
'PhabricatorCacheTTLGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorCachedClassMapQuery' => 'Phobject',
'PhabricatorCaches' => 'Phobject',
'PhabricatorCachesTestCase' => 'PhabricatorTestCase',
'PhabricatorCalendarApplication' => 'PhabricatorApplication',
'PhabricatorCalendarController' => 'PhabricatorController',
'PhabricatorCalendarDAO' => 'PhabricatorLiskDAO',
'PhabricatorCalendarEvent' => array(
'PhabricatorCalendarDAO',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
'PhabricatorPolicyCodexInterface',
'PhabricatorProjectInterface',
'PhabricatorMarkupInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorSubscribableInterface',
'PhabricatorTokenReceiverInterface',
'PhabricatorDestructibleInterface',
'PhabricatorMentionableInterface',
'PhabricatorFlaggableInterface',
'PhabricatorSpacesInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
'PhabricatorConduitResultInterface',
),
'PhabricatorCalendarEventAcceptTransaction' => 'PhabricatorCalendarEventReplyTransaction',
'PhabricatorCalendarEventAllDayTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventAvailabilityController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventCancelController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventCancelTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventDateTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventDeclineTransaction' => 'PhabricatorCalendarEventReplyTransaction',
'PhabricatorCalendarEventDefaultEditCapability' => 'PhabricatorPolicyCapability',
'PhabricatorCalendarEventDefaultViewCapability' => 'PhabricatorPolicyCapability',
'PhabricatorCalendarEventDescriptionTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventDragController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventEditEngine' => 'PhabricatorEditEngine',
'PhabricatorCalendarEventEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorCalendarEventEmailCommand' => 'MetaMTAEmailTransactionCommand',
'PhabricatorCalendarEventEndDateTransaction' => 'PhabricatorCalendarEventDateTransaction',
'PhabricatorCalendarEventExportController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventFerretEngine' => 'PhabricatorFerretEngine',
'PhabricatorCalendarEventForkTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventFrequencyTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventFulltextEngine' => 'PhabricatorFulltextEngine',
'PhabricatorCalendarEventHeraldAdapter' => 'HeraldAdapter',
'PhabricatorCalendarEventHeraldField' => 'HeraldField',
'PhabricatorCalendarEventHeraldFieldGroup' => 'HeraldFieldGroup',
'PhabricatorCalendarEventHostPolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorCalendarEventHostTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventIconTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventInviteTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventInvitee' => array(
'PhabricatorCalendarDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorCalendarEventInviteeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorCalendarEventInviteesPolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorCalendarEventJoinController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventListController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventMailReceiver' => 'PhabricatorObjectMailReceiver',
'PhabricatorCalendarEventNameHeraldField' => 'PhabricatorCalendarEventHeraldField',
'PhabricatorCalendarEventNameTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventNotificationView' => 'Phobject',
'PhabricatorCalendarEventPHIDType' => 'PhabricatorPHIDType',
'PhabricatorCalendarEventPolicyCodex' => 'PhabricatorPolicyCodex',
'PhabricatorCalendarEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorCalendarEventRSVPEmailCommand' => 'PhabricatorCalendarEventEmailCommand',
'PhabricatorCalendarEventRecurringTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventReplyTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhabricatorCalendarEventSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorCalendarEventStartDateTransaction' => 'PhabricatorCalendarEventDateTransaction',
'PhabricatorCalendarEventTransaction' => 'PhabricatorModularTransaction',
'PhabricatorCalendarEventTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PhabricatorCalendarEventTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorCalendarEventTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorCalendarEventUntilDateTransaction' => 'PhabricatorCalendarEventDateTransaction',
'PhabricatorCalendarEventViewController' => 'PhabricatorCalendarController',
'PhabricatorCalendarExport' => array(
'PhabricatorCalendarDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorCalendarExportDisableController' => 'PhabricatorCalendarController',
'PhabricatorCalendarExportDisableTransaction' => 'PhabricatorCalendarExportTransactionType',
'PhabricatorCalendarExportEditController' => 'PhabricatorCalendarController',
'PhabricatorCalendarExportEditEngine' => 'PhabricatorEditEngine',
'PhabricatorCalendarExportEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorCalendarExportICSController' => 'PhabricatorCalendarController',
'PhabricatorCalendarExportListController' => 'PhabricatorCalendarController',
'PhabricatorCalendarExportModeTransaction' => 'PhabricatorCalendarExportTransactionType',
'PhabricatorCalendarExportNameTransaction' => 'PhabricatorCalendarExportTransactionType',
'PhabricatorCalendarExportPHIDType' => 'PhabricatorPHIDType',
'PhabricatorCalendarExportQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorCalendarExportQueryKeyTransaction' => 'PhabricatorCalendarExportTransactionType',
'PhabricatorCalendarExportSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorCalendarExportTransaction' => 'PhabricatorModularTransaction',
'PhabricatorCalendarExportTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorCalendarExportTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorCalendarExportViewController' => 'PhabricatorCalendarController',
'PhabricatorCalendarExternalInvitee' => array(
'PhabricatorCalendarDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorCalendarExternalInviteePHIDType' => 'PhabricatorPHIDType',
'PhabricatorCalendarExternalInviteeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorCalendarICSFileImportEngine' => 'PhabricatorCalendarICSImportEngine',
'PhabricatorCalendarICSImportEngine' => 'PhabricatorCalendarImportEngine',
'PhabricatorCalendarICSURIImportEngine' => 'PhabricatorCalendarICSImportEngine',
'PhabricatorCalendarICSWriter' => 'Phobject',
'PhabricatorCalendarIconSet' => 'PhabricatorIconSet',
'PhabricatorCalendarImport' => array(
'PhabricatorCalendarDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorCalendarImportDefaultLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportDeleteController' => 'PhabricatorCalendarController',
'PhabricatorCalendarImportDeleteLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportDeleteTransaction' => 'PhabricatorCalendarImportTransactionType',
'PhabricatorCalendarImportDisableController' => 'PhabricatorCalendarController',
'PhabricatorCalendarImportDisableTransaction' => 'PhabricatorCalendarImportTransactionType',
'PhabricatorCalendarImportDropController' => 'PhabricatorCalendarController',
'PhabricatorCalendarImportDuplicateLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportEditController' => 'PhabricatorCalendarController',
'PhabricatorCalendarImportEditEngine' => 'PhabricatorEditEngine',
'PhabricatorCalendarImportEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorCalendarImportEmptyLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportEngine' => 'Phobject',
'PhabricatorCalendarImportEpochLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportFetchLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportFrequencyLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportFrequencyTransaction' => 'PhabricatorCalendarImportTransactionType',
'PhabricatorCalendarImportICSFileTransaction' => 'PhabricatorCalendarImportTransactionType',
'PhabricatorCalendarImportICSLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportICSURITransaction' => 'PhabricatorCalendarImportTransactionType',
'PhabricatorCalendarImportICSWarningLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportIgnoredNodeLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportListController' => 'PhabricatorCalendarController',
'PhabricatorCalendarImportLog' => array(
'PhabricatorCalendarDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorCalendarImportLogListController' => 'PhabricatorCalendarController',
'PhabricatorCalendarImportLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorCalendarImportLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorCalendarImportLogType' => 'Phobject',
'PhabricatorCalendarImportLogView' => 'AphrontView',
'PhabricatorCalendarImportNameTransaction' => 'PhabricatorCalendarImportTransactionType',
'PhabricatorCalendarImportOriginalLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportOrphanLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportPHIDType' => 'PhabricatorPHIDType',
'PhabricatorCalendarImportQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorCalendarImportQueueLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportReloadController' => 'PhabricatorCalendarController',
'PhabricatorCalendarImportReloadTransaction' => 'PhabricatorCalendarImportTransactionType',
'PhabricatorCalendarImportReloadWorker' => 'PhabricatorWorker',
'PhabricatorCalendarImportSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorCalendarImportTransaction' => 'PhabricatorModularTransaction',
'PhabricatorCalendarImportTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorCalendarImportTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorCalendarImportTriggerLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportUpdateLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportViewController' => 'PhabricatorCalendarController',
'PhabricatorCalendarInviteeDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorCalendarInviteeUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorCalendarInviteeViewerFunctionDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorCalendarManagementNotifyWorkflow' => 'PhabricatorCalendarManagementWorkflow',
'PhabricatorCalendarManagementReloadWorkflow' => 'PhabricatorCalendarManagementWorkflow',
'PhabricatorCalendarManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorCalendarNotification' => 'PhabricatorCalendarDAO',
'PhabricatorCalendarNotificationEngine' => 'Phobject',
'PhabricatorCalendarRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'PhabricatorCalendarReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PhabricatorCalendarSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorCelerityApplication' => 'PhabricatorApplication',
'PhabricatorCelerityTestCase' => 'PhabricatorTestCase',
'PhabricatorChangeParserTestCase' => 'PhabricatorWorkingCopyTestCase',
'PhabricatorChangePasswordUserLogType' => 'PhabricatorUserLogType',
'PhabricatorChangesetCachePurger' => 'PhabricatorCachePurger',
'PhabricatorChangesetResponse' => 'AphrontProxyResponse',
'PhabricatorChangesetViewState' => 'Phobject',
'PhabricatorChangesetViewStateEngine' => 'Phobject',
'PhabricatorChartAxis' => 'Phobject',
'PhabricatorChartDataQuery' => 'Phobject',
'PhabricatorChartDataset' => 'Phobject',
'PhabricatorChartDisplayData' => 'Phobject',
'PhabricatorChartEngine' => 'Phobject',
'PhabricatorChartFunction' => 'Phobject',
'PhabricatorChartFunctionArgument' => 'Phobject',
'PhabricatorChartFunctionArgumentParser' => 'Phobject',
'PhabricatorChartFunctionLabel' => 'Phobject',
'PhabricatorChartInterval' => 'Phobject',
'PhabricatorChartRenderingEngine' => 'Phobject',
'PhabricatorChartStackedAreaDataset' => 'PhabricatorChartDataset',
'PhabricatorChatLogApplication' => 'PhabricatorApplication',
'PhabricatorChatLogChannel' => array(
'PhabricatorChatLogDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorChatLogChannelListController' => 'PhabricatorChatLogController',
'PhabricatorChatLogChannelLogController' => 'PhabricatorChatLogController',
'PhabricatorChatLogChannelQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorChatLogController' => 'PhabricatorController',
'PhabricatorChatLogDAO' => 'PhabricatorLiskDAO',
'PhabricatorChatLogEvent' => array(
'PhabricatorChatLogDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorChatLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorCheckboxesEditField' => 'PhabricatorEditField',
'PhabricatorChunkedFileStorageEngine' => 'PhabricatorFileStorageEngine',
'PhabricatorClassConfigType' => 'PhabricatorTextConfigType',
'PhabricatorClusterConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorClusterDatabasesConfigType' => 'PhabricatorJSONConfigType',
'PhabricatorClusterException' => 'Exception',
'PhabricatorClusterExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorClusterImpossibleWriteException' => 'PhabricatorClusterException',
'PhabricatorClusterImproperWriteException' => 'PhabricatorClusterException',
'PhabricatorClusterMailersConfigType' => 'PhabricatorJSONConfigType',
'PhabricatorClusterNoHostForRoleException' => 'Exception',
'PhabricatorClusterSearchConfigType' => 'PhabricatorJSONConfigType',
'PhabricatorClusterServiceHealthRecord' => 'Phobject',
'PhabricatorClusterStrandedException' => 'PhabricatorClusterException',
'PhabricatorColumnsEditField' => 'PhabricatorPHIDListEditField',
'PhabricatorCommentEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorCommentEditField' => 'PhabricatorEditField',
'PhabricatorCommentEditType' => 'PhabricatorEditType',
'PhabricatorCommitBranchesField' => 'PhabricatorCommitCustomField',
'PhabricatorCommitCustomField' => 'PhabricatorCustomField',
'PhabricatorCommitMergedCommitsField' => 'PhabricatorCommitCustomField',
'PhabricatorCommitRepositoryField' => 'PhabricatorCommitCustomField',
'PhabricatorCommitSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorCommitTagsField' => 'PhabricatorCommitCustomField',
'PhabricatorCommonPasswords' => 'Phobject',
'PhabricatorComposeChartFunction' => 'PhabricatorHigherOrderChartFunction',
'PhabricatorConduitAPIController' => 'PhabricatorConduitController',
'PhabricatorConduitApplication' => 'PhabricatorApplication',
'PhabricatorConduitCallManagementWorkflow' => 'PhabricatorConduitManagementWorkflow',
'PhabricatorConduitCertificateFailureUserLogType' => 'PhabricatorUserLogType',
'PhabricatorConduitCertificateToken' => 'PhabricatorConduitDAO',
'PhabricatorConduitCertificateUserLogType' => 'PhabricatorUserLogType',
'PhabricatorConduitConsoleController' => 'PhabricatorConduitController',
'PhabricatorConduitContentSource' => 'PhabricatorContentSource',
'PhabricatorConduitController' => 'PhabricatorController',
'PhabricatorConduitDAO' => 'PhabricatorLiskDAO',
'PhabricatorConduitEditField' => 'PhabricatorEditField',
'PhabricatorConduitListController' => 'PhabricatorConduitController',
'PhabricatorConduitLogController' => 'PhabricatorConduitController',
'PhabricatorConduitLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorConduitLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorConduitManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorConduitMethodCallLog' => array(
'PhabricatorConduitDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorConduitMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorConduitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorConduitResultInterface' => 'PhabricatorPHIDInterface',
'PhabricatorConduitSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorConduitSearchFieldSpecification' => 'Phobject',
'PhabricatorConduitTestCase' => 'PhabricatorTestCase',
'PhabricatorConduitToken' => array(
'PhabricatorConduitDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorConduitTokenController' => 'PhabricatorConduitController',
'PhabricatorConduitTokenEditController' => 'PhabricatorConduitController',
'PhabricatorConduitTokenHandshakeController' => 'PhabricatorConduitController',
'PhabricatorConduitTokenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorConduitTokenTerminateController' => 'PhabricatorConduitController',
'PhabricatorConduitTokensSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorConfigApplication' => 'PhabricatorApplication',
'PhabricatorConfigCacheController' => 'PhabricatorConfigServicesController',
'PhabricatorConfigClusterDatabasesController' => 'PhabricatorConfigServicesController',
'PhabricatorConfigClusterNotificationsController' => 'PhabricatorConfigServicesController',
'PhabricatorConfigClusterRepositoriesController' => 'PhabricatorConfigServicesController',
'PhabricatorConfigClusterSearchController' => 'PhabricatorConfigServicesController',
'PhabricatorConfigCollectorsModule' => 'PhabricatorConfigModule',
'PhabricatorConfigColumnSchema' => 'PhabricatorConfigStorageSchema',
'PhabricatorConfigConfigPHIDType' => 'PhabricatorPHIDType',
'PhabricatorConfigConsoleController' => 'PhabricatorConfigController',
'PhabricatorConfigConstants' => 'Phobject',
'PhabricatorConfigController' => 'PhabricatorController',
'PhabricatorConfigCoreSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorConfigDatabaseController' => 'PhabricatorConfigServicesController',
'PhabricatorConfigDatabaseIssueController' => 'PhabricatorConfigDatabaseController',
'PhabricatorConfigDatabaseSchema' => 'PhabricatorConfigStorageSchema',
'PhabricatorConfigDatabaseSource' => 'PhabricatorConfigProxySource',
'PhabricatorConfigDatabaseStatusController' => 'PhabricatorConfigDatabaseController',
'PhabricatorConfigDefaultSource' => 'PhabricatorConfigProxySource',
'PhabricatorConfigDictionarySource' => 'PhabricatorConfigSource',
'PhabricatorConfigEdgeModule' => 'PhabricatorConfigModule',
'PhabricatorConfigEditController' => 'PhabricatorConfigSettingsController',
'PhabricatorConfigEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorConfigEntry' => array(
'PhabricatorConfigEntryDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
),
'PhabricatorConfigEntryDAO' => 'PhabricatorLiskDAO',
'PhabricatorConfigEntryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorConfigFileSource' => 'PhabricatorConfigProxySource',
'PhabricatorConfigGroupConstants' => 'PhabricatorConfigConstants',
'PhabricatorConfigHTTPParameterTypesModule' => 'PhabricatorConfigModule',
'PhabricatorConfigIgnoreController' => 'PhabricatorConfigController',
'PhabricatorConfigIssueListController' => 'PhabricatorConfigController',
'PhabricatorConfigIssuePanelController' => 'PhabricatorConfigController',
'PhabricatorConfigIssueViewController' => 'PhabricatorConfigController',
'PhabricatorConfigJSON' => 'Phobject',
'PhabricatorConfigJSONOptionType' => 'PhabricatorConfigOptionType',
'PhabricatorConfigKeySchema' => 'PhabricatorConfigStorageSchema',
'PhabricatorConfigLocalSource' => 'PhabricatorConfigProxySource',
'PhabricatorConfigManagementDeleteWorkflow' => 'PhabricatorConfigManagementWorkflow',
'PhabricatorConfigManagementDoneWorkflow' => 'PhabricatorConfigManagementWorkflow',
'PhabricatorConfigManagementGetWorkflow' => 'PhabricatorConfigManagementWorkflow',
'PhabricatorConfigManagementListWorkflow' => 'PhabricatorConfigManagementWorkflow',
'PhabricatorConfigManagementMigrateWorkflow' => 'PhabricatorConfigManagementWorkflow',
'PhabricatorConfigManagementSetWorkflow' => 'PhabricatorConfigManagementWorkflow',
'PhabricatorConfigManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorConfigManualActivity' => 'PhabricatorConfigEntryDAO',
'PhabricatorConfigModule' => 'Phobject',
'PhabricatorConfigModuleController' => 'PhabricatorConfigController',
'PhabricatorConfigOption' => 'Phobject',
'PhabricatorConfigOptionType' => 'Phobject',
'PhabricatorConfigPHIDModule' => 'PhabricatorConfigModule',
'PhabricatorConfigProxySource' => 'PhabricatorConfigSource',
'PhabricatorConfigPurgeCacheController' => 'PhabricatorConfigController',
'PhabricatorConfigRegexOptionType' => 'PhabricatorConfigJSONOptionType',
'PhabricatorConfigRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorConfigRequestExceptionHandlerModule' => 'PhabricatorConfigModule',
'PhabricatorConfigResponse' => 'AphrontStandaloneHTMLResponse',
'PhabricatorConfigSchemaQuery' => 'Phobject',
'PhabricatorConfigSchemaSpec' => 'Phobject',
'PhabricatorConfigServerSchema' => 'PhabricatorConfigStorageSchema',
'PhabricatorConfigServicesController' => 'PhabricatorConfigController',
'PhabricatorConfigSettingsController' => 'PhabricatorConfigController',
'PhabricatorConfigSettingsHistoryController' => 'PhabricatorConfigSettingsController',
'PhabricatorConfigSettingsListController' => 'PhabricatorConfigSettingsController',
'PhabricatorConfigSetupCheckModule' => 'PhabricatorConfigModule',
'PhabricatorConfigSiteModule' => 'PhabricatorConfigModule',
'PhabricatorConfigSiteSource' => 'PhabricatorConfigProxySource',
'PhabricatorConfigSource' => 'Phobject',
'PhabricatorConfigStackSource' => 'PhabricatorConfigSource',
'PhabricatorConfigStorageSchema' => 'Phobject',
'PhabricatorConfigTableSchema' => 'PhabricatorConfigStorageSchema',
'PhabricatorConfigTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorConfigType' => 'Phobject',
'PhabricatorConfigValidationException' => 'Exception',
'PhabricatorConpherenceApplication' => 'PhabricatorApplication',
'PhabricatorConpherenceColumnMinimizeSetting' => 'PhabricatorInternalSetting',
'PhabricatorConpherenceColumnVisibleSetting' => 'PhabricatorInternalSetting',
'PhabricatorConpherenceNotificationsSetting' => 'PhabricatorSelectSetting',
'PhabricatorConpherencePreferencesSettingsPanel' => 'PhabricatorEditEngineSettingsPanel',
'PhabricatorConpherenceProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorConpherenceRoomContextFreeGrammar' => 'PhutilContextFreeGrammar',
'PhabricatorConpherenceRoomTestDataGenerator' => 'PhabricatorTestDataGenerator',
'PhabricatorConpherenceSoundSetting' => 'PhabricatorSelectSetting',
'PhabricatorConpherenceThreadPHIDType' => 'PhabricatorPHIDType',
'PhabricatorConpherenceWidgetVisibleSetting' => 'PhabricatorInternalSetting',
'PhabricatorConsoleApplication' => 'PhabricatorApplication',
'PhabricatorConsoleContentSource' => 'PhabricatorContentSource',
'PhabricatorConstantChartFunction' => 'PhabricatorPureChartFunction',
'PhabricatorContactNumbersSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorContentSource' => 'Phobject',
'PhabricatorContentSourceModule' => 'PhabricatorConfigModule',
'PhabricatorContentSourceView' => 'AphrontView',
'PhabricatorContributedToObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorController' => 'AphrontController',
'PhabricatorCookies' => 'Phobject',
'PhabricatorCoreConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorCoreCreateTransaction' => 'PhabricatorCoreTransactionType',
'PhabricatorCoreTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorCoreVoidTransaction' => 'PhabricatorModularTransactionType',
'PhabricatorCosChartFunction' => 'PhabricatorPureChartFunction',
'PhabricatorCountFact' => 'PhabricatorFact',
'PhabricatorCountdown' => array(
'PhabricatorCountdownDAO',
'PhabricatorPolicyInterface',
'PhabricatorFlaggableInterface',
'PhabricatorSubscribableInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorTokenReceiverInterface',
'PhabricatorSpacesInterface',
'PhabricatorProjectInterface',
'PhabricatorDestructibleInterface',
'PhabricatorConduitResultInterface',
),
'PhabricatorCountdownApplication' => 'PhabricatorApplication',
'PhabricatorCountdownController' => 'PhabricatorController',
'PhabricatorCountdownCountdownPHIDType' => 'PhabricatorPHIDType',
'PhabricatorCountdownDAO' => 'PhabricatorLiskDAO',
'PhabricatorCountdownDefaultEditCapability' => 'PhabricatorPolicyCapability',
'PhabricatorCountdownDefaultViewCapability' => 'PhabricatorPolicyCapability',
'PhabricatorCountdownDescriptionTransaction' => 'PhabricatorCountdownTransactionType',
'PhabricatorCountdownEditController' => 'PhabricatorCountdownController',
'PhabricatorCountdownEditEngine' => 'PhabricatorEditEngine',
'PhabricatorCountdownEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorCountdownEpochTransaction' => 'PhabricatorCountdownTransactionType',
'PhabricatorCountdownListController' => 'PhabricatorCountdownController',
'PhabricatorCountdownMailReceiver' => 'PhabricatorObjectMailReceiver',
'PhabricatorCountdownQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorCountdownRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'PhabricatorCountdownReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PhabricatorCountdownSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorCountdownSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorCountdownTitleTransaction' => 'PhabricatorCountdownTransactionType',
'PhabricatorCountdownTransaction' => 'PhabricatorModularTransaction',
'PhabricatorCountdownTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PhabricatorCountdownTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorCountdownTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorCountdownView' => 'AphrontView',
'PhabricatorCountdownViewController' => 'PhabricatorCountdownController',
'PhabricatorCredentialEditField' => 'PhabricatorEditField',
'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery',
'PhabricatorCustomField' => 'Phobject',
'PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorCustomFieldApplicationSearchDatasource' => 'PhabricatorTypeaheadProxyDatasource',
'PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorCustomFieldAttachment' => 'Phobject',
'PhabricatorCustomFieldConfigOptionType' => 'PhabricatorConfigOptionType',
'PhabricatorCustomFieldDataNotAvailableException' => 'Exception',
'PhabricatorCustomFieldEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorCustomFieldEditField' => 'PhabricatorEditField',
'PhabricatorCustomFieldEditType' => 'PhabricatorEditType',
'PhabricatorCustomFieldExportEngineExtension' => 'PhabricatorExportEngineExtension',
'PhabricatorCustomFieldFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
'PhabricatorCustomFieldHeraldAction' => 'HeraldAction',
'PhabricatorCustomFieldHeraldActionGroup' => 'HeraldActionGroup',
'PhabricatorCustomFieldHeraldField' => 'HeraldField',
'PhabricatorCustomFieldHeraldFieldGroup' => 'HeraldFieldGroup',
'PhabricatorCustomFieldImplementationIncompleteException' => 'Exception',
'PhabricatorCustomFieldIndexStorage' => 'PhabricatorLiskDAO',
'PhabricatorCustomFieldList' => 'Phobject',
'PhabricatorCustomFieldMonogramParser' => 'Phobject',
'PhabricatorCustomFieldNotAttachedException' => 'Exception',
'PhabricatorCustomFieldNotProxyException' => 'Exception',
'PhabricatorCustomFieldNumericIndexStorage' => 'PhabricatorCustomFieldIndexStorage',
'PhabricatorCustomFieldSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'PhabricatorCustomFieldStorage' => 'PhabricatorLiskDAO',
'PhabricatorCustomFieldStorageQuery' => 'Phobject',
'PhabricatorCustomFieldStringIndexStorage' => 'PhabricatorCustomFieldIndexStorage',
'PhabricatorCustomLogoConfigType' => 'PhabricatorConfigOptionType',
'PhabricatorCustomUIFooterConfigType' => 'PhabricatorConfigJSONOptionType',
'PhabricatorDaemon' => 'PhutilDaemon',
'PhabricatorDaemonBulkJobController' => 'PhabricatorDaemonController',
'PhabricatorDaemonBulkJobListController' => 'PhabricatorDaemonBulkJobController',
'PhabricatorDaemonBulkJobMonitorController' => 'PhabricatorDaemonBulkJobController',
'PhabricatorDaemonBulkJobViewController' => 'PhabricatorDaemonBulkJobController',
'PhabricatorDaemonConsoleController' => 'PhabricatorDaemonController',
'PhabricatorDaemonContentSource' => 'PhabricatorContentSource',
'PhabricatorDaemonController' => 'PhabricatorController',
'PhabricatorDaemonDAO' => 'PhabricatorLiskDAO',
'PhabricatorDaemonEventListener' => 'PhabricatorEventListener',
'PhabricatorDaemonLockLog' => 'PhabricatorDaemonDAO',
'PhabricatorDaemonLockLogGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorDaemonLog' => array(
'PhabricatorDaemonDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorDaemonLogEvent' => 'PhabricatorDaemonDAO',
'PhabricatorDaemonLogEventGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorDaemonLogGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorDaemonLogListController' => 'PhabricatorDaemonController',
'PhabricatorDaemonLogListView' => 'AphrontView',
'PhabricatorDaemonLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorDaemonLogViewController' => 'PhabricatorDaemonController',
'PhabricatorDaemonManagementDebugWorkflow' => 'PhabricatorDaemonManagementWorkflow',
'PhabricatorDaemonManagementLaunchWorkflow' => 'PhabricatorDaemonManagementWorkflow',
'PhabricatorDaemonManagementListWorkflow' => 'PhabricatorDaemonManagementWorkflow',
'PhabricatorDaemonManagementLogWorkflow' => 'PhabricatorDaemonManagementWorkflow',
'PhabricatorDaemonManagementReloadWorkflow' => 'PhabricatorDaemonManagementWorkflow',
'PhabricatorDaemonManagementRestartWorkflow' => 'PhabricatorDaemonManagementWorkflow',
'PhabricatorDaemonManagementStartWorkflow' => 'PhabricatorDaemonManagementWorkflow',
'PhabricatorDaemonManagementStatusWorkflow' => 'PhabricatorDaemonManagementWorkflow',
'PhabricatorDaemonManagementStopWorkflow' => 'PhabricatorDaemonManagementWorkflow',
'PhabricatorDaemonManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorDaemonOverseerModule' => 'PhutilDaemonOverseerModule',
'PhabricatorDaemonReference' => 'Phobject',
'PhabricatorDaemonTaskGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorDaemonTasksTableView' => 'AphrontView',
'PhabricatorDaemonsApplication' => 'PhabricatorApplication',
'PhabricatorDaemonsSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorDailyRoutineTriggerClock' => 'PhabricatorTriggerClock',
'PhabricatorDarkConsoleSetting' => 'PhabricatorSelectSetting',
'PhabricatorDarkConsoleTabSetting' => 'PhabricatorInternalSetting',
'PhabricatorDarkConsoleVisibleSetting' => 'PhabricatorInternalSetting',
'PhabricatorDashboard' => array(
'PhabricatorDashboardDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorFlaggableInterface',
'PhabricatorDestructibleInterface',
'PhabricatorProjectInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
'PhabricatorDashboardPanelContainerInterface',
),
'PhabricatorDashboardAdjustController' => 'PhabricatorDashboardController',
'PhabricatorDashboardApplication' => 'PhabricatorApplication',
'PhabricatorDashboardApplicationInstallWorkflow' => 'PhabricatorDashboardInstallWorkflow',
'PhabricatorDashboardArchiveController' => 'PhabricatorDashboardController',
'PhabricatorDashboardChartPanelChartTransaction' => 'PhabricatorDashboardPanelPropertyTransaction',
'PhabricatorDashboardChartPanelType' => 'PhabricatorDashboardPanelType',
'PhabricatorDashboardColumn' => 'Phobject',
'PhabricatorDashboardConsoleController' => 'PhabricatorDashboardController',
'PhabricatorDashboardController' => 'PhabricatorController',
'PhabricatorDashboardDAO' => 'PhabricatorLiskDAO',
'PhabricatorDashboardDashboardPHIDType' => 'PhabricatorPHIDType',
'PhabricatorDashboardDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorDashboardEditController' => 'PhabricatorDashboardController',
'PhabricatorDashboardEditEngine' => 'PhabricatorEditEngine',
'PhabricatorDashboardFavoritesInstallWorkflow' => 'PhabricatorDashboardApplicationInstallWorkflow',
'PhabricatorDashboardFerretEngine' => 'PhabricatorFerretEngine',
'PhabricatorDashboardFullLayoutMode' => 'PhabricatorDashboardLayoutMode',
'PhabricatorDashboardFulltextEngine' => 'PhabricatorFulltextEngine',
'PhabricatorDashboardHalfLayoutMode' => 'PhabricatorDashboardLayoutMode',
'PhabricatorDashboardHomeInstallWorkflow' => 'PhabricatorDashboardApplicationInstallWorkflow',
'PhabricatorDashboardIconSet' => 'PhabricatorIconSet',
'PhabricatorDashboardIconTransaction' => 'PhabricatorDashboardTransactionType',
'PhabricatorDashboardInstallController' => 'PhabricatorDashboardController',
'PhabricatorDashboardInstallWorkflow' => 'Phobject',
'PhabricatorDashboardLayoutMode' => 'Phobject',
'PhabricatorDashboardLayoutTransaction' => 'PhabricatorDashboardTransactionType',
'PhabricatorDashboardListController' => 'PhabricatorDashboardController',
'PhabricatorDashboardNameTransaction' => 'PhabricatorDashboardTransactionType',
'PhabricatorDashboardObjectInstallWorkflow' => 'PhabricatorDashboardInstallWorkflow',
'PhabricatorDashboardOneThirdLayoutMode' => 'PhabricatorDashboardLayoutMode',
'PhabricatorDashboardPanel' => array(
'PhabricatorDashboardDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorFlaggableInterface',
'PhabricatorDestructibleInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
'PhabricatorDashboardPanelContainerInterface',
),
'PhabricatorDashboardPanelArchiveController' => 'PhabricatorDashboardController',
'PhabricatorDashboardPanelContainerIndexEngineExtension' => 'PhabricatorEdgeIndexEngineExtension',
'PhabricatorDashboardPanelDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorDashboardPanelEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PhabricatorDashboardPanelEditController' => 'PhabricatorDashboardController',
'PhabricatorDashboardPanelEditEngine' => 'PhabricatorEditEngine',
'PhabricatorDashboardPanelFerretEngine' => 'PhabricatorFerretEngine',
'PhabricatorDashboardPanelFulltextEngine' => 'PhabricatorFulltextEngine',
'PhabricatorDashboardPanelListController' => 'PhabricatorDashboardController',
'PhabricatorDashboardPanelNameTransaction' => 'PhabricatorDashboardPanelTransactionType',
'PhabricatorDashboardPanelPHIDType' => 'PhabricatorPHIDType',
'PhabricatorDashboardPanelPropertyTransaction' => 'PhabricatorDashboardPanelTransactionType',
'PhabricatorDashboardPanelQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorDashboardPanelRef' => 'Phobject',
'PhabricatorDashboardPanelRefList' => 'Phobject',
'PhabricatorDashboardPanelRenderController' => 'PhabricatorDashboardController',
'PhabricatorDashboardPanelRenderingEngine' => 'Phobject',
'PhabricatorDashboardPanelSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorDashboardPanelStatusTransaction' => 'PhabricatorDashboardPanelTransactionType',
'PhabricatorDashboardPanelTabsController' => 'PhabricatorDashboardController',
'PhabricatorDashboardPanelTransaction' => 'PhabricatorModularTransaction',
'PhabricatorDashboardPanelTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorDashboardPanelTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorDashboardPanelTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorDashboardPanelType' => 'Phobject',
'PhabricatorDashboardPanelUsedByObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorDashboardPanelViewController' => 'PhabricatorDashboardController',
'PhabricatorDashboardPanelsTransaction' => 'PhabricatorDashboardTransactionType',
'PhabricatorDashboardPortal' => array(
'PhabricatorDashboardDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorProjectInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
),
'PhabricatorDashboardPortalController' => 'PhabricatorDashboardController',
'PhabricatorDashboardPortalDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorDashboardPortalEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PhabricatorDashboardPortalEditController' => 'PhabricatorDashboardPortalController',
'PhabricatorDashboardPortalEditEngine' => 'PhabricatorEditEngine',
'PhabricatorDashboardPortalEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorDashboardPortalFerretEngine' => 'PhabricatorFerretEngine',
'PhabricatorDashboardPortalFulltextEngine' => 'PhabricatorFulltextEngine',
'PhabricatorDashboardPortalInstallWorkflow' => 'PhabricatorDashboardObjectInstallWorkflow',
'PhabricatorDashboardPortalListController' => 'PhabricatorDashboardPortalController',
'PhabricatorDashboardPortalMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorDashboardPortalNameTransaction' => 'PhabricatorDashboardPortalTransactionType',
'PhabricatorDashboardPortalPHIDType' => 'PhabricatorPHIDType',
'PhabricatorDashboardPortalProfileMenuEngine' => 'PhabricatorProfileMenuEngine',
'PhabricatorDashboardPortalQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorDashboardPortalSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhabricatorDashboardPortalSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorDashboardPortalStatus' => 'Phobject',
'PhabricatorDashboardPortalTransaction' => 'PhabricatorModularTransaction',
'PhabricatorDashboardPortalTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorDashboardPortalTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorDashboardPortalViewController' => 'PhabricatorDashboardPortalController',
'PhabricatorDashboardProfileController' => 'PhabricatorController',
'PhabricatorDashboardProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorDashboardProjectInstallWorkflow' => 'PhabricatorDashboardObjectInstallWorkflow',
'PhabricatorDashboardQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorDashboardQueryPanelApplicationEditField' => 'PhabricatorEditField',
'PhabricatorDashboardQueryPanelApplicationTransaction' => 'PhabricatorDashboardPanelPropertyTransaction',
'PhabricatorDashboardQueryPanelInstallController' => 'PhabricatorDashboardController',
'PhabricatorDashboardQueryPanelLimitTransaction' => 'PhabricatorDashboardPanelPropertyTransaction',
'PhabricatorDashboardQueryPanelQueryEditField' => 'PhabricatorEditField',
'PhabricatorDashboardQueryPanelQueryTransaction' => 'PhabricatorDashboardPanelPropertyTransaction',
'PhabricatorDashboardQueryPanelType' => 'PhabricatorDashboardPanelType',
'PhabricatorDashboardRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'PhabricatorDashboardRenderingEngine' => 'Phobject',
'PhabricatorDashboardSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorDashboardSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorDashboardStatusTransaction' => 'PhabricatorDashboardTransactionType',
'PhabricatorDashboardTabsPanelTabsTransaction' => 'PhabricatorDashboardPanelPropertyTransaction',
'PhabricatorDashboardTabsPanelType' => 'PhabricatorDashboardPanelType',
'PhabricatorDashboardTextPanelTextTransaction' => 'PhabricatorDashboardPanelPropertyTransaction',
'PhabricatorDashboardTextPanelType' => 'PhabricatorDashboardPanelType',
'PhabricatorDashboardTransaction' => 'PhabricatorModularTransaction',
'PhabricatorDashboardTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorDashboardTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorDashboardTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorDashboardTwoThirdsLayoutMode' => 'PhabricatorDashboardLayoutMode',
'PhabricatorDashboardViewController' => 'PhabricatorDashboardProfileController',
'PhabricatorDataCacheSpec' => 'PhabricatorCacheSpec',
'PhabricatorDataNotAttachedException' => 'Exception',
'PhabricatorDatabaseRef' => 'Phobject',
'PhabricatorDatabaseRefParser' => 'Phobject',
'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorDatasourceApplicationEngineExtension' => 'PhabricatorDatasourceEngineExtension',
'PhabricatorDatasourceEditField' => 'PhabricatorTokenizerEditField',
'PhabricatorDatasourceEditType' => 'PhabricatorPHIDListEditType',
'PhabricatorDatasourceEngine' => 'Phobject',
'PhabricatorDatasourceEngineExtension' => 'Phobject',
'PhabricatorDatasourceURIEngineExtension' => 'PhabricatorDatasourceEngineExtension',
'PhabricatorDateFormatSetting' => 'PhabricatorSelectSetting',
'PhabricatorDateTimeSettingsPanel' => 'PhabricatorEditEngineSettingsPanel',
'PhabricatorDebugController' => 'PhabricatorController',
'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorDefaultSyntaxStyle' => 'PhabricatorSyntaxStyle',
'PhabricatorDefaultUnlockEngine' => 'PhabricatorUnlockEngine',
'PhabricatorDemoChartEngine' => 'PhabricatorChartEngine',
'PhabricatorDestructibleCodex' => 'Phobject',
'PhabricatorDestructionEngine' => 'Phobject',
'PhabricatorDestructionEngineExtension' => 'Phobject',
'PhabricatorDestructionEngineExtensionModule' => 'PhabricatorConfigModule',
'PhabricatorDeveloperConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorDeveloperPreferencesSettingsPanel' => 'PhabricatorEditEngineSettingsPanel',
'PhabricatorDiffInlineCommentContentState' => 'PhabricatorInlineCommentContentState',
'PhabricatorDiffInlineCommentContext' => 'PhabricatorInlineCommentContext',
'PhabricatorDiffInlineCommentQuery' => 'PhabricatorApplicationTransactionCommentQuery',
'PhabricatorDiffPreferencesSettingsPanel' => 'PhabricatorEditEngineSettingsPanel',
'PhabricatorDiffScopeEngine' => 'Phobject',
'PhabricatorDiffScopeEngineTestCase' => 'PhabricatorTestCase',
'PhabricatorDifferenceEngine' => 'Phobject',
'PhabricatorDifferentialApplication' => 'PhabricatorApplication',
'PhabricatorDifferentialAttachCommitWorkflow' => 'PhabricatorDifferentialManagementWorkflow',
'PhabricatorDifferentialConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorDifferentialExtractWorkflow' => 'PhabricatorDifferentialManagementWorkflow',
'PhabricatorDifferentialManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorDifferentialMigrateHunkWorkflow' => 'PhabricatorDifferentialManagementWorkflow',
'PhabricatorDifferentialRebuildChangesetsWorkflow' => 'PhabricatorDifferentialManagementWorkflow',
'PhabricatorDifferentialRevisionTestDataGenerator' => 'PhabricatorTestDataGenerator',
'PhabricatorDiffusionApplication' => 'PhabricatorApplication',
'PhabricatorDiffusionBlameSetting' => 'PhabricatorInternalSetting',
'PhabricatorDiffusionConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorDisabledUserController' => 'PhabricatorAuthController',
'PhabricatorDisplayPreferencesSettingsPanel' => 'PhabricatorEditEngineSettingsPanel',
'PhabricatorDisqusAuthProvider' => 'PhabricatorOAuth2AuthProvider',
'PhabricatorDividerEditField' => 'PhabricatorEditField',
'PhabricatorDividerProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorDivinerApplication' => 'PhabricatorApplication',
'PhabricatorDocumentEngine' => 'Phobject',
'PhabricatorDocumentEngineBlock' => 'Phobject',
'PhabricatorDocumentEngineBlockDiff' => 'Phobject',
'PhabricatorDocumentEngineBlocks' => 'Phobject',
'PhabricatorDocumentEngineParserException' => 'Exception',
'PhabricatorDocumentRef' => 'Phobject',
'PhabricatorDocumentRenderingEngine' => 'Phobject',
'PhabricatorDoorkeeperApplication' => 'PhabricatorApplication',
'PhabricatorDoubleExportField' => 'PhabricatorExportField',
'PhabricatorDraft' => 'PhabricatorDraftDAO',
'PhabricatorDraftDAO' => 'PhabricatorLiskDAO',
'PhabricatorDraftEngine' => 'Phobject',
'PhabricatorDrydockApplication' => 'PhabricatorApplication',
'PhabricatorDuoAuthFactor' => 'PhabricatorAuthFactor',
'PhabricatorDuoFuture' => 'FutureProxy',
'PhabricatorEdgeChangeRecord' => 'Phobject',
'PhabricatorEdgeChangeRecordTestCase' => 'PhabricatorTestCase',
'PhabricatorEdgeConfig' => 'PhabricatorEdgeConstants',
'PhabricatorEdgeConstants' => 'Phobject',
'PhabricatorEdgeCycleException' => 'Exception',
'PhabricatorEdgeEditType' => 'PhabricatorPHIDListEditType',
'PhabricatorEdgeEditor' => 'Phobject',
'PhabricatorEdgeGraph' => 'AbstractDirectedGraph',
'PhabricatorEdgeIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
'PhabricatorEdgeObject' => array(
'Phobject',
'PhabricatorPolicyInterface',
),
'PhabricatorEdgeObjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorEdgeQuery' => 'PhabricatorQuery',
'PhabricatorEdgeTestCase' => 'PhabricatorTestCase',
'PhabricatorEdgeType' => 'Phobject',
'PhabricatorEdgeTypeTestCase' => 'PhabricatorTestCase',
'PhabricatorEdgesDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
'PhabricatorEditEngine' => array(
'Phobject',
'PhabricatorPolicyInterface',
),
'PhabricatorEditEngineAPIMethod' => 'ConduitAPIMethod',
'PhabricatorEditEngineBulkJobType' => 'PhabricatorWorkerBulkJobType',
'PhabricatorEditEngineCheckboxesCommentAction' => 'PhabricatorEditEngineCommentAction',
'PhabricatorEditEngineColumnsCommentAction' => 'PhabricatorEditEngineCommentAction',
'PhabricatorEditEngineCommentAction' => 'Phobject',
'PhabricatorEditEngineCommentActionGroup' => 'Phobject',
'PhabricatorEditEngineConfiguration' => array(
'PhabricatorSearchDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
),
'PhabricatorEditEngineConfigurationDefaultCreateController' => 'PhabricatorEditEngineController',
'PhabricatorEditEngineConfigurationDefaultsController' => 'PhabricatorEditEngineController',
'PhabricatorEditEngineConfigurationDisableController' => 'PhabricatorEditEngineController',
'PhabricatorEditEngineConfigurationEditController' => 'PhabricatorEditEngineController',
'PhabricatorEditEngineConfigurationEditEngine' => 'PhabricatorEditEngine',
'PhabricatorEditEngineConfigurationEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorEditEngineConfigurationIsEditController' => 'PhabricatorEditEngineController',
'PhabricatorEditEngineConfigurationListController' => 'PhabricatorEditEngineController',
'PhabricatorEditEngineConfigurationLockController' => 'PhabricatorEditEngineController',
'PhabricatorEditEngineConfigurationPHIDType' => 'PhabricatorPHIDType',
'PhabricatorEditEngineConfigurationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorEditEngineConfigurationReorderController' => 'PhabricatorEditEngineController',
'PhabricatorEditEngineConfigurationSaveController' => 'PhabricatorEditEngineController',
'PhabricatorEditEngineConfigurationSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorEditEngineConfigurationSortController' => 'PhabricatorEditEngineController',
'PhabricatorEditEngineConfigurationSubtypeController' => 'PhabricatorEditEngineController',
'PhabricatorEditEngineConfigurationTransaction' => 'PhabricatorModularTransaction',
'PhabricatorEditEngineConfigurationTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorEditEngineConfigurationViewController' => 'PhabricatorEditEngineController',
'PhabricatorEditEngineController' => 'PhabricatorApplicationTransactionController',
'PhabricatorEditEngineCreateOrderTransaction' => 'PhabricatorEditEngineTransactionType',
'PhabricatorEditEngineDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorEditEngineDefaultCreateTransaction' => 'PhabricatorEditEngineTransactionType',
'PhabricatorEditEngineDefaultLock' => 'PhabricatorEditEngineLock',
'PhabricatorEditEngineDefaultTransaction' => 'PhabricatorEditEngineTransactionType',
'PhabricatorEditEngineDisableTransaction' => 'PhabricatorEditEngineTransactionType',
'PhabricatorEditEngineEditOrderTransaction' => 'PhabricatorEditEngineTransactionType',
'PhabricatorEditEngineExtension' => 'Phobject',
'PhabricatorEditEngineExtensionModule' => 'PhabricatorConfigModule',
'PhabricatorEditEngineIsEditTransaction' => 'PhabricatorEditEngineTransactionType',
'PhabricatorEditEngineListController' => 'PhabricatorEditEngineController',
'PhabricatorEditEngineLock' => 'Phobject',
'PhabricatorEditEngineLocksTransaction' => 'PhabricatorEditEngineTransactionType',
'PhabricatorEditEngineMFAEngine' => 'Phobject',
'PhabricatorEditEngineNameTransaction' => 'PhabricatorEditEngineTransactionType',
'PhabricatorEditEngineOrderTransaction' => 'PhabricatorEditEngineTransactionType',
'PhabricatorEditEnginePageState' => 'Phobject',
'PhabricatorEditEnginePointsCommentAction' => 'PhabricatorEditEngineCommentAction',
'PhabricatorEditEnginePreambleTransaction' => 'PhabricatorEditEngineTransactionType',
'PhabricatorEditEngineProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorEditEngineQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorEditEngineSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorEditEngineSelectCommentAction' => 'PhabricatorEditEngineCommentAction',
'PhabricatorEditEngineSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorEditEngineStaticCommentAction' => 'PhabricatorEditEngineCommentAction',
'PhabricatorEditEngineSubtype' => 'Phobject',
'PhabricatorEditEngineSubtypeHeraldField' => 'HeraldField',
'PhabricatorEditEngineSubtypeMap' => 'Phobject',
'PhabricatorEditEngineSubtypeTestCase' => 'PhabricatorTestCase',
'PhabricatorEditEngineSubtypeTransaction' => 'PhabricatorEditEngineTransactionType',
'PhabricatorEditEngineTokenizerCommentAction' => 'PhabricatorEditEngineCommentAction',
'PhabricatorEditEngineTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorEditField' => 'Phobject',
'PhabricatorEditPage' => 'Phobject',
'PhabricatorEditType' => 'Phobject',
'PhabricatorEditor' => 'Phobject',
'PhabricatorEditorExtension' => 'Phobject',
'PhabricatorEditorExtensionModule' => 'PhabricatorConfigModule',
'PhabricatorEditorMailEngineExtension' => 'PhabricatorMailEngineExtension',
'PhabricatorEditorSetting' => 'PhabricatorStringSetting',
'PhabricatorEditorURIEngine' => 'Phobject',
'PhabricatorEditorURIEngineTestCase' => 'PhabricatorTestCase',
'PhabricatorEditorURIParserException' => 'Exception',
'PhabricatorElasticFulltextStorageEngine' => 'PhabricatorFulltextStorageEngine',
'PhabricatorElasticsearchHost' => 'PhabricatorSearchHost',
'PhabricatorElasticsearchSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorEmailAddressesSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorEmailContentSource' => 'PhabricatorContentSource',
'PhabricatorEmailDeliverySettingsPanel' => 'PhabricatorEditEngineSettingsPanel',
'PhabricatorEmailFormatSetting' => 'PhabricatorSelectSetting',
'PhabricatorEmailFormatSettingsPanel' => 'PhabricatorEditEngineSettingsPanel',
'PhabricatorEmailLoginController' => 'PhabricatorAuthController',
'PhabricatorEmailLoginUserLogType' => 'PhabricatorUserLogType',
'PhabricatorEmailNotificationsSetting' => 'PhabricatorSelectSetting',
'PhabricatorEmailPreferencesSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorEmailRePrefixSetting' => 'PhabricatorSelectSetting',
'PhabricatorEmailSelfActionsSetting' => 'PhabricatorSelectSetting',
'PhabricatorEmailStampsSetting' => 'PhabricatorSelectSetting',
'PhabricatorEmailTagsSetting' => 'PhabricatorInternalSetting',
'PhabricatorEmailVarySubjectsSetting' => 'PhabricatorSelectSetting',
'PhabricatorEmailVerificationController' => 'PhabricatorAuthController',
'PhabricatorEmbedFileRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'PhabricatorEmojiDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorEmojiRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorEmojiTranslation' => 'PhutilTranslation',
'PhabricatorEmptyQueryException' => 'Exception',
'PhabricatorEnterHisecUserLogType' => 'PhabricatorUserLogType',
'PhabricatorEnumConfigType' => 'PhabricatorTextConfigType',
'PhabricatorEnv' => 'Phobject',
'PhabricatorEnvTestCase' => 'PhabricatorTestCase',
'PhabricatorEpochEditField' => 'PhabricatorEditField',
'PhabricatorEpochExportField' => 'PhabricatorExportField',
'PhabricatorEvent' => 'PhutilEvent',
'PhabricatorEventEngine' => 'Phobject',
'PhabricatorEventListener' => 'PhutilEventListener',
'PhabricatorEventType' => 'PhutilEventType',
'PhabricatorExampleEventListener' => 'PhabricatorEventListener',
'PhabricatorExcelExportFormat' => 'PhabricatorExportFormat',
'PhabricatorExecFutureFileUploadSource' => 'PhabricatorFileUploadSource',
'PhabricatorExitHisecUserLogType' => 'PhabricatorUserLogType',
'PhabricatorExportEngine' => 'Phobject',
'PhabricatorExportEngineBulkJobType' => 'PhabricatorWorkerSingleBulkJobType',
'PhabricatorExportEngineExtension' => 'Phobject',
'PhabricatorExportField' => 'Phobject',
'PhabricatorExportFormat' => 'Phobject',
'PhabricatorExportFormatSetting' => 'PhabricatorInternalSetting',
'PhabricatorExtendingPhabricatorConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorExtensionsSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorExternalAccount' => array(
'PhabricatorUserDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorExternalAccountIdentifier' => array(
'PhabricatorUserDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorExternalAccountIdentifierQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorExternalAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorExternalAccountsSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorExternalEditorSettingsPanel' => 'PhabricatorEditEngineSettingsPanel',
'PhabricatorExtraConfigSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorFacebookAuthProvider' => 'PhabricatorOAuth2AuthProvider',
'PhabricatorFact' => 'Phobject',
'PhabricatorFactAggregate' => 'PhabricatorFactDAO',
'PhabricatorFactApplication' => 'PhabricatorApplication',
'PhabricatorFactChart' => array(
'PhabricatorFactDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorFactChartController' => 'PhabricatorFactController',
'PhabricatorFactChartFunction' => 'PhabricatorChartFunction',
'PhabricatorFactController' => 'PhabricatorController',
'PhabricatorFactCursor' => 'PhabricatorFactDAO',
'PhabricatorFactDAO' => 'PhabricatorLiskDAO',
'PhabricatorFactDaemon' => 'PhabricatorDaemon',
'PhabricatorFactDatapointQuery' => 'Phobject',
'PhabricatorFactDimension' => 'PhabricatorFactDAO',
'PhabricatorFactEngine' => 'Phobject',
'PhabricatorFactEngineTestCase' => 'PhabricatorTestCase',
'PhabricatorFactHomeController' => 'PhabricatorFactController',
'PhabricatorFactIntDatapoint' => 'PhabricatorFactDAO',
'PhabricatorFactKeyDimension' => 'PhabricatorFactDimension',
'PhabricatorFactManagementAnalyzeWorkflow' => 'PhabricatorFactManagementWorkflow',
'PhabricatorFactManagementCursorsWorkflow' => 'PhabricatorFactManagementWorkflow',
'PhabricatorFactManagementDestroyWorkflow' => 'PhabricatorFactManagementWorkflow',
'PhabricatorFactManagementListWorkflow' => 'PhabricatorFactManagementWorkflow',
'PhabricatorFactManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorFactObjectController' => 'PhabricatorFactController',
'PhabricatorFactObjectDimension' => 'PhabricatorFactDimension',
'PhabricatorFactRaw' => 'PhabricatorFactDAO',
'PhabricatorFactUpdateIterator' => 'PhutilBufferedIterator',
'PhabricatorFailHisecUserLogType' => 'PhabricatorUserLogType',
'PhabricatorFaviconController' => 'PhabricatorController',
'PhabricatorFaviconRef' => 'Phobject',
'PhabricatorFaviconRefQuery' => 'Phobject',
'PhabricatorFavoritesApplication' => 'PhabricatorApplication',
'PhabricatorFavoritesController' => 'PhabricatorController',
'PhabricatorFavoritesMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension',
'PhabricatorFavoritesMenuItemController' => 'PhabricatorFavoritesController',
'PhabricatorFavoritesProfileMenuEngine' => 'PhabricatorProfileMenuEngine',
'PhabricatorFaxContentSource' => 'PhabricatorContentSource',
'PhabricatorFeedApplication' => 'PhabricatorApplication',
'PhabricatorFeedBuilder' => 'Phobject',
'PhabricatorFeedConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorFeedController' => 'PhabricatorController',
'PhabricatorFeedDAO' => 'PhabricatorLiskDAO',
'PhabricatorFeedDetailController' => 'PhabricatorFeedController',
'PhabricatorFeedListController' => 'PhabricatorFeedController',
'PhabricatorFeedManagementRepublishWorkflow' => 'PhabricatorFeedManagementWorkflow',
'PhabricatorFeedManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorFeedQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorFeedSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorFeedStory' => array(
'Phobject',
'PhabricatorPolicyInterface',
'PhabricatorMarkupInterface',
),
'PhabricatorFeedStoryData' => array(
'PhabricatorFeedDAO',
'PhabricatorDestructibleInterface',
),
'PhabricatorFeedStoryNotification' => 'PhabricatorFeedDAO',
'PhabricatorFeedStoryPublisher' => 'Phobject',
'PhabricatorFeedStoryReference' => 'PhabricatorFeedDAO',
'PhabricatorFeedTransactionListController' => 'PhabricatorFeedController',
'PhabricatorFeedTransactionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorFeedTransactionSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorFerretEngine' => 'Phobject',
'PhabricatorFerretEngineTestCase' => 'PhabricatorTestCase',
'PhabricatorFerretFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
'PhabricatorFerretFulltextStorageEngine' => 'PhabricatorFulltextStorageEngine',
'PhabricatorFerretMetadata' => 'Phobject',
'PhabricatorFerretSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'PhabricatorFile' => array(
'PhabricatorFileDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorTokenReceiverInterface',
'PhabricatorSubscribableInterface',
'PhabricatorFlaggableInterface',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorConduitResultInterface',
'PhabricatorIndexableInterface',
'PhabricatorNgramsInterface',
),
'PhabricatorFileAES256StorageFormat' => 'PhabricatorFileStorageFormat',
'PhabricatorFileAltTextTransaction' => 'PhabricatorFileTransactionType',
+ 'PhabricatorFileAttachment' => array(
+ 'PhabricatorFileDAO',
+ 'PhabricatorPolicyInterface',
+ 'PhabricatorExtendedPolicyInterface',
+ ),
+ 'PhabricatorFileAttachmentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorFileBundleLoader' => 'Phobject',
'PhabricatorFileChunk' => array(
'PhabricatorFileDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorFileChunkIterator' => array(
'Phobject',
'Iterator',
),
'PhabricatorFileChunkQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorFileComposeController' => 'PhabricatorFileController',
'PhabricatorFileController' => 'PhabricatorController',
'PhabricatorFileDAO' => 'PhabricatorLiskDAO',
'PhabricatorFileDataController' => 'PhabricatorFileController',
'PhabricatorFileDeleteController' => 'PhabricatorFileController',
'PhabricatorFileDeleteTransaction' => 'PhabricatorFileTransactionType',
+ 'PhabricatorFileDetachController' => 'PhabricatorFileController',
'PhabricatorFileDocumentController' => 'PhabricatorFileController',
'PhabricatorFileDocumentRenderingEngine' => 'PhabricatorDocumentRenderingEngine',
'PhabricatorFileDropUploadController' => 'PhabricatorFileController',
'PhabricatorFileEditController' => 'PhabricatorFileController',
'PhabricatorFileEditEngine' => 'PhabricatorEditEngine',
'PhabricatorFileEditField' => 'PhabricatorEditField',
'PhabricatorFileEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorFileExternalRequest' => array(
'PhabricatorFileDAO',
'PhabricatorDestructibleInterface',
),
'PhabricatorFileExternalRequestGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorFileFilePHIDType' => 'PhabricatorPHIDType',
- 'PhabricatorFileHasObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorFileIconSetSelectController' => 'PhabricatorFileController',
'PhabricatorFileImageMacro' => array(
'PhabricatorFileDAO',
'PhabricatorSubscribableInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorFlaggableInterface',
'PhabricatorTokenReceiverInterface',
'PhabricatorPolicyInterface',
),
'PhabricatorFileImageProxyController' => 'PhabricatorFileController',
'PhabricatorFileImageTransform' => 'PhabricatorFileTransform',
'PhabricatorFileIntegrityException' => 'Exception',
'PhabricatorFileLightboxController' => 'PhabricatorFileController',
'PhabricatorFileLinkView' => 'AphrontTagView',
'PhabricatorFileListController' => 'PhabricatorFileController',
'PhabricatorFileNameNgrams' => 'PhabricatorSearchNgrams',
'PhabricatorFileNameTransaction' => 'PhabricatorFileTransactionType',
'PhabricatorFileQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorFileROT13StorageFormat' => 'PhabricatorFileStorageFormat',
'PhabricatorFileRawStorageFormat' => 'PhabricatorFileStorageFormat',
'PhabricatorFileSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorFileSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhabricatorFileSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO',
'PhabricatorFileStorageConfigurationException' => 'Exception',
'PhabricatorFileStorageEngine' => 'Phobject',
'PhabricatorFileStorageEngineTestCase' => 'PhabricatorTestCase',
'PhabricatorFileStorageFormat' => 'Phobject',
'PhabricatorFileStorageFormatTestCase' => 'PhabricatorTestCase',
'PhabricatorFileTemporaryGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorFileTestCase' => 'PhabricatorTestCase',
'PhabricatorFileTestDataGenerator' => 'PhabricatorTestDataGenerator',
'PhabricatorFileThumbnailTransform' => 'PhabricatorFileImageTransform',
'PhabricatorFileTransaction' => 'PhabricatorModularTransaction',
'PhabricatorFileTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PhabricatorFileTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorFileTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorFileTransform' => 'Phobject',
'PhabricatorFileTransformController' => 'PhabricatorFileController',
'PhabricatorFileTransformListController' => 'PhabricatorFileController',
'PhabricatorFileTransformTestCase' => 'PhabricatorTestCase',
+ 'PhabricatorFileUICurtainAttachController' => 'PhabricatorFileController',
+ 'PhabricatorFileUICurtainListController' => 'PhabricatorFileController',
'PhabricatorFileUploadController' => 'PhabricatorFileController',
'PhabricatorFileUploadDialogController' => 'PhabricatorFileController',
'PhabricatorFileUploadException' => 'Exception',
'PhabricatorFileUploadSource' => 'Phobject',
'PhabricatorFileUploadSourceByteLimitException' => 'Exception',
'PhabricatorFileViewController' => 'PhabricatorFileController',
'PhabricatorFileinfoSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorFilesApplication' => 'PhabricatorApplication',
'PhabricatorFilesApplicationStorageEnginePanel' => 'PhabricatorApplicationConfigurationPanel',
'PhabricatorFilesBuiltinFile' => 'Phobject',
'PhabricatorFilesComposeAvatarBuiltinFile' => 'PhabricatorFilesBuiltinFile',
'PhabricatorFilesComposeIconBuiltinFile' => 'PhabricatorFilesBuiltinFile',
'PhabricatorFilesConfigOptions' => 'PhabricatorApplicationConfigOptions',
+ 'PhabricatorFilesCurtainExtension' => 'PHUICurtainExtension',
'PhabricatorFilesManagementCatWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementCompactWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementCycleWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementEncodeWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementEnginesWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementGenerateKeyWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementIntegrityWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementMigrateWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementRebuildWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorFilesOnDiskBuiltinFile' => 'PhabricatorFilesBuiltinFile',
'PhabricatorFilesOutboundRequestAction' => 'PhabricatorSystemAction',
'PhabricatorFiletreeVisibleSetting' => 'PhabricatorInternalSetting',
'PhabricatorFiletreeWidthSetting' => 'PhabricatorInternalSetting',
'PhabricatorFlag' => array(
'PhabricatorFlagDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorFlagAddFlagHeraldAction' => 'PhabricatorFlagHeraldAction',
'PhabricatorFlagColor' => 'PhabricatorFlagConstants',
'PhabricatorFlagConstants' => 'Phobject',
'PhabricatorFlagController' => 'PhabricatorController',
'PhabricatorFlagDAO' => 'PhabricatorLiskDAO',
'PhabricatorFlagDeleteController' => 'PhabricatorFlagController',
'PhabricatorFlagDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
'PhabricatorFlagEditController' => 'PhabricatorFlagController',
'PhabricatorFlagHeraldAction' => 'HeraldAction',
'PhabricatorFlagListController' => 'PhabricatorFlagController',
'PhabricatorFlagQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorFlagRemoveFlagHeraldAction' => 'PhabricatorFlagHeraldAction',
'PhabricatorFlagSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorFlagSelectControl' => 'AphrontFormControl',
'PhabricatorFlaggableInterface' => 'PhabricatorPHIDInterface',
'PhabricatorFlagsApplication' => 'PhabricatorApplication',
'PhabricatorFlagsUIEventListener' => 'PhabricatorEventListener',
'PhabricatorFullLoginUserLogType' => 'PhabricatorUserLogType',
'PhabricatorFulltextEngine' => 'Phobject',
'PhabricatorFulltextEngineExtension' => 'Phobject',
'PhabricatorFulltextEngineExtensionModule' => 'PhabricatorConfigModule',
'PhabricatorFulltextIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
'PhabricatorFulltextInterface' => 'PhabricatorIndexableInterface',
'PhabricatorFulltextResultSet' => 'Phobject',
'PhabricatorFulltextStorageEngine' => 'Phobject',
'PhabricatorFulltextToken' => 'Phobject',
'PhabricatorFundApplication' => 'PhabricatorApplication',
'PhabricatorGDSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorGarbageCollector' => 'Phobject',
'PhabricatorGarbageCollectorManagementCollectWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow',
'PhabricatorGarbageCollectorManagementCompactEdgesWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow',
'PhabricatorGarbageCollectorManagementSetPolicyWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow',
'PhabricatorGarbageCollectorManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorGeneralCachePurger' => 'PhabricatorCachePurger',
'PhabricatorGestureUIExample' => 'PhabricatorUIExample',
'PhabricatorGitGraphStream' => 'PhabricatorRepositoryGraphStream',
'PhabricatorGitHubAuthProvider' => 'PhabricatorOAuth2AuthProvider',
'PhabricatorGlobalLock' => 'PhutilLock',
'PhabricatorGlobalLockTestCase' => 'PhabricatorTestCase',
'PhabricatorGlobalUploadTargetView' => 'AphrontView',
'PhabricatorGoogleAuthProvider' => 'PhabricatorOAuth2AuthProvider',
'PhabricatorGuidanceContext' => 'Phobject',
'PhabricatorGuidanceEngine' => 'Phobject',
'PhabricatorGuidanceEngineExtension' => 'Phobject',
'PhabricatorGuidanceMessage' => 'Phobject',
'PhabricatorGuideApplication' => 'PhabricatorApplication',
'PhabricatorGuideController' => 'PhabricatorController',
'PhabricatorGuideInstallModule' => 'PhabricatorGuideModule',
'PhabricatorGuideItemView' => 'Phobject',
'PhabricatorGuideListView' => 'AphrontView',
'PhabricatorGuideModule' => 'Phobject',
'PhabricatorGuideModuleController' => 'PhabricatorGuideController',
'PhabricatorGuideQuickStartModule' => 'PhabricatorGuideModule',
'PhabricatorHMACTestCase' => 'PhabricatorTestCase',
'PhabricatorHTTPParameterTypeTableView' => 'AphrontView',
'PhabricatorHandleList' => array(
'Phobject',
'Iterator',
'ArrayAccess',
'Countable',
),
'PhabricatorHandleObjectSelectorDataView' => 'Phobject',
'PhabricatorHandlePool' => 'Phobject',
'PhabricatorHandlePoolTestCase' => 'PhabricatorTestCase',
'PhabricatorHandleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorHandleRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorHandlesEditField' => 'PhabricatorPHIDListEditField',
'PhabricatorHarbormasterApplication' => 'PhabricatorApplication',
'PhabricatorHash' => 'Phobject',
'PhabricatorHashTestCase' => 'PhabricatorTestCase',
'PhabricatorHelpApplication' => 'PhabricatorApplication',
'PhabricatorHelpController' => 'PhabricatorController',
'PhabricatorHelpDocumentationController' => 'PhabricatorHelpController',
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
'PhabricatorHeraldApplication' => 'PhabricatorApplication',
'PhabricatorHeraldContentSource' => 'PhabricatorContentSource',
'PhabricatorHexdumpDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorHighSecurityRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorHigherOrderChartFunction' => 'PhabricatorChartFunction',
'PhabricatorHomeApplication' => 'PhabricatorApplication',
'PhabricatorHomeConstants' => 'PhabricatorHomeController',
'PhabricatorHomeController' => 'PhabricatorController',
'PhabricatorHomeLauncherProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorHomeMenuItemController' => 'PhabricatorHomeController',
'PhabricatorHomeProfileMenuEngine' => 'PhabricatorProfileMenuEngine',
'PhabricatorHomeProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorHovercardEngineExtension' => 'Phobject',
'PhabricatorHovercardEngineExtensionModule' => 'PhabricatorConfigModule',
'PhabricatorIDExportField' => 'PhabricatorExportField',
'PhabricatorIDsSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'PhabricatorIDsSearchField' => 'PhabricatorSearchField',
'PhabricatorIconDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorIconRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorIconSet' => 'Phobject',
'PhabricatorIconSetEditField' => 'PhabricatorEditField',
'PhabricatorIconSetIcon' => 'Phobject',
'PhabricatorImageDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorImageMacroRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorImageRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorImageTransformer' => 'Phobject',
'PhabricatorImagemagickSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorInFlightErrorView' => 'AphrontView',
'PhabricatorIndexEngine' => 'Phobject',
'PhabricatorIndexEngineExtension' => 'Phobject',
'PhabricatorIndexEngineExtensionModule' => 'PhabricatorConfigModule',
'PhabricatorInfrastructureTestCase' => 'PhabricatorTestCase',
'PhabricatorInlineComment' => array(
'Phobject',
'PhabricatorMarkupInterface',
),
'PhabricatorInlineCommentAdjustmentEngine' => 'Phobject',
'PhabricatorInlineCommentContentState' => 'Phobject',
'PhabricatorInlineCommentContext' => 'Phobject',
'PhabricatorInlineCommentController' => 'PhabricatorController',
'PhabricatorInlineSummaryView' => 'AphrontView',
'PhabricatorInstructionsEditField' => 'PhabricatorEditField',
'PhabricatorIntConfigType' => 'PhabricatorTextConfigType',
'PhabricatorIntEditField' => 'PhabricatorEditField',
'PhabricatorIntExportField' => 'PhabricatorExportField',
'PhabricatorInternalSetting' => 'PhabricatorSetting',
'PhabricatorInternationalizationManagementExtractWorkflow' => 'PhabricatorInternationalizationManagementWorkflow',
'PhabricatorInternationalizationManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorInvalidConfigSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorInvalidQueryCursorException' => 'Exception',
'PhabricatorIteratedMD5PasswordHasher' => 'PhabricatorPasswordHasher',
'PhabricatorIteratedMD5PasswordHasherTestCase' => 'PhabricatorTestCase',
'PhabricatorIteratorFileUploadSource' => 'PhabricatorFileUploadSource',
'PhabricatorJIRAAuthProvider' => array(
'PhabricatorOAuth1AuthProvider',
'DoorkeeperRemarkupURIInterface',
),
'PhabricatorJSONConfigType' => 'PhabricatorTextConfigType',
'PhabricatorJSONDocumentEngine' => 'PhabricatorTextDocumentEngine',
'PhabricatorJSONExportFormat' => 'PhabricatorExportFormat',
'PhabricatorJavelinLinter' => 'ArcanistLinter',
'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorJupyterDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorKeyValueDatabaseCache' => 'PhutilKeyValueCache',
'PhabricatorKeyValueSerializingCacheProxy' => 'PhutilKeyValueCacheProxy',
'PhabricatorKeyboardRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorKeyring' => 'Phobject',
'PhabricatorKeyringConfigOptionType' => 'PhabricatorConfigJSONOptionType',
'PhabricatorLDAPAuthProvider' => 'PhabricatorAuthProvider',
'PhabricatorLabelProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorLanguageSettingsPanel' => 'PhabricatorEditEngineSettingsPanel',
'PhabricatorLegalpadApplication' => 'PhabricatorApplication',
'PhabricatorLegalpadDocumentPHIDType' => 'PhabricatorPHIDType',
'PhabricatorLegalpadSignaturePolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorLibraryTestCase' => 'PhutilLibraryTestCase',
'PhabricatorLinkProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorLipsumArtist' => 'Phobject',
'PhabricatorLipsumContentSource' => 'PhabricatorContentSource',
'PhabricatorLipsumGenerateWorkflow' => 'PhabricatorLipsumManagementWorkflow',
'PhabricatorLipsumManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorLipsumMondrianArtist' => 'PhabricatorLipsumArtist',
'PhabricatorLiskDAO' => 'LiskDAO',
'PhabricatorLiskExportEngineExtension' => 'PhabricatorExportEngineExtension',
'PhabricatorLiskFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
'PhabricatorLiskSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'PhabricatorLiskSerializer' => 'Phobject',
'PhabricatorListExportField' => 'PhabricatorExportField',
'PhabricatorLocalDiskFileStorageEngine' => 'PhabricatorFileStorageEngine',
'PhabricatorLocalTimeTestCase' => 'PhabricatorTestCase',
'PhabricatorLocaleScopeGuard' => 'Phobject',
'PhabricatorLocaleScopeGuardTestCase' => 'PhabricatorTestCase',
'PhabricatorLockLogManagementWorkflow' => 'PhabricatorLockManagementWorkflow',
'PhabricatorLockManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorLogTriggerAction' => 'PhabricatorTriggerAction',
'PhabricatorLoginFailureUserLogType' => 'PhabricatorUserLogType',
'PhabricatorLoginUserLogType' => 'PhabricatorUserLogType',
'PhabricatorLogoutController' => 'PhabricatorAuthController',
'PhabricatorLogoutUserLogType' => 'PhabricatorUserLogType',
'PhabricatorLunarPhasePolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorMacroApplication' => 'PhabricatorApplication',
'PhabricatorMacroAudioBehaviorTransaction' => 'PhabricatorMacroTransactionType',
'PhabricatorMacroAudioController' => 'PhabricatorMacroController',
'PhabricatorMacroAudioTransaction' => 'PhabricatorMacroTransactionType',
'PhabricatorMacroController' => 'PhabricatorController',
'PhabricatorMacroDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorMacroDisableController' => 'PhabricatorMacroController',
'PhabricatorMacroDisabledTransaction' => 'PhabricatorMacroTransactionType',
'PhabricatorMacroEditController' => 'PhameBlogController',
'PhabricatorMacroEditEngine' => 'PhabricatorEditEngine',
'PhabricatorMacroEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorMacroFileTransaction' => 'PhabricatorMacroTransactionType',
'PhabricatorMacroListController' => 'PhabricatorMacroController',
'PhabricatorMacroMacroPHIDType' => 'PhabricatorPHIDType',
'PhabricatorMacroMailReceiver' => 'PhabricatorObjectMailReceiver',
'PhabricatorMacroManageCapability' => 'PhabricatorPolicyCapability',
'PhabricatorMacroMemeController' => 'PhabricatorMacroController',
'PhabricatorMacroMemeDialogController' => 'PhabricatorMacroController',
'PhabricatorMacroNameTransaction' => 'PhabricatorMacroTransactionType',
'PhabricatorMacroQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorMacroReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PhabricatorMacroSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorMacroTestCase' => 'PhabricatorTestCase',
'PhabricatorMacroTransaction' => 'PhabricatorModularTransaction',
'PhabricatorMacroTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PhabricatorMacroTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorMacroTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorMacroViewController' => 'PhabricatorMacroController',
'PhabricatorMailAdapter' => 'Phobject',
'PhabricatorMailAdapterTestCase' => 'PhabricatorTestCase',
'PhabricatorMailAmazonSESAdapter' => 'PhabricatorMailAdapter',
'PhabricatorMailAmazonSNSAdapter' => 'PhabricatorMailAdapter',
'PhabricatorMailAttachment' => 'Phobject',
'PhabricatorMailConfigTestCase' => 'PhabricatorTestCase',
'PhabricatorMailEmailEngine' => 'PhabricatorMailMessageEngine',
'PhabricatorMailEmailHeraldField' => 'HeraldField',
'PhabricatorMailEmailHeraldFieldGroup' => 'HeraldFieldGroup',
'PhabricatorMailEmailMessage' => 'PhabricatorMailExternalMessage',
'PhabricatorMailEmailSubjectHeraldField' => 'PhabricatorMailEmailHeraldField',
'PhabricatorMailEngineExtension' => 'Phobject',
'PhabricatorMailExternalMessage' => 'Phobject',
'PhabricatorMailHeader' => 'Phobject',
'PhabricatorMailMailgunAdapter' => 'PhabricatorMailAdapter',
'PhabricatorMailManagementListInboundWorkflow' => 'PhabricatorMailManagementWorkflow',
'PhabricatorMailManagementListOutboundWorkflow' => 'PhabricatorMailManagementWorkflow',
'PhabricatorMailManagementReceiveTestWorkflow' => 'PhabricatorMailManagementWorkflow',
'PhabricatorMailManagementResendWorkflow' => 'PhabricatorMailManagementWorkflow',
'PhabricatorMailManagementSendTestWorkflow' => 'PhabricatorMailManagementWorkflow',
'PhabricatorMailManagementShowInboundWorkflow' => 'PhabricatorMailManagementWorkflow',
'PhabricatorMailManagementShowOutboundWorkflow' => 'PhabricatorMailManagementWorkflow',
'PhabricatorMailManagementUnverifyWorkflow' => 'PhabricatorMailManagementWorkflow',
'PhabricatorMailManagementVolumeWorkflow' => 'PhabricatorMailManagementWorkflow',
'PhabricatorMailManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorMailMessageEngine' => 'Phobject',
'PhabricatorMailMustEncryptHeraldAction' => 'HeraldAction',
'PhabricatorMailOutboundMailHeraldAdapter' => 'HeraldAdapter',
'PhabricatorMailOutboundRoutingHeraldAction' => 'HeraldAction',
'PhabricatorMailOutboundRoutingSelfEmailHeraldAction' => 'PhabricatorMailOutboundRoutingHeraldAction',
'PhabricatorMailOutboundRoutingSelfNotificationHeraldAction' => 'PhabricatorMailOutboundRoutingHeraldAction',
'PhabricatorMailOutboundStatus' => 'Phobject',
'PhabricatorMailPostmarkAdapter' => 'PhabricatorMailAdapter',
'PhabricatorMailPropertiesDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
'PhabricatorMailReceiver' => 'Phobject',
'PhabricatorMailReceiverTestCase' => 'PhabricatorTestCase',
'PhabricatorMailReplyHandler' => 'Phobject',
'PhabricatorMailRoutingRule' => 'Phobject',
'PhabricatorMailSMSEngine' => 'PhabricatorMailMessageEngine',
'PhabricatorMailSMSMessage' => 'PhabricatorMailExternalMessage',
'PhabricatorMailSMTPAdapter' => 'PhabricatorMailAdapter',
'PhabricatorMailSendGridAdapter' => 'PhabricatorMailAdapter',
'PhabricatorMailSendmailAdapter' => 'PhabricatorMailAdapter',
'PhabricatorMailSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorMailStamp' => 'Phobject',
'PhabricatorMailTarget' => 'Phobject',
'PhabricatorMailTestAdapter' => 'PhabricatorMailAdapter',
'PhabricatorMailTwilioAdapter' => 'PhabricatorMailAdapter',
'PhabricatorMailUtil' => 'Phobject',
'PhabricatorMainMenuBarExtension' => 'Phobject',
'PhabricatorMainMenuSearchView' => 'AphrontView',
'PhabricatorMainMenuView' => 'AphrontView',
'PhabricatorManageProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorManagementWorkflow' => 'PhutilArgumentWorkflow',
'PhabricatorManiphestApplication' => 'PhabricatorApplication',
'PhabricatorManiphestConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorManiphestTaskFactEngine' => 'PhabricatorTransactionFactEngine',
'PhabricatorManiphestTaskTestDataGenerator' => 'PhabricatorTestDataGenerator',
'PhabricatorManualActivitySetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorMarkupCache' => 'PhabricatorCacheDAO',
'PhabricatorMarkupEngine' => 'Phobject',
'PhabricatorMarkupEngineTestCase' => 'PhabricatorTestCase',
'PhabricatorMarkupOneOff' => array(
'Phobject',
'PhabricatorMarkupInterface',
),
'PhabricatorMarkupPreviewController' => 'PhabricatorController',
'PhabricatorMaxChartFunction' => 'PhabricatorPureChartFunction',
'PhabricatorMemeEngine' => 'Phobject',
'PhabricatorMemeRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorMentionRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorMercurialGraphStream' => 'PhabricatorRepositoryGraphStream',
'PhabricatorMetaMTAActor' => 'Phobject',
'PhabricatorMetaMTAActorQuery' => 'PhabricatorQuery',
'PhabricatorMetaMTAApplication' => 'PhabricatorApplication',
'PhabricatorMetaMTAApplicationEmail' => array(
'PhabricatorMetaMTADAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorDestructibleInterface',
'PhabricatorSpacesInterface',
),
'PhabricatorMetaMTAApplicationEmailDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorMetaMTAApplicationEmailEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorMetaMTAApplicationEmailHeraldField' => 'HeraldField',
'PhabricatorMetaMTAApplicationEmailPHIDType' => 'PhabricatorPHIDType',
'PhabricatorMetaMTAApplicationEmailPanel' => 'PhabricatorApplicationConfigurationPanel',
'PhabricatorMetaMTAApplicationEmailQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorMetaMTAApplicationEmailTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorMetaMTAApplicationEmailTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorMetaMTAConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorMetaMTAController' => 'PhabricatorController',
'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO',
'PhabricatorMetaMTAEmailBodyParser' => 'Phobject',
'PhabricatorMetaMTAEmailBodyParserTestCase' => 'PhabricatorTestCase',
'PhabricatorMetaMTAEmailHeraldAction' => 'HeraldAction',
'PhabricatorMetaMTAEmailOthersHeraldAction' => 'PhabricatorMetaMTAEmailHeraldAction',
'PhabricatorMetaMTAEmailSelfHeraldAction' => 'PhabricatorMetaMTAEmailHeraldAction',
'PhabricatorMetaMTAErrorMailAction' => 'PhabricatorSystemAction',
'PhabricatorMetaMTAMail' => array(
'PhabricatorMetaMTADAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorMetaMTAMailBody' => 'Phobject',
'PhabricatorMetaMTAMailBodyTestCase' => 'PhabricatorTestCase',
'PhabricatorMetaMTAMailHasRecipientEdgeType' => 'PhabricatorEdgeType',
'PhabricatorMetaMTAMailListController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTAMailPHIDType' => 'PhabricatorPHIDType',
'PhabricatorMetaMTAMailProperties' => array(
'PhabricatorMetaMTADAO',
'PhabricatorPolicyInterface',
),
'PhabricatorMetaMTAMailPropertiesQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorMetaMTAMailQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorMetaMTAMailSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorMetaMTAMailSection' => 'Phobject',
'PhabricatorMetaMTAMailTestCase' => 'PhabricatorTestCase',
'PhabricatorMetaMTAMailViewController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTAMailableDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorMetaMTAMailableFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorMetaMTAMailgunReceiveController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTAMemberQuery' => 'PhabricatorQuery',
'PhabricatorMetaMTAPermanentFailureException' => 'Exception',
'PhabricatorMetaMTAPostmarkReceiveController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTAReceivedMail' => 'PhabricatorMetaMTADAO',
'PhabricatorMetaMTAReceivedMailProcessingException' => 'Exception',
'PhabricatorMetaMTAReceivedMailTestCase' => 'PhabricatorTestCase',
'PhabricatorMetaMTASchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorMetaMTASendGridReceiveController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTAWorker' => 'PhabricatorWorker',
'PhabricatorMetronome' => 'Phobject',
'PhabricatorMetronomeTestCase' => 'PhabricatorTestCase',
'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock',
'PhabricatorMinChartFunction' => 'PhabricatorPureChartFunction',
'PhabricatorModularTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorModularTransactionType' => 'Phobject',
'PhabricatorMonogramDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension',
'PhabricatorMonospacedFontSetting' => 'PhabricatorStringSetting',
'PhabricatorMonospacedTextareasSetting' => 'PhabricatorSelectSetting',
'PhabricatorMotivatorProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorMultiColumnUIExample' => 'PhabricatorUIExample',
'PhabricatorMultiFactorSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorMultimeterApplication' => 'PhabricatorApplication',
'PhabricatorMustVerifyEmailController' => 'PhabricatorAuthController',
'PhabricatorMutedByEdgeType' => 'PhabricatorEdgeType',
'PhabricatorMutedEdgeType' => 'PhabricatorEdgeType',
'PhabricatorMySQLConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorMySQLFileStorageEngine' => 'PhabricatorFileStorageEngine',
'PhabricatorMySQLSearchHost' => 'PhabricatorSearchHost',
'PhabricatorMySQLSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorNamedQuery' => array(
'PhabricatorSearchDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorNamedQueryConfig' => array(
'PhabricatorSearchDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorNamedQueryConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorNamedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorNavigationRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorNeverTriggerClock' => 'PhabricatorTriggerClock',
'PhabricatorNgramsIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
'PhabricatorNgramsInterface' => 'PhabricatorIndexableInterface',
'PhabricatorNotificationBuilder' => 'Phobject',
'PhabricatorNotificationClearController' => 'PhabricatorNotificationController',
'PhabricatorNotificationClient' => 'Phobject',
'PhabricatorNotificationConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorNotificationController' => 'PhabricatorController',
'PhabricatorNotificationDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
'PhabricatorNotificationIndividualController' => 'PhabricatorNotificationController',
'PhabricatorNotificationListController' => 'PhabricatorNotificationController',
'PhabricatorNotificationPanelController' => 'PhabricatorNotificationController',
'PhabricatorNotificationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorNotificationSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorNotificationServerRef' => 'Phobject',
'PhabricatorNotificationServersConfigType' => 'PhabricatorJSONConfigType',
'PhabricatorNotificationStatusView' => 'AphrontTagView',
'PhabricatorNotificationTestController' => 'PhabricatorNotificationController',
'PhabricatorNotificationUIExample' => 'PhabricatorUIExample',
'PhabricatorNotificationsApplication' => 'PhabricatorApplication',
'PhabricatorNotificationsSetting' => 'PhabricatorInternalSetting',
'PhabricatorNotificationsSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorNuanceApplication' => 'PhabricatorApplication',
'PhabricatorOAuth1AuthProvider' => 'PhabricatorOAuthAuthProvider',
'PhabricatorOAuth1SecretTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType',
'PhabricatorOAuth2AuthProvider' => 'PhabricatorOAuthAuthProvider',
'PhabricatorOAuthAuthProvider' => 'PhabricatorAuthProvider',
'PhabricatorOAuthClientAuthorization' => array(
'PhabricatorOAuthServerDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorOAuthClientAuthorizationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorOAuthClientController' => 'PhabricatorOAuthServerController',
'PhabricatorOAuthClientDisableController' => 'PhabricatorOAuthClientController',
'PhabricatorOAuthClientEditController' => 'PhabricatorOAuthClientController',
'PhabricatorOAuthClientListController' => 'PhabricatorOAuthClientController',
'PhabricatorOAuthClientSecretController' => 'PhabricatorOAuthClientController',
'PhabricatorOAuthClientTestController' => 'PhabricatorOAuthClientController',
'PhabricatorOAuthClientViewController' => 'PhabricatorOAuthClientController',
'PhabricatorOAuthResponse' => 'AphrontResponse',
'PhabricatorOAuthServer' => 'Phobject',
'PhabricatorOAuthServerAccessToken' => 'PhabricatorOAuthServerDAO',
'PhabricatorOAuthServerApplication' => 'PhabricatorApplication',
'PhabricatorOAuthServerAuthController' => 'PhabricatorOAuthServerController',
'PhabricatorOAuthServerAuthorizationCode' => 'PhabricatorOAuthServerDAO',
'PhabricatorOAuthServerAuthorizationsSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorOAuthServerClient' => array(
'PhabricatorOAuthServerDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorOAuthServerClientAuthorizationPHIDType' => 'PhabricatorPHIDType',
'PhabricatorOAuthServerClientPHIDType' => 'PhabricatorPHIDType',
'PhabricatorOAuthServerClientQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorOAuthServerClientSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorOAuthServerController' => 'PhabricatorController',
'PhabricatorOAuthServerCreateClientsCapability' => 'PhabricatorPolicyCapability',
'PhabricatorOAuthServerDAO' => 'PhabricatorLiskDAO',
'PhabricatorOAuthServerEditEngine' => 'PhabricatorEditEngine',
'PhabricatorOAuthServerEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorOAuthServerSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorOAuthServerScope' => 'Phobject',
'PhabricatorOAuthServerTestCase' => 'PhabricatorTestCase',
'PhabricatorOAuthServerTokenController' => 'PhabricatorOAuthServerController',
'PhabricatorOAuthServerTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorOAuthServerTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorObjectGraph' => 'AbstractDirectedGraph',
'PhabricatorObjectHandle' => array(
'Phobject',
'PhabricatorPolicyInterface',
),
'PhabricatorObjectHasAsanaSubtaskEdgeType' => 'PhabricatorEdgeType',
'PhabricatorObjectHasAsanaTaskEdgeType' => 'PhabricatorEdgeType',
'PhabricatorObjectHasContributorEdgeType' => 'PhabricatorEdgeType',
'PhabricatorObjectHasDraftEdgeType' => 'PhabricatorEdgeType',
- 'PhabricatorObjectHasFileEdgeType' => 'PhabricatorEdgeType',
'PhabricatorObjectHasJiraIssueEdgeType' => 'PhabricatorEdgeType',
'PhabricatorObjectHasSubscriberEdgeType' => 'PhabricatorEdgeType',
'PhabricatorObjectHasUnsubscriberEdgeType' => 'PhabricatorEdgeType',
'PhabricatorObjectHasWatcherEdgeType' => 'PhabricatorEdgeType',
'PhabricatorObjectListQuery' => 'Phobject',
'PhabricatorObjectListQueryTestCase' => 'PhabricatorTestCase',
'PhabricatorObjectMailReceiver' => 'PhabricatorMailReceiver',
'PhabricatorObjectMailReceiverTestCase' => 'PhabricatorTestCase',
'PhabricatorObjectMentionedByObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorObjectMentionsObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorObjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorObjectRelationship' => 'Phobject',
'PhabricatorObjectRelationshipList' => 'Phobject',
'PhabricatorObjectRelationshipSource' => 'Phobject',
'PhabricatorObjectRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorObjectSelectorDialog' => 'Phobject',
'PhabricatorObjectStatus' => 'Phobject',
'PhabricatorObjectUsesDashboardPanelEdgeType' => 'PhabricatorEdgeType',
'PhabricatorOffsetPagedQuery' => 'PhabricatorQuery',
'PhabricatorOldWorldContentSource' => 'PhabricatorContentSource',
'PhabricatorOlderInlinesSetting' => 'PhabricatorSelectSetting',
'PhabricatorOneTimeTriggerClock' => 'PhabricatorTriggerClock',
'PhabricatorOpcodeCacheSpec' => 'PhabricatorCacheSpec',
'PhabricatorOptionExportField' => 'PhabricatorExportField',
'PhabricatorOptionGroupSetting' => 'PhabricatorSetting',
'PhabricatorOwnerPathQuery' => 'Phobject',
'PhabricatorOwnersApplication' => 'PhabricatorApplication',
'PhabricatorOwnersArchiveController' => 'PhabricatorOwnersController',
'PhabricatorOwnersAuditRule' => 'Phobject',
'PhabricatorOwnersConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorOwnersConfiguredCustomField' => array(
'PhabricatorOwnersCustomField',
'PhabricatorStandardCustomFieldInterface',
),
'PhabricatorOwnersController' => 'PhabricatorController',
'PhabricatorOwnersCustomField' => 'PhabricatorCustomField',
'PhabricatorOwnersCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
'PhabricatorOwnersCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
'PhabricatorOwnersCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage',
'PhabricatorOwnersDAO' => 'PhabricatorLiskDAO',
'PhabricatorOwnersDefaultEditCapability' => 'PhabricatorPolicyCapability',
'PhabricatorOwnersDefaultViewCapability' => 'PhabricatorPolicyCapability',
'PhabricatorOwnersDetailController' => 'PhabricatorOwnersController',
'PhabricatorOwnersEditController' => 'PhabricatorOwnersController',
'PhabricatorOwnersHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'PhabricatorOwnersListController' => 'PhabricatorOwnersController',
'PhabricatorOwnersOwner' => 'PhabricatorOwnersDAO',
'PhabricatorOwnersPackage' => array(
'PhabricatorOwnersDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorCustomFieldInterface',
'PhabricatorDestructibleInterface',
'PhabricatorConduitResultInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
'PhabricatorNgramsInterface',
),
'PhabricatorOwnersPackageAuditingTransaction' => 'PhabricatorOwnersPackageTransactionType',
'PhabricatorOwnersPackageAuthorityTransaction' => 'PhabricatorOwnersPackageTransactionType',
'PhabricatorOwnersPackageAutoreviewTransaction' => 'PhabricatorOwnersPackageTransactionType',
'PhabricatorOwnersPackageContextFreeGrammar' => 'PhutilContextFreeGrammar',
'PhabricatorOwnersPackageDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorOwnersPackageDescriptionTransaction' => 'PhabricatorOwnersPackageTransactionType',
'PhabricatorOwnersPackageDominionTransaction' => 'PhabricatorOwnersPackageTransactionType',
'PhabricatorOwnersPackageEditEngine' => 'PhabricatorEditEngine',
'PhabricatorOwnersPackageFerretEngine' => 'PhabricatorFerretEngine',
'PhabricatorOwnersPackageFulltextEngine' => 'PhabricatorFulltextEngine',
'PhabricatorOwnersPackageFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorOwnersPackageIgnoredTransaction' => 'PhabricatorOwnersPackageTransactionType',
'PhabricatorOwnersPackageNameNgrams' => 'PhabricatorSearchNgrams',
'PhabricatorOwnersPackageNameTransaction' => 'PhabricatorOwnersPackageTransactionType',
'PhabricatorOwnersPackageOwnerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorOwnersPackageOwnersTransaction' => 'PhabricatorOwnersPackageTransactionType',
'PhabricatorOwnersPackagePHIDType' => 'PhabricatorPHIDType',
'PhabricatorOwnersPackagePathsTransaction' => 'PhabricatorOwnersPackageTransactionType',
'PhabricatorOwnersPackagePrimaryTransaction' => 'PhabricatorOwnersPackageTransactionType',
'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorOwnersPackageRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'PhabricatorOwnersPackageSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorOwnersPackageStatusTransaction' => 'PhabricatorOwnersPackageTransactionType',
'PhabricatorOwnersPackageTestCase' => 'PhabricatorTestCase',
'PhabricatorOwnersPackageTestDataGenerator' => 'PhabricatorTestDataGenerator',
'PhabricatorOwnersPackageTransaction' => 'PhabricatorModularTransaction',
'PhabricatorOwnersPackageTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorOwnersPackageTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorOwnersPackageTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorOwnersPath' => 'PhabricatorOwnersDAO',
'PhabricatorOwnersPathContextFreeGrammar' => 'PhutilContextFreeGrammar',
'PhabricatorOwnersPathsController' => 'PhabricatorOwnersController',
'PhabricatorOwnersPathsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorOwnersSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorOwnersSearchField' => 'PhabricatorSearchTokenizerField',
'PhabricatorPDFCatalogObject' => 'PhabricatorPDFObject',
'PhabricatorPDFContentsObject' => 'PhabricatorPDFObject',
'PhabricatorPDFDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorPDFFontObject' => 'PhabricatorPDFObject',
'PhabricatorPDFFragment' => 'Phobject',
'PhabricatorPDFFragmentOffset' => 'Phobject',
'PhabricatorPDFGenerator' => 'Phobject',
'PhabricatorPDFHeadFragment' => 'PhabricatorPDFFragment',
'PhabricatorPDFInfoObject' => 'PhabricatorPDFObject',
'PhabricatorPDFIterator' => array(
'Phobject',
'Iterator',
),
'PhabricatorPDFObject' => 'PhabricatorPDFFragment',
'PhabricatorPDFPageObject' => 'PhabricatorPDFObject',
'PhabricatorPDFPagesObject' => 'PhabricatorPDFObject',
'PhabricatorPDFResourcesObject' => 'PhabricatorPDFObject',
'PhabricatorPDFTailFragment' => 'PhabricatorPDFFragment',
'PhabricatorPHDConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorPHID' => 'Phobject',
'PhabricatorPHIDConstants' => 'Phobject',
'PhabricatorPHIDExportField' => 'PhabricatorExportField',
'PhabricatorPHIDListEditField' => 'PhabricatorEditField',
'PhabricatorPHIDListEditType' => 'PhabricatorEditType',
'PhabricatorPHIDListExportField' => 'PhabricatorListExportField',
'PhabricatorPHIDMailStamp' => 'PhabricatorMailStamp',
'PhabricatorPHIDResolver' => 'Phobject',
'PhabricatorPHIDType' => 'Phobject',
'PhabricatorPHIDTypeTestCase' => 'PhutilTestCase',
'PhabricatorPHIDsSearchField' => 'PhabricatorSearchField',
'PhabricatorPHPASTApplication' => 'PhabricatorApplication',
'PhabricatorPHPConfigSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorPHPPreflightSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorPackagesApplication' => 'PhabricatorApplication',
'PhabricatorPackagesController' => 'PhabricatorController',
'PhabricatorPackagesCreatePublisherCapability' => 'PhabricatorPolicyCapability',
'PhabricatorPackagesDAO' => 'PhabricatorLiskDAO',
'PhabricatorPackagesEditEngine' => 'PhabricatorEditEngine',
'PhabricatorPackagesEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorPackagesNgrams' => 'PhabricatorSearchNgrams',
'PhabricatorPackagesPackage' => array(
'PhabricatorPackagesDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorDestructibleInterface',
'PhabricatorSubscribableInterface',
'PhabricatorProjectInterface',
'PhabricatorConduitResultInterface',
'PhabricatorNgramsInterface',
),
'PhabricatorPackagesPackageController' => 'PhabricatorPackagesController',
'PhabricatorPackagesPackageDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorPackagesPackageDefaultEditCapability' => 'PhabricatorPolicyCapability',
'PhabricatorPackagesPackageDefaultViewCapability' => 'PhabricatorPolicyCapability',
'PhabricatorPackagesPackageEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PhabricatorPackagesPackageEditController' => 'PhabricatorPackagesPackageController',
'PhabricatorPackagesPackageEditEngine' => 'PhabricatorPackagesEditEngine',
'PhabricatorPackagesPackageEditor' => 'PhabricatorPackagesEditor',
'PhabricatorPackagesPackageKeyTransaction' => 'PhabricatorPackagesPackageTransactionType',
'PhabricatorPackagesPackageListController' => 'PhabricatorPackagesPackageController',
'PhabricatorPackagesPackageListView' => 'PhabricatorPackagesView',
'PhabricatorPackagesPackageNameNgrams' => 'PhabricatorPackagesNgrams',
'PhabricatorPackagesPackageNameTransaction' => 'PhabricatorPackagesPackageTransactionType',
'PhabricatorPackagesPackagePHIDType' => 'PhabricatorPHIDType',
'PhabricatorPackagesPackagePublisherTransaction' => 'PhabricatorPackagesPackageTransactionType',
'PhabricatorPackagesPackageQuery' => 'PhabricatorPackagesQuery',
'PhabricatorPackagesPackageSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhabricatorPackagesPackageSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorPackagesPackageTransaction' => 'PhabricatorModularTransaction',
'PhabricatorPackagesPackageTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorPackagesPackageTransactionType' => 'PhabricatorPackagesTransactionType',
'PhabricatorPackagesPackageViewController' => 'PhabricatorPackagesPackageController',
'PhabricatorPackagesPublisher' => array(
'PhabricatorPackagesDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorDestructibleInterface',
'PhabricatorSubscribableInterface',
'PhabricatorProjectInterface',
'PhabricatorConduitResultInterface',
'PhabricatorNgramsInterface',
),
'PhabricatorPackagesPublisherController' => 'PhabricatorPackagesController',
'PhabricatorPackagesPublisherDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorPackagesPublisherDefaultEditCapability' => 'PhabricatorPolicyCapability',
'PhabricatorPackagesPublisherEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PhabricatorPackagesPublisherEditController' => 'PhabricatorPackagesPublisherController',
'PhabricatorPackagesPublisherEditEngine' => 'PhabricatorPackagesEditEngine',
'PhabricatorPackagesPublisherEditor' => 'PhabricatorPackagesEditor',
'PhabricatorPackagesPublisherKeyTransaction' => 'PhabricatorPackagesPublisherTransactionType',
'PhabricatorPackagesPublisherListController' => 'PhabricatorPackagesPublisherController',
'PhabricatorPackagesPublisherListView' => 'PhabricatorPackagesView',
'PhabricatorPackagesPublisherNameNgrams' => 'PhabricatorPackagesNgrams',
'PhabricatorPackagesPublisherNameTransaction' => 'PhabricatorPackagesPublisherTransactionType',
'PhabricatorPackagesPublisherPHIDType' => 'PhabricatorPHIDType',
'PhabricatorPackagesPublisherQuery' => 'PhabricatorPackagesQuery',
'PhabricatorPackagesPublisherSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhabricatorPackagesPublisherSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorPackagesPublisherTransaction' => 'PhabricatorModularTransaction',
'PhabricatorPackagesPublisherTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorPackagesPublisherTransactionType' => 'PhabricatorPackagesTransactionType',
'PhabricatorPackagesPublisherViewController' => 'PhabricatorPackagesPublisherController',
'PhabricatorPackagesQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorPackagesSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorPackagesTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorPackagesVersion' => array(
'PhabricatorPackagesDAO',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorDestructibleInterface',
'PhabricatorSubscribableInterface',
'PhabricatorProjectInterface',
'PhabricatorConduitResultInterface',
'PhabricatorNgramsInterface',
),
'PhabricatorPackagesVersionController' => 'PhabricatorPackagesController',
'PhabricatorPackagesVersionEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PhabricatorPackagesVersionEditController' => 'PhabricatorPackagesVersionController',
'PhabricatorPackagesVersionEditEngine' => 'PhabricatorPackagesEditEngine',
'PhabricatorPackagesVersionEditor' => 'PhabricatorPackagesEditor',
'PhabricatorPackagesVersionListController' => 'PhabricatorPackagesVersionController',
'PhabricatorPackagesVersionListView' => 'PhabricatorPackagesView',
'PhabricatorPackagesVersionNameNgrams' => 'PhabricatorPackagesNgrams',
'PhabricatorPackagesVersionNameTransaction' => 'PhabricatorPackagesVersionTransactionType',
'PhabricatorPackagesVersionPHIDType' => 'PhabricatorPHIDType',
'PhabricatorPackagesVersionPackageTransaction' => 'PhabricatorPackagesVersionTransactionType',
'PhabricatorPackagesVersionQuery' => 'PhabricatorPackagesQuery',
'PhabricatorPackagesVersionSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhabricatorPackagesVersionSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorPackagesVersionTransaction' => 'PhabricatorModularTransaction',
'PhabricatorPackagesVersionTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorPackagesVersionTransactionType' => 'PhabricatorPackagesTransactionType',
'PhabricatorPackagesVersionViewController' => 'PhabricatorPackagesVersionController',
'PhabricatorPackagesView' => 'AphrontView',
'PhabricatorPagerUIExample' => 'PhabricatorUIExample',
'PhabricatorPartialLoginUserLogType' => 'PhabricatorUserLogType',
'PhabricatorPassphraseApplication' => 'PhabricatorApplication',
'PhabricatorPasswordAuthProvider' => 'PhabricatorAuthProvider',
'PhabricatorPasswordDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
'PhabricatorPasswordHasher' => 'Phobject',
'PhabricatorPasswordHasherTestCase' => 'PhabricatorTestCase',
'PhabricatorPasswordHasherUnavailableException' => 'Exception',
'PhabricatorPasswordSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorPaste' => array(
'PhabricatorPasteDAO',
'PhabricatorSubscribableInterface',
'PhabricatorTokenReceiverInterface',
'PhabricatorFlaggableInterface',
'PhabricatorMentionableInterface',
'PhabricatorPolicyInterface',
'PhabricatorProjectInterface',
'PhabricatorDestructibleInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorSpacesInterface',
'PhabricatorConduitResultInterface',
'PhabricatorFerretInterface',
'PhabricatorFulltextInterface',
),
'PhabricatorPasteApplication' => 'PhabricatorApplication',
'PhabricatorPasteArchiveController' => 'PhabricatorPasteController',
'PhabricatorPasteContentSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorPasteContentTransaction' => 'PhabricatorPasteTransactionType',
'PhabricatorPasteController' => 'PhabricatorController',
'PhabricatorPasteDAO' => 'PhabricatorLiskDAO',
'PhabricatorPasteEditController' => 'PhabricatorPasteController',
'PhabricatorPasteEditEngine' => 'PhabricatorEditEngine',
'PhabricatorPasteEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorPasteFerretEngine' => 'PhabricatorFerretEngine',
'PhabricatorPasteFilenameContextFreeGrammar' => 'PhutilContextFreeGrammar',
'PhabricatorPasteFulltextEngine' => 'PhabricatorFulltextEngine',
'PhabricatorPasteLanguageTransaction' => 'PhabricatorPasteTransactionType',
'PhabricatorPasteListController' => 'PhabricatorPasteController',
'PhabricatorPastePastePHIDType' => 'PhabricatorPHIDType',
'PhabricatorPasteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorPasteRawController' => 'PhabricatorPasteController',
'PhabricatorPasteRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'PhabricatorPasteSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorPasteSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorPasteSnippet' => 'Phobject',
'PhabricatorPasteStatusTransaction' => 'PhabricatorPasteTransactionType',
'PhabricatorPasteTestDataGenerator' => 'PhabricatorTestDataGenerator',
'PhabricatorPasteTitleTransaction' => 'PhabricatorPasteTransactionType',
'PhabricatorPasteTransaction' => 'PhabricatorModularTransaction',
'PhabricatorPasteTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PhabricatorPasteTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorPasteTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorPasteViewController' => 'PhabricatorPasteController',
'PhabricatorPathSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorPeopleAnyOwnerDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorPeopleApplication' => 'PhabricatorApplication',
'PhabricatorPeopleApproveController' => 'PhabricatorPeopleController',
'PhabricatorPeopleAvailabilitySearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorPeopleBadgesProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorPeopleCommitsProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorPeopleController' => 'PhabricatorController',
'PhabricatorPeopleCreateController' => 'PhabricatorPeopleController',
'PhabricatorPeopleCreateGuidanceContext' => 'PhabricatorGuidanceContext',
'PhabricatorPeopleDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorPeopleDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension',
'PhabricatorPeopleDeleteController' => 'PhabricatorPeopleController',
'PhabricatorPeopleDetailsProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorPeopleDisableController' => 'PhabricatorPeopleController',
'PhabricatorPeopleEmailLoginMailEngine' => 'PhabricatorPeopleMailEngine',
'PhabricatorPeopleEmpowerController' => 'PhabricatorPeopleController',
'PhabricatorPeopleExternalIdentifierPHIDType' => 'PhabricatorPHIDType',
'PhabricatorPeopleExternalPHIDType' => 'PhabricatorPHIDType',
'PhabricatorPeopleIconSet' => 'PhabricatorIconSet',
'PhabricatorPeopleInviteController' => 'PhabricatorPeopleController',
'PhabricatorPeopleInviteListController' => 'PhabricatorPeopleInviteController',
'PhabricatorPeopleInviteSendController' => 'PhabricatorPeopleInviteController',
'PhabricatorPeopleListController' => 'PhabricatorPeopleController',
'PhabricatorPeopleLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorPeopleLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorPeopleLogViewController' => 'PhabricatorPeopleController',
'PhabricatorPeopleLogsController' => 'PhabricatorPeopleController',
'PhabricatorPeopleMailEngine' => 'Phobject',
'PhabricatorPeopleMailEngineException' => 'Exception',
'PhabricatorPeopleManageProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorPeopleManagementApproveWorkflow' => 'PhabricatorPeopleManagementWorkflow',
'PhabricatorPeopleManagementEmpowerWorkflow' => 'PhabricatorPeopleManagementWorkflow',
'PhabricatorPeopleManagementEnableWorkflow' => 'PhabricatorPeopleManagementWorkflow',
'PhabricatorPeopleManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorPeopleNewController' => 'PhabricatorPeopleController',
'PhabricatorPeopleNoOwnerDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorPeopleOwnerDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorPeoplePictureProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorPeopleProfileBadgesController' => 'PhabricatorPeopleProfileController',
'PhabricatorPeopleProfileCommitsController' => 'PhabricatorPeopleProfileController',
'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController',
'PhabricatorPeopleProfileEditController' => 'PhabricatorPeopleProfileController',
'PhabricatorPeopleProfileManageController' => 'PhabricatorPeopleProfileController',
'PhabricatorPeopleProfileMenuEngine' => 'PhabricatorProfileMenuEngine',
'PhabricatorPeopleProfilePictureController' => 'PhabricatorPeopleProfileController',
'PhabricatorPeopleProfileRevisionsController' => 'PhabricatorPeopleProfileController',
'PhabricatorPeopleProfileTasksController' => 'PhabricatorPeopleProfileController',
'PhabricatorPeopleProfileViewController' => 'PhabricatorPeopleProfileController',
'PhabricatorPeopleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorPeopleRenameController' => 'PhabricatorPeopleController',
'PhabricatorPeopleRevisionsProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorPeopleSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorPeopleTasksProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorPeopleTestDataGenerator' => 'PhabricatorTestDataGenerator',
'PhabricatorPeopleTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorPeopleUserEmailPHIDType' => 'PhabricatorPHIDType',
'PhabricatorPeopleUserEmailQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorPeopleUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorPeopleUserPHIDType' => 'PhabricatorPHIDType',
'PhabricatorPeopleUsernameMailEngine' => 'PhabricatorPeopleMailEngine',
'PhabricatorPeopleWelcomeController' => 'PhabricatorPeopleController',
'PhabricatorPeopleWelcomeMailEngine' => 'PhabricatorPeopleMailEngine',
'PhabricatorPhabricatorAuthProvider' => 'PhabricatorOAuth2AuthProvider',
'PhabricatorPhameApplication' => 'PhabricatorApplication',
'PhabricatorPhameBlogPHIDType' => 'PhabricatorPHIDType',
'PhabricatorPhamePostPHIDType' => 'PhabricatorPHIDType',
'PhabricatorPhluxApplication' => 'PhabricatorApplication',
'PhabricatorPholioApplication' => 'PhabricatorApplication',
'PhabricatorPholioMockTestDataGenerator' => 'PhabricatorTestDataGenerator',
'PhabricatorPhoneNumber' => 'Phobject',
'PhabricatorPhoneNumberTestCase' => 'PhabricatorTestCase',
'PhabricatorPhortuneApplication' => 'PhabricatorApplication',
'PhabricatorPhortuneContentSource' => 'PhabricatorContentSource',
'PhabricatorPhortuneManagementInvoiceWorkflow' => 'PhabricatorPhortuneManagementWorkflow',
'PhabricatorPhortuneManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorPhortuneTestCase' => 'PhabricatorTestCase',
- 'PhabricatorPhragmentApplication' => 'PhabricatorApplication',
'PhabricatorPhrequentApplication' => 'PhabricatorApplication',
'PhabricatorPhrictionApplication' => 'PhabricatorApplication',
'PhabricatorPhurlApplication' => 'PhabricatorApplication',
'PhabricatorPhurlConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorPhurlController' => 'PhabricatorController',
'PhabricatorPhurlDAO' => 'PhabricatorLiskDAO',
'PhabricatorPhurlLinkRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorPhurlRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'PhabricatorPhurlSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorPhurlShortURLController' => 'PhabricatorPhurlController',
'PhabricatorPhurlShortURLDefaultController' => 'PhabricatorPhurlController',
'PhabricatorPhurlURL' => array(
'PhabricatorPhurlDAO',
'PhabricatorPolicyInterface',
'PhabricatorProjectInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorSubscribableInterface',
'PhabricatorTokenReceiverInterface',
'PhabricatorDestructibleInterface',
'PhabricatorMentionableInterface',
'PhabricatorFlaggableInterface',
'PhabricatorSpacesInterface',
'PhabricatorConduitResultInterface',
'PhabricatorNgramsInterface',
),
'PhabricatorPhurlURLAccessController' => 'PhabricatorPhurlController',
'PhabricatorPhurlURLAliasTransaction' => 'PhabricatorPhurlURLTransactionType',
'PhabricatorPhurlURLCreateCapability' => 'PhabricatorPolicyCapability',
'PhabricatorPhurlURLDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorPhurlURLDescriptionTransaction' => 'PhabricatorPhurlURLTransactionType',
'PhabricatorPhurlURLEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PhabricatorPhurlURLEditController' => 'PhabricatorPhurlController',
'PhabricatorPhurlURLEditEngine' => 'PhabricatorEditEngine',
'PhabricatorPhurlURLEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorPhurlURLListController' => 'PhabricatorPhurlController',
'PhabricatorPhurlURLLongURLTransaction' => 'PhabricatorPhurlURLTransactionType',
'PhabricatorPhurlURLMailReceiver' => 'PhabricatorObjectMailReceiver',
'PhabricatorPhurlURLNameNgrams' => 'PhabricatorSearchNgrams',
'PhabricatorPhurlURLNameTransaction' => 'PhabricatorPhurlURLTransactionType',
'PhabricatorPhurlURLPHIDType' => 'PhabricatorPHIDType',
'PhabricatorPhurlURLQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorPhurlURLReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PhabricatorPhurlURLSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhabricatorPhurlURLSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorPhurlURLTransaction' => 'PhabricatorModularTransaction',
'PhabricatorPhurlURLTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PhabricatorPhurlURLTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorPhurlURLTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorPhurlURLViewController' => 'PhabricatorPhurlController',
'PhabricatorPinnedApplicationsSetting' => 'PhabricatorInternalSetting',
'PhabricatorPirateEnglishTranslation' => 'PhutilTranslation',
'PhabricatorPlatform404Controller' => 'PhabricatorController',
'PhabricatorPlatformSite' => 'PhabricatorSite',
'PhabricatorPointsEditField' => 'PhabricatorEditField',
'PhabricatorPointsFact' => 'PhabricatorFact',
'PhabricatorPolicies' => 'PhabricatorPolicyConstants',
'PhabricatorPolicy' => array(
'PhabricatorPolicyDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorPolicyApplication' => 'PhabricatorApplication',
'PhabricatorPolicyAwareQuery' => 'PhabricatorOffsetPagedQuery',
'PhabricatorPolicyAwareTestQuery' => 'PhabricatorPolicyAwareQuery',
'PhabricatorPolicyCanEditCapability' => 'PhabricatorPolicyCapability',
'PhabricatorPolicyCanInteractCapability' => 'PhabricatorPolicyCapability',
'PhabricatorPolicyCanJoinCapability' => 'PhabricatorPolicyCapability',
'PhabricatorPolicyCanViewCapability' => 'PhabricatorPolicyCapability',
'PhabricatorPolicyCapability' => 'Phobject',
'PhabricatorPolicyCapabilityTestCase' => 'PhabricatorTestCase',
'PhabricatorPolicyCodex' => 'Phobject',
'PhabricatorPolicyCodexRuleDescription' => 'Phobject',
'PhabricatorPolicyConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorPolicyConstants' => 'Phobject',
'PhabricatorPolicyController' => 'PhabricatorController',
'PhabricatorPolicyDAO' => 'PhabricatorLiskDAO',
'PhabricatorPolicyDataTestCase' => 'PhabricatorTestCase',
'PhabricatorPolicyEditController' => 'PhabricatorPolicyController',
'PhabricatorPolicyEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorPolicyEditField' => 'PhabricatorEditField',
'PhabricatorPolicyException' => 'Exception',
'PhabricatorPolicyExplainController' => 'PhabricatorPolicyController',
'PhabricatorPolicyFavoritesSetting' => 'PhabricatorInternalSetting',
'PhabricatorPolicyFilter' => 'Phobject',
'PhabricatorPolicyFilterSet' => 'Phobject',
'PhabricatorPolicyInterface' => 'PhabricatorPHIDInterface',
'PhabricatorPolicyManagementShowWorkflow' => 'PhabricatorPolicyManagementWorkflow',
'PhabricatorPolicyManagementUnlockWorkflow' => 'PhabricatorPolicyManagementWorkflow',
'PhabricatorPolicyManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorPolicyPHIDTypePolicy' => 'PhabricatorPHIDType',
'PhabricatorPolicyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorPolicyRef' => 'Phobject',
'PhabricatorPolicyRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorPolicyRule' => 'Phobject',
'PhabricatorPolicyRulesView' => 'AphrontView',
'PhabricatorPolicySearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'PhabricatorPolicyTestCase' => 'PhabricatorTestCase',
'PhabricatorPolicyTestObject' => array(
'Phobject',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
),
'PhabricatorPolicyType' => 'PhabricatorPolicyConstants',
'PhabricatorPonderApplication' => 'PhabricatorApplication',
'PhabricatorPreambleTestCase' => 'PhabricatorTestCase',
'PhabricatorPrimaryEmailUserLogType' => 'PhabricatorUserLogType',
'PhabricatorProfileMenuEditEngine' => 'PhabricatorEditEngine',
'PhabricatorProfileMenuEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorProfileMenuEngine' => 'Phobject',
'PhabricatorProfileMenuItem' => 'Phobject',
'PhabricatorProfileMenuItemAffectsObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorProfileMenuItemConfiguration' => array(
'PhabricatorSearchDAO',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorIndexableInterface',
),
'PhabricatorProfileMenuItemConfigurationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorProfileMenuItemConfigurationTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorProfileMenuItemConfigurationTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorProfileMenuItemIconSet' => 'PhabricatorIconSet',
'PhabricatorProfileMenuItemIndexEngineExtension' => 'PhabricatorEdgeIndexEngineExtension',
'PhabricatorProfileMenuItemPHIDType' => 'PhabricatorPHIDType',
'PhabricatorProfileMenuItemView' => 'Phobject',
'PhabricatorProfileMenuItemViewList' => 'Phobject',
'PhabricatorProject' => array(
'PhabricatorProjectDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorFlaggableInterface',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
'PhabricatorCustomFieldInterface',
'PhabricatorDestructibleInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
'PhabricatorConduitResultInterface',
'PhabricatorColumnProxyInterface',
'PhabricatorSpacesInterface',
'PhabricatorEditEngineSubtypeInterface',
'PhabricatorWorkboardInterface',
),
'PhabricatorProjectActivityChartEngine' => 'PhabricatorChartEngine',
'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction',
'PhabricatorProjectApplication' => 'PhabricatorApplication',
'PhabricatorProjectArchiveController' => 'PhabricatorProjectController',
'PhabricatorProjectBoardBackgroundController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectBoardController' => 'PhabricatorProjectController',
'PhabricatorProjectBoardDefaultController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectBoardDisableController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectBoardFilterController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectBoardImportController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectBoardManageController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectBoardReloadController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectBoardReorderController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectBuiltinsExample' => 'PhabricatorUIExample',
'PhabricatorProjectBurndownChartEngine' => 'PhabricatorChartEngine',
'PhabricatorProjectCardView' => 'AphrontTagView',
'PhabricatorProjectColorTransaction' => 'PhabricatorProjectTransactionType',
'PhabricatorProjectColorsConfigType' => 'PhabricatorJSONConfigType',
'PhabricatorProjectColumn' => array(
'PhabricatorProjectDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorExtendedPolicyInterface',
'PhabricatorConduitResultInterface',
),
'PhabricatorProjectColumnAuthorOrder' => 'PhabricatorProjectColumnOrder',
'PhabricatorProjectColumnBulkEditController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectColumnBulkMoveController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectColumnCreatedOrder' => 'PhabricatorProjectColumnOrder',
'PhabricatorProjectColumnDetailController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectColumnEditController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectColumnHeader' => 'Phobject',
'PhabricatorProjectColumnHideController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectColumnLimitTransaction' => 'PhabricatorProjectColumnTransactionType',
'PhabricatorProjectColumnNameTransaction' => 'PhabricatorProjectColumnTransactionType',
'PhabricatorProjectColumnNaturalOrder' => 'PhabricatorProjectColumnOrder',
'PhabricatorProjectColumnOrder' => 'Phobject',
'PhabricatorProjectColumnOwnerOrder' => 'PhabricatorProjectColumnOrder',
'PhabricatorProjectColumnPHIDType' => 'PhabricatorPHIDType',
'PhabricatorProjectColumnPointsOrder' => 'PhabricatorProjectColumnOrder',
'PhabricatorProjectColumnPosition' => array(
'PhabricatorProjectDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorProjectColumnPositionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorProjectColumnPriorityOrder' => 'PhabricatorProjectColumnOrder',
'PhabricatorProjectColumnQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorProjectColumnRemoveTriggerController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectColumnSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorProjectColumnStatusOrder' => 'PhabricatorProjectColumnOrder',
'PhabricatorProjectColumnStatusTransaction' => 'PhabricatorProjectColumnTransactionType',
'PhabricatorProjectColumnTitleOrder' => 'PhabricatorProjectColumnOrder',
'PhabricatorProjectColumnTransaction' => 'PhabricatorModularTransaction',
'PhabricatorProjectColumnTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorProjectColumnTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorProjectColumnTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorProjectColumnTriggerTransaction' => 'PhabricatorProjectColumnTransactionType',
'PhabricatorProjectColumnViewQueryController' => 'PhabricatorProjectBoardController',
'PhabricatorProjectConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorProjectConfiguredCustomField' => array(
'PhabricatorProjectStandardCustomField',
'PhabricatorStandardCustomFieldInterface',
),
'PhabricatorProjectController' => 'PhabricatorController',
'PhabricatorProjectCoreTestCase' => 'PhabricatorTestCase',
'PhabricatorProjectCoverController' => 'PhabricatorProjectController',
'PhabricatorProjectCustomField' => 'PhabricatorCustomField',
'PhabricatorProjectCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
'PhabricatorProjectCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
'PhabricatorProjectCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage',
'PhabricatorProjectDAO' => 'PhabricatorLiskDAO',
'PhabricatorProjectDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField',
'PhabricatorProjectDetailsProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorProjectDropEffect' => 'Phobject',
'PhabricatorProjectEditController' => 'PhabricatorProjectController',
'PhabricatorProjectEditEngine' => 'PhabricatorEditEngine',
'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController',
'PhabricatorProjectFerretEngine' => 'PhabricatorFerretEngine',
'PhabricatorProjectFilterTransaction' => 'PhabricatorProjectTransactionType',
'PhabricatorProjectFulltextEngine' => 'PhabricatorFulltextEngine',
'PhabricatorProjectHeraldAction' => 'HeraldAction',
'PhabricatorProjectHeraldAdapter' => 'HeraldAdapter',
'PhabricatorProjectHeraldFieldGroup' => 'HeraldFieldGroup',
'PhabricatorProjectHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'PhabricatorProjectIconSet' => 'PhabricatorIconSet',
'PhabricatorProjectIconTransaction' => 'PhabricatorProjectTransactionType',
'PhabricatorProjectIconsConfigType' => 'PhabricatorJSONConfigType',
'PhabricatorProjectImageTransaction' => 'PhabricatorProjectTransactionType',
'PhabricatorProjectListController' => 'PhabricatorProjectController',
'PhabricatorProjectListView' => 'AphrontView',
'PhabricatorProjectLockController' => 'PhabricatorProjectController',
'PhabricatorProjectLockTransaction' => 'PhabricatorProjectTransactionType',
'PhabricatorProjectLogicalAncestorDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectLogicalDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectLogicalOnlyDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorProjectLogicalOrNotDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectLogicalUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectLogicalViewerDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorProjectManageController' => 'PhabricatorProjectController',
'PhabricatorProjectManageProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorProjectMaterializedMemberEdgeType' => 'PhabricatorEdgeType',
'PhabricatorProjectMemberListView' => 'PhabricatorProjectUserListView',
'PhabricatorProjectMemberOfProjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorProjectMembersAddController' => 'PhabricatorProjectController',
'PhabricatorProjectMembersDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectMembersPolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorProjectMembersProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorProjectMembersRemoveController' => 'PhabricatorProjectController',
'PhabricatorProjectMembersViewController' => 'PhabricatorProjectController',
'PhabricatorProjectMenuItemController' => 'PhabricatorProjectController',
'PhabricatorProjectMilestoneTransaction' => 'PhabricatorProjectTypeTransaction',
'PhabricatorProjectMoveController' => 'PhabricatorProjectController',
'PhabricatorProjectNameContextFreeGrammar' => 'PhutilContextFreeGrammar',
'PhabricatorProjectNameTransaction' => 'PhabricatorProjectTransactionType',
'PhabricatorProjectNoProjectsDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorProjectObjectHasProjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorProjectOrUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectOrUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectPHIDResolver' => 'PhabricatorPHIDResolver',
'PhabricatorProjectParentTransaction' => 'PhabricatorProjectTypeTransaction',
'PhabricatorProjectPictureProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorProjectPointsProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
'PhabricatorProjectProfileMenuEngine' => 'PhabricatorProfileMenuEngine',
'PhabricatorProjectProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorProjectProjectHasMemberEdgeType' => 'PhabricatorEdgeType',
'PhabricatorProjectProjectHasObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorProjectProjectPHIDType' => 'PhabricatorPHIDType',
'PhabricatorProjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorProjectRemoveHeraldAction' => 'PhabricatorProjectHeraldAction',
'PhabricatorProjectReportsController' => 'PhabricatorProjectController',
'PhabricatorProjectReportsProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorProjectSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorProjectSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorProjectSearchField' => 'PhabricatorSearchTokenizerField',
'PhabricatorProjectSilenceController' => 'PhabricatorProjectController',
'PhabricatorProjectSilencedEdgeType' => 'PhabricatorEdgeType',
'PhabricatorProjectSlug' => 'PhabricatorProjectDAO',
'PhabricatorProjectSlugsTransaction' => 'PhabricatorProjectTransactionType',
'PhabricatorProjectSortTransaction' => 'PhabricatorProjectTransactionType',
'PhabricatorProjectStandardCustomField' => array(
'PhabricatorProjectCustomField',
'PhabricatorStandardCustomFieldInterface',
),
'PhabricatorProjectStatus' => 'Phobject',
'PhabricatorProjectStatusTransaction' => 'PhabricatorProjectTransactionType',
'PhabricatorProjectSubprojectWarningController' => 'PhabricatorProjectController',
'PhabricatorProjectSubprojectsController' => 'PhabricatorProjectController',
'PhabricatorProjectSubprojectsProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorProjectSubtypeDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorProjectSubtypesConfigType' => 'PhabricatorJSONConfigType',
'PhabricatorProjectTagsAddedField' => 'PhabricatorProjectTagsField',
'PhabricatorProjectTagsField' => 'HeraldField',
'PhabricatorProjectTagsRemovedField' => 'PhabricatorProjectTagsField',
'PhabricatorProjectTestDataGenerator' => 'PhabricatorTestDataGenerator',
'PhabricatorProjectTransaction' => 'PhabricatorModularTransaction',
'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorProjectTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorProjectTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorProjectTrigger' => array(
'PhabricatorProjectDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorIndexableInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorProjectTriggerAddProjectsRule' => 'PhabricatorProjectTriggerRule',
'PhabricatorProjectTriggerController' => 'PhabricatorProjectController',
'PhabricatorProjectTriggerCorruptionException' => 'Exception',
'PhabricatorProjectTriggerEditController' => 'PhabricatorProjectTriggerController',
'PhabricatorProjectTriggerEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorProjectTriggerInvalidRule' => 'PhabricatorProjectTriggerRule',
'PhabricatorProjectTriggerListController' => 'PhabricatorProjectTriggerController',
'PhabricatorProjectTriggerManiphestOwnerRule' => 'PhabricatorProjectTriggerRule',
'PhabricatorProjectTriggerManiphestPriorityRule' => 'PhabricatorProjectTriggerRule',
'PhabricatorProjectTriggerManiphestStatusRule' => 'PhabricatorProjectTriggerRule',
'PhabricatorProjectTriggerNameTransaction' => 'PhabricatorProjectTriggerTransactionType',
'PhabricatorProjectTriggerPHIDType' => 'PhabricatorPHIDType',
'PhabricatorProjectTriggerPlaySoundRule' => 'PhabricatorProjectTriggerRule',
'PhabricatorProjectTriggerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorProjectTriggerRemoveProjectsRule' => 'PhabricatorProjectTriggerRule',
'PhabricatorProjectTriggerRule' => 'Phobject',
'PhabricatorProjectTriggerRuleRecord' => 'Phobject',
'PhabricatorProjectTriggerRulesetTransaction' => 'PhabricatorProjectTriggerTransactionType',
'PhabricatorProjectTriggerSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorProjectTriggerTransaction' => 'PhabricatorModularTransaction',
'PhabricatorProjectTriggerTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorProjectTriggerTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorProjectTriggerUnknownRule' => 'PhabricatorProjectTriggerRule',
'PhabricatorProjectTriggerUsage' => 'PhabricatorProjectDAO',
'PhabricatorProjectTriggerUsageIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
'PhabricatorProjectTriggerViewController' => 'PhabricatorProjectTriggerController',
'PhabricatorProjectTypeTransaction' => 'PhabricatorProjectTransactionType',
'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener',
'PhabricatorProjectUpdateController' => 'PhabricatorProjectController',
'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectUserListView' => 'AphrontView',
'PhabricatorProjectViewController' => 'PhabricatorProjectController',
'PhabricatorProjectWatchController' => 'PhabricatorProjectController',
'PhabricatorProjectWatcherListView' => 'PhabricatorProjectUserListView',
'PhabricatorProjectWorkboardBackgroundColor' => 'Phobject',
'PhabricatorProjectWorkboardBackgroundTransaction' => 'PhabricatorProjectTransactionType',
'PhabricatorProjectWorkboardProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorProjectWorkboardTransaction' => 'PhabricatorProjectTransactionType',
'PhabricatorProjectsAllPolicyRule' => 'PhabricatorProjectsBasePolicyRule',
'PhabricatorProjectsAncestorsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorProjectsBasePolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorProjectsCurtainExtension' => 'PHUICurtainExtension',
'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField',
'PhabricatorProjectsExportEngineExtension' => 'PhabricatorExportEngineExtension',
'PhabricatorProjectsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
'PhabricatorProjectsMailEngineExtension' => 'PhabricatorMailEngineExtension',
'PhabricatorProjectsMembersSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorProjectsMembershipIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
'PhabricatorProjectsPolicyRule' => 'PhabricatorProjectsBasePolicyRule',
'PhabricatorProjectsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorProjectsSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'PhabricatorProjectsWatchersSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorPronounSetting' => 'PhabricatorSelectSetting',
'PhabricatorProtocolLog' => 'Phobject',
'PhabricatorPureChartFunction' => 'PhabricatorChartFunction',
'PhabricatorPygmentSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorQuery' => 'Phobject',
'PhabricatorQueryConstraint' => 'Phobject',
'PhabricatorQueryCursor' => 'Phobject',
'PhabricatorQueryIterator' => 'PhutilBufferedIterator',
'PhabricatorQueryOrderItem' => 'Phobject',
'PhabricatorQueryOrderTestCase' => 'PhabricatorTestCase',
'PhabricatorQueryOrderVector' => array(
'Phobject',
'Iterator',
),
'PhabricatorRateLimitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorReassignEmailUserLogType' => 'PhabricatorUserLogType',
'PhabricatorRebuildIndexesWorker' => 'PhabricatorWorker',
'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorRedirectController' => 'PhabricatorController',
'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController',
'PhabricatorRegexListConfigType' => 'PhabricatorTextListConfigType',
'PhabricatorRegistrationProfile' => 'Phobject',
- 'PhabricatorReleephApplication' => 'PhabricatorApplication',
- 'PhabricatorReleephApplicationConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorRemarkupCachePurger' => 'PhabricatorCachePurger',
'PhabricatorRemarkupControl' => 'AphrontFormTextAreaControl',
'PhabricatorRemarkupCowsayBlockInterpreter' => 'PhutilRemarkupBlockInterpreter',
'PhabricatorRemarkupCustomBlockRule' => 'PhutilRemarkupBlockRule',
'PhabricatorRemarkupCustomInlineRule' => 'PhutilRemarkupRule',
'PhabricatorRemarkupDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorRemarkupEditField' => 'PhabricatorEditField',
'PhabricatorRemarkupFigletBlockInterpreter' => 'PhutilRemarkupBlockInterpreter',
'PhabricatorRemarkupHyperlinkEngineExtension' => 'PhutilRemarkupHyperlinkEngineExtension',
'PhabricatorRemarkupUIExample' => 'PhabricatorUIExample',
'PhabricatorRemoveEmailUserLogType' => 'PhabricatorUserLogType',
'PhabricatorRemoveMultifactorUserLogType' => 'PhabricatorUserLogType',
'PhabricatorRepositoriesSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorRepository' => array(
'PhabricatorRepositoryDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorFlaggableInterface',
'PhabricatorMarkupInterface',
'PhabricatorDestructibleInterface',
'PhabricatorDestructibleCodexInterface',
'PhabricatorProjectInterface',
'PhabricatorSpacesInterface',
'PhabricatorConduitResultInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
),
'PhabricatorRepositoryActivateTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryAuditRequest' => array(
'PhabricatorRepositoryDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorRepositoryBlueprintsTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryBranch' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryCallsignTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryCommit' => array(
'PhabricatorRepositoryDAO',
'PhabricatorPolicyInterface',
'PhabricatorFlaggableInterface',
'PhabricatorProjectInterface',
'PhabricatorTokenReceiverInterface',
'PhabricatorSubscribableInterface',
'PhabricatorMentionableInterface',
'HarbormasterBuildableInterface',
'HarbormasterCircleCIBuildableInterface',
'HarbormasterBuildkiteBuildableInterface',
'PhabricatorCustomFieldInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorTimelineInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
'PhabricatorConduitResultInterface',
'PhabricatorDraftInterface',
),
'PhabricatorRepositoryCommitChangeParserWorker' => 'PhabricatorRepositoryCommitParserWorker',
'PhabricatorRepositoryCommitData' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryCommitHint' => array(
'PhabricatorRepositoryDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorRepositoryCommitMessageParserWorker' => 'PhabricatorRepositoryCommitParserWorker',
'PhabricatorRepositoryCommitPHIDType' => 'PhabricatorPHIDType',
'PhabricatorRepositoryCommitParserWorker' => 'PhabricatorWorker',
'PhabricatorRepositoryCommitPublishWorker' => 'PhabricatorRepositoryCommitParserWorker',
'PhabricatorRepositoryCommitRef' => 'Phobject',
'PhabricatorRepositoryCommitTestCase' => 'PhabricatorTestCase',
'PhabricatorRepositoryConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorRepositoryCopyTimeLimitTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryDAO' => 'PhabricatorLiskDAO',
'PhabricatorRepositoryDangerousTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryDefaultBranchTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryDescriptionTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryDestructibleCodex' => 'PhabricatorDestructibleCodex',
'PhabricatorRepositoryDiscoveryEngine' => 'PhabricatorRepositoryEngine',
'PhabricatorRepositoryEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorRepositoryEncodingTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryEngine' => 'Phobject',
'PhabricatorRepositoryEnormousTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryFerretEngine' => 'PhabricatorFerretEngine',
'PhabricatorRepositoryFetchRefsTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryFilesizeLimitTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryFulltextEngine' => 'PhabricatorFulltextEngine',
'PhabricatorRepositoryGitCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
'PhabricatorRepositoryGitLFSRef' => array(
'PhabricatorRepositoryDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorRepositoryGitLFSRefQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorRepositoryGraphCache' => 'Phobject',
'PhabricatorRepositoryGraphStream' => 'Phobject',
'PhabricatorRepositoryIdentity' => array(
'PhabricatorRepositoryDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
),
'PhabricatorRepositoryIdentityAssignTransaction' => 'PhabricatorRepositoryIdentityTransactionType',
'PhabricatorRepositoryIdentityChangeWorker' => 'PhabricatorWorker',
'PhabricatorRepositoryIdentityEditEngine' => 'PhabricatorEditEngine',
'PhabricatorRepositoryIdentityFerretEngine' => 'PhabricatorFerretEngine',
'PhabricatorRepositoryIdentityPHIDType' => 'PhabricatorPHIDType',
'PhabricatorRepositoryIdentityQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorRepositoryIdentityTransaction' => 'PhabricatorModularTransaction',
'PhabricatorRepositoryIdentityTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorRepositoryIdentityTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorRepositoryMaintenanceTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryManagementCacheWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementClusterizeWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementDiscoverWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementHintWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementImportingWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementListPathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementListWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementLockWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementMaintenanceWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementMirrorWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementMovePathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementParentsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementPullWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementRebuildIdentitiesWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementRefsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementReparseWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementThawWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementUnpublishWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementUpdateWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
'PhabricatorRepositoryMirror' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryMirrorEngine' => 'PhabricatorRepositoryEngine',
'PhabricatorRepositoryNameTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryNotifyTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryOldRef' => array(
'PhabricatorRepositoryDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorRepositoryParsedChange' => 'Phobject',
'PhabricatorRepositoryPermanentRefsTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryPublisher' => 'Phobject',
'PhabricatorRepositoryPublisherHoldReason' => 'Phobject',
'PhabricatorRepositoryPullEngine' => 'PhabricatorRepositoryEngine',
'PhabricatorRepositoryPullEvent' => array(
'PhabricatorRepositoryDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorRepositoryPullEventPHIDType' => 'PhabricatorPHIDType',
'PhabricatorRepositoryPullEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorRepositoryPullLocalDaemon' => 'PhabricatorDaemon',
'PhabricatorRepositoryPullLocalDaemonModule' => 'PhutilDaemonOverseerModule',
'PhabricatorRepositoryPushEvent' => array(
'PhabricatorRepositoryDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorRepositoryPushEventPHIDType' => 'PhabricatorPHIDType',
'PhabricatorRepositoryPushEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorRepositoryPushLog' => array(
'PhabricatorRepositoryDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorRepositoryPushLogPHIDType' => 'PhabricatorPHIDType',
'PhabricatorRepositoryPushLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorRepositoryPushLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorRepositoryPushMailWorker' => 'PhabricatorWorker',
'PhabricatorRepositoryPushPolicyTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryPushReplyHandler' => 'PhabricatorMailReplyHandler',
'PhabricatorRepositoryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorRepositoryRefCursor' => array(
'PhabricatorRepositoryDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorRepositoryRefCursorPHIDType' => 'PhabricatorPHIDType',
'PhabricatorRepositoryRefCursorQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorRepositoryRefEngine' => 'PhabricatorRepositoryEngine',
'PhabricatorRepositoryRefPosition' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryRepositoryPHIDType' => 'PhabricatorPHIDType',
'PhabricatorRepositorySVNSubpathTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositorySchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorRepositorySearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorRepositoryServiceTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositorySlugTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryStagingURITransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryStatusMessage' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositorySvnCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
'PhabricatorRepositorySvnCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
'PhabricatorRepositorySymbol' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositorySymbolLanguagesTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositorySymbolSourcesTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositorySyncEvent' => array(
'PhabricatorRepositoryDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorRepositorySyncEventPHIDType' => 'PhabricatorPHIDType',
'PhabricatorRepositorySyncEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorRepositoryTestCase' => 'PhabricatorTestCase',
'PhabricatorRepositoryTouchLimitTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryTrackOnlyTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryTransaction' => 'PhabricatorModularTransaction',
'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorRepositoryTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorRepositoryType' => 'Phobject',
'PhabricatorRepositoryURI' => array(
'PhabricatorRepositoryDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
'PhabricatorConduitResultInterface',
),
'PhabricatorRepositoryURIIndex' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryURIPHIDType' => 'PhabricatorPHIDType',
'PhabricatorRepositoryURIQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase',
'PhabricatorRepositoryURITransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorRepositoryURITransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorRepositoryVCSTransaction' => 'PhabricatorRepositoryTransactionType',
'PhabricatorRepositoryWorkingCopyVersion' => 'PhabricatorRepositoryDAO',
'PhabricatorRequestExceptionHandler' => 'AphrontRequestExceptionHandler',
'PhabricatorResetPasswordUserLogType' => 'PhabricatorUserLogType',
'PhabricatorResourceSite' => 'PhabricatorSite',
'PhabricatorRobotsBlogController' => 'PhabricatorRobotsController',
'PhabricatorRobotsController' => 'PhabricatorController',
'PhabricatorRobotsPlatformController' => 'PhabricatorRobotsController',
'PhabricatorRobotsResourceController' => 'PhabricatorRobotsController',
'PhabricatorRobotsShortController' => 'PhabricatorRobotsController',
'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine',
'PhabricatorSMSAuthFactor' => 'PhabricatorAuthFactor',
'PhabricatorSQLPatchList' => 'Phobject',
'PhabricatorSSHKeyGenerator' => 'Phobject',
'PhabricatorSSHKeysSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorSSHLog' => 'Phobject',
'PhabricatorSSHPassthruCommand' => 'Phobject',
'PhabricatorSSHWorkflow' => 'PhutilArgumentWorkflow',
'PhabricatorSavedQuery' => array(
'PhabricatorSearchDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorSavedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorScaleChartFunction' => 'PhabricatorPureChartFunction',
'PhabricatorScheduleTaskTriggerAction' => 'PhabricatorTriggerAction',
'PhabricatorScopedEnv' => 'Phobject',
'PhabricatorSearchAbstractDocument' => 'Phobject',
'PhabricatorSearchApplication' => 'PhabricatorApplication',
'PhabricatorSearchApplicationSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorSearchApplicationStorageEnginePanel' => 'PhabricatorApplicationConfigurationPanel',
'PhabricatorSearchBaseController' => 'PhabricatorController',
'PhabricatorSearchCheckboxesField' => 'PhabricatorSearchField',
'PhabricatorSearchConstraintException' => 'Exception',
'PhabricatorSearchController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchCustomFieldProxyField' => 'PhabricatorSearchField',
'PhabricatorSearchDAO' => 'PhabricatorLiskDAO',
'PhabricatorSearchDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorSearchDatasourceField' => 'PhabricatorSearchTokenizerField',
'PhabricatorSearchDateControlField' => 'PhabricatorSearchField',
'PhabricatorSearchDateField' => 'PhabricatorSearchField',
'PhabricatorSearchDefaultController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchDeleteController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchDocument' => 'PhabricatorSearchDAO',
'PhabricatorSearchDocumentFieldType' => 'Phobject',
'PhabricatorSearchDocumentQuery' => 'PhabricatorPolicyAwareQuery',
'PhabricatorSearchDocumentRelationship' => 'PhabricatorSearchDAO',
'PhabricatorSearchDocumentTypeDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorSearchEditController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchEngineAPIMethod' => 'ConduitAPIMethod',
'PhabricatorSearchEngineAttachment' => 'Phobject',
'PhabricatorSearchEngineExtension' => 'Phobject',
'PhabricatorSearchEngineExtensionModule' => 'PhabricatorConfigModule',
'PhabricatorSearchFerretNgramGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorSearchField' => 'Phobject',
'PhabricatorSearchHandleController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchHost' => 'Phobject',
'PhabricatorSearchHovercardController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchIndexVersion' => 'PhabricatorSearchDAO',
'PhabricatorSearchIndexVersionDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
'PhabricatorSearchIntField' => 'PhabricatorSearchField',
'PhabricatorSearchManagementIndexWorkflow' => 'PhabricatorSearchManagementWorkflow',
'PhabricatorSearchManagementInitWorkflow' => 'PhabricatorSearchManagementWorkflow',
'PhabricatorSearchManagementNgramsWorkflow' => 'PhabricatorSearchManagementWorkflow',
'PhabricatorSearchManagementQueryWorkflow' => 'PhabricatorSearchManagementWorkflow',
'PhabricatorSearchManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorSearchNgramEngine' => 'Phobject',
'PhabricatorSearchNgrams' => 'PhabricatorSearchDAO',
'PhabricatorSearchNgramsDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
'PhabricatorSearchOrderController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchOrderField' => 'PhabricatorSearchField',
'PhabricatorSearchRelationship' => 'Phobject',
'PhabricatorSearchRelationshipController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchRelationshipSourceController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchResultBucket' => 'Phobject',
'PhabricatorSearchResultBucketGroup' => 'Phobject',
'PhabricatorSearchResultView' => 'AphrontView',
'PhabricatorSearchSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorSearchScopeSetting' => 'PhabricatorSelectSetting',
'PhabricatorSearchSelectField' => 'PhabricatorSearchField',
'PhabricatorSearchService' => 'Phobject',
'PhabricatorSearchSettingsPanel' => 'PhabricatorEditEngineSettingsPanel',
'PhabricatorSearchStringListField' => 'PhabricatorSearchField',
'PhabricatorSearchSubscribersField' => 'PhabricatorSearchTokenizerField',
'PhabricatorSearchTextField' => 'PhabricatorSearchField',
'PhabricatorSearchThreeStateField' => 'PhabricatorSearchField',
'PhabricatorSearchTokenizerField' => 'PhabricatorSearchField',
'PhabricatorSearchWorker' => 'PhabricatorWorker',
'PhabricatorSecurityConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorSecuritySetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorSelectEditField' => 'PhabricatorEditField',
'PhabricatorSelectSetting' => 'PhabricatorSetting',
'PhabricatorSelfHyperlinkEngineExtension' => 'PhabricatorRemarkupHyperlinkEngineExtension',
'PhabricatorSessionsSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorSetConfigType' => 'PhabricatorTextConfigType',
'PhabricatorSetting' => 'Phobject',
'PhabricatorSettingsAccountPanelGroup' => 'PhabricatorSettingsPanelGroup',
'PhabricatorSettingsAddEmailAction' => 'PhabricatorSystemAction',
'PhabricatorSettingsAdjustController' => 'PhabricatorController',
'PhabricatorSettingsApplication' => 'PhabricatorApplication',
'PhabricatorSettingsApplicationsPanelGroup' => 'PhabricatorSettingsPanelGroup',
'PhabricatorSettingsAuthenticationPanelGroup' => 'PhabricatorSettingsPanelGroup',
'PhabricatorSettingsDeveloperPanelGroup' => 'PhabricatorSettingsPanelGroup',
'PhabricatorSettingsEditEngine' => 'PhabricatorEditEngine',
'PhabricatorSettingsEmailPanelGroup' => 'PhabricatorSettingsPanelGroup',
'PhabricatorSettingsIssueController' => 'PhabricatorController',
'PhabricatorSettingsListController' => 'PhabricatorController',
'PhabricatorSettingsLogsPanelGroup' => 'PhabricatorSettingsPanelGroup',
'PhabricatorSettingsMainController' => 'PhabricatorController',
'PhabricatorSettingsPanel' => 'Phobject',
'PhabricatorSettingsPanelGroup' => 'Phobject',
'PhabricatorSettingsTimezoneController' => 'PhabricatorController',
'PhabricatorSetupCheck' => 'Phobject',
'PhabricatorSetupCheckTestCase' => 'PhabricatorTestCase',
'PhabricatorSetupEngine' => 'Phobject',
'PhabricatorSetupIssue' => 'Phobject',
'PhabricatorSetupIssueUIExample' => 'PhabricatorUIExample',
'PhabricatorSetupIssueView' => 'AphrontView',
'PhabricatorShiftChartFunction' => 'PhabricatorPureChartFunction',
'PhabricatorShortSite' => 'PhabricatorSite',
'PhabricatorSignDocumentsUserLogType' => 'PhabricatorUserLogType',
'PhabricatorSimpleEditType' => 'PhabricatorEditType',
'PhabricatorSinChartFunction' => 'PhabricatorPureChartFunction',
'PhabricatorSite' => 'AphrontSite',
'PhabricatorSlackAuthProvider' => 'PhabricatorOAuth2AuthProvider',
'PhabricatorSlowvoteApplication' => 'PhabricatorApplication',
'PhabricatorSlowvoteChoice' => 'PhabricatorSlowvoteDAO',
'PhabricatorSlowvoteCloseController' => 'PhabricatorSlowvoteController',
- 'PhabricatorSlowvoteCloseTransaction' => 'PhabricatorSlowvoteTransactionType',
'PhabricatorSlowvoteCommentController' => 'PhabricatorSlowvoteController',
'PhabricatorSlowvoteController' => 'PhabricatorController',
'PhabricatorSlowvoteDAO' => 'PhabricatorLiskDAO',
'PhabricatorSlowvoteDefaultViewCapability' => 'PhabricatorPolicyCapability',
'PhabricatorSlowvoteDescriptionTransaction' => 'PhabricatorSlowvoteTransactionType',
'PhabricatorSlowvoteEditController' => 'PhabricatorSlowvoteController',
'PhabricatorSlowvoteEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorSlowvoteListController' => 'PhabricatorSlowvoteController',
'PhabricatorSlowvoteMailReceiver' => 'PhabricatorObjectMailReceiver',
'PhabricatorSlowvoteOption' => 'PhabricatorSlowvoteDAO',
'PhabricatorSlowvotePoll' => array(
'PhabricatorSlowvoteDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorSubscribableInterface',
'PhabricatorFlaggableInterface',
'PhabricatorTokenReceiverInterface',
'PhabricatorProjectInterface',
'PhabricatorDestructibleInterface',
'PhabricatorSpacesInterface',
'PhabricatorConduitResultInterface',
),
'PhabricatorSlowvotePollController' => 'PhabricatorSlowvoteController',
'PhabricatorSlowvotePollPHIDType' => 'PhabricatorPHIDType',
'PhabricatorSlowvoteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorSlowvoteQuestionTransaction' => 'PhabricatorSlowvoteTransactionType',
'PhabricatorSlowvoteReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PhabricatorSlowvoteResponsesTransaction' => 'PhabricatorSlowvoteTransactionType',
'PhabricatorSlowvoteSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorSlowvoteSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorSlowvoteShuffleTransaction' => 'PhabricatorSlowvoteTransactionType',
+ 'PhabricatorSlowvoteStatusTransaction' => 'PhabricatorSlowvoteTransactionType',
'PhabricatorSlowvoteTransaction' => 'PhabricatorModularTransaction',
'PhabricatorSlowvoteTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PhabricatorSlowvoteTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorSlowvoteTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorSlowvoteVoteController' => 'PhabricatorSlowvoteController',
+ 'PhabricatorSlowvoteVotingMethodTransaction' => 'PhabricatorSlowvoteTransactionType',
'PhabricatorSlug' => 'Phobject',
'PhabricatorSlugTestCase' => 'PhabricatorTestCase',
'PhabricatorSourceCodeView' => 'AphrontView',
'PhabricatorSourceDocumentEngine' => 'PhabricatorTextDocumentEngine',
'PhabricatorSpaceEditField' => 'PhabricatorEditField',
'PhabricatorSpacesApplication' => 'PhabricatorApplication',
'PhabricatorSpacesArchiveController' => 'PhabricatorSpacesController',
'PhabricatorSpacesCapabilityCreateSpaces' => 'PhabricatorPolicyCapability',
'PhabricatorSpacesCapabilityDefaultEdit' => 'PhabricatorPolicyCapability',
'PhabricatorSpacesCapabilityDefaultView' => 'PhabricatorPolicyCapability',
'PhabricatorSpacesController' => 'PhabricatorController',
'PhabricatorSpacesDAO' => 'PhabricatorLiskDAO',
'PhabricatorSpacesEditController' => 'PhabricatorSpacesController',
'PhabricatorSpacesExportEngineExtension' => 'PhabricatorExportEngineExtension',
'PhabricatorSpacesInterface' => 'PhabricatorPHIDInterface',
'PhabricatorSpacesListController' => 'PhabricatorSpacesController',
'PhabricatorSpacesMailEngineExtension' => 'PhabricatorMailEngineExtension',
'PhabricatorSpacesNamespace' => array(
'PhabricatorSpacesDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorSpacesNamespaceArchiveTransaction' => 'PhabricatorSpacesNamespaceTransactionType',
'PhabricatorSpacesNamespaceDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorSpacesNamespaceDefaultTransaction' => 'PhabricatorSpacesNamespaceTransactionType',
'PhabricatorSpacesNamespaceDescriptionTransaction' => 'PhabricatorSpacesNamespaceTransactionType',
'PhabricatorSpacesNamespaceEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorSpacesNamespaceNameTransaction' => 'PhabricatorSpacesNamespaceTransactionType',
'PhabricatorSpacesNamespacePHIDType' => 'PhabricatorPHIDType',
'PhabricatorSpacesNamespaceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorSpacesNamespaceSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorSpacesNamespaceTransaction' => 'PhabricatorModularTransaction',
'PhabricatorSpacesNamespaceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorSpacesNamespaceTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorSpacesNoAccessController' => 'PhabricatorSpacesController',
'PhabricatorSpacesRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'PhabricatorSpacesSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorSpacesSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'PhabricatorSpacesSearchField' => 'PhabricatorSearchTokenizerField',
'PhabricatorSpacesTestCase' => 'PhabricatorTestCase',
'PhabricatorSpacesViewController' => 'PhabricatorSpacesController',
'PhabricatorStandardCustomField' => 'PhabricatorCustomField',
'PhabricatorStandardCustomFieldBlueprints' => 'PhabricatorStandardCustomFieldTokenizer',
'PhabricatorStandardCustomFieldBool' => 'PhabricatorStandardCustomField',
'PhabricatorStandardCustomFieldCredential' => 'PhabricatorStandardCustomField',
'PhabricatorStandardCustomFieldDatasource' => 'PhabricatorStandardCustomFieldTokenizer',
'PhabricatorStandardCustomFieldDate' => 'PhabricatorStandardCustomField',
'PhabricatorStandardCustomFieldHeader' => 'PhabricatorStandardCustomField',
'PhabricatorStandardCustomFieldInt' => 'PhabricatorStandardCustomField',
'PhabricatorStandardCustomFieldLink' => 'PhabricatorStandardCustomField',
'PhabricatorStandardCustomFieldPHIDs' => 'PhabricatorStandardCustomField',
'PhabricatorStandardCustomFieldRemarkup' => 'PhabricatorStandardCustomField',
'PhabricatorStandardCustomFieldSelect' => 'PhabricatorStandardCustomField',
'PhabricatorStandardCustomFieldText' => 'PhabricatorStandardCustomField',
'PhabricatorStandardCustomFieldTokenizer' => 'PhabricatorStandardCustomFieldPHIDs',
'PhabricatorStandardCustomFieldUsers' => 'PhabricatorStandardCustomFieldTokenizer',
'PhabricatorStandardPageView' => array(
'PhabricatorBarePageView',
'AphrontResponseProducerInterface',
),
'PhabricatorStandardSelectCustomFieldDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorStandardTimelineEngine' => 'PhabricatorTimelineEngine',
'PhabricatorStaticEditField' => 'PhabricatorEditField',
'PhabricatorStatusController' => 'PhabricatorController',
'PhabricatorStatusUIExample' => 'PhabricatorUIExample',
'PhabricatorStorageFixtureScopeGuard' => 'Phobject',
'PhabricatorStorageManagementAPI' => 'Phobject',
'PhabricatorStorageManagementAdjustWorkflow' => 'PhabricatorStorageManagementWorkflow',
'PhabricatorStorageManagementAnalyzeWorkflow' => 'PhabricatorStorageManagementWorkflow',
'PhabricatorStorageManagementDatabasesWorkflow' => 'PhabricatorStorageManagementWorkflow',
'PhabricatorStorageManagementDestroyWorkflow' => 'PhabricatorStorageManagementWorkflow',
'PhabricatorStorageManagementDumpWorkflow' => 'PhabricatorStorageManagementWorkflow',
'PhabricatorStorageManagementOptimizeWorkflow' => 'PhabricatorStorageManagementWorkflow',
'PhabricatorStorageManagementPartitionWorkflow' => 'PhabricatorStorageManagementWorkflow',
'PhabricatorStorageManagementProbeWorkflow' => 'PhabricatorStorageManagementWorkflow',
'PhabricatorStorageManagementQuickstartWorkflow' => 'PhabricatorStorageManagementWorkflow',
'PhabricatorStorageManagementRenamespaceWorkflow' => 'PhabricatorStorageManagementWorkflow',
'PhabricatorStorageManagementShellWorkflow' => 'PhabricatorStorageManagementWorkflow',
'PhabricatorStorageManagementStatusWorkflow' => 'PhabricatorStorageManagementWorkflow',
'PhabricatorStorageManagementUpgradeWorkflow' => 'PhabricatorStorageManagementWorkflow',
'PhabricatorStorageManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorStoragePatch' => 'Phobject',
'PhabricatorStorageSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorStorageSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorStringConfigType' => 'PhabricatorTextConfigType',
'PhabricatorStringExportField' => 'PhabricatorExportField',
'PhabricatorStringListConfigType' => 'PhabricatorTextListConfigType',
'PhabricatorStringListEditField' => 'PhabricatorEditField',
'PhabricatorStringListExportField' => 'PhabricatorListExportField',
'PhabricatorStringMailStamp' => 'PhabricatorMailStamp',
'PhabricatorStringSetting' => 'PhabricatorSetting',
'PhabricatorSubmitEditField' => 'PhabricatorEditField',
'PhabricatorSubscribedToObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorSubscribersEditField' => 'PhabricatorTokenizerEditField',
'PhabricatorSubscribersQuery' => 'PhabricatorQuery',
'PhabricatorSubscriptionTriggerClock' => 'PhabricatorTriggerClock',
'PhabricatorSubscriptionsAddSelfHeraldAction' => 'PhabricatorSubscriptionsHeraldAction',
'PhabricatorSubscriptionsAddSubscribersHeraldAction' => 'PhabricatorSubscriptionsHeraldAction',
'PhabricatorSubscriptionsApplication' => 'PhabricatorApplication',
'PhabricatorSubscriptionsCurtainExtension' => 'PHUICurtainExtension',
'PhabricatorSubscriptionsEditController' => 'PhabricatorController',
'PhabricatorSubscriptionsEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorSubscriptionsEditor' => 'PhabricatorEditor',
'PhabricatorSubscriptionsExportEngineExtension' => 'PhabricatorExportEngineExtension',
'PhabricatorSubscriptionsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
'PhabricatorSubscriptionsHeraldAction' => 'HeraldAction',
'PhabricatorSubscriptionsListController' => 'PhabricatorController',
'PhabricatorSubscriptionsMailEngineExtension' => 'PhabricatorMailEngineExtension',
'PhabricatorSubscriptionsMuteController' => 'PhabricatorController',
'PhabricatorSubscriptionsRemoveSelfHeraldAction' => 'PhabricatorSubscriptionsHeraldAction',
'PhabricatorSubscriptionsRemoveSubscribersHeraldAction' => 'PhabricatorSubscriptionsHeraldAction',
'PhabricatorSubscriptionsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorSubscriptionsSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'PhabricatorSubscriptionsSubscribeEmailCommand' => 'MetaMTAEmailTransactionCommand',
'PhabricatorSubscriptionsSubscribersPolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorSubscriptionsTransactionController' => 'PhabricatorController',
'PhabricatorSubscriptionsUIEventListener' => 'PhabricatorEventListener',
'PhabricatorSubscriptionsUnsubscribeEmailCommand' => 'MetaMTAEmailTransactionCommand',
'PhabricatorSubtypeEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorSumChartFunction' => 'PhabricatorHigherOrderChartFunction',
'PhabricatorSupportApplication' => 'PhabricatorApplication',
'PhabricatorSyntaxHighlighter' => 'Phobject',
'PhabricatorSyntaxHighlightingConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorSyntaxStyle' => 'Phobject',
'PhabricatorSystemAction' => 'Phobject',
'PhabricatorSystemActionEngine' => 'Phobject',
'PhabricatorSystemActionGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorSystemActionLog' => 'PhabricatorSystemDAO',
'PhabricatorSystemActionRateLimitException' => 'Exception',
'PhabricatorSystemApplication' => 'PhabricatorApplication',
'PhabricatorSystemDAO' => 'PhabricatorLiskDAO',
'PhabricatorSystemDebugUIEventListener' => 'PhabricatorEventListener',
'PhabricatorSystemDestructionGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorSystemDestructionLog' => 'PhabricatorSystemDAO',
'PhabricatorSystemObjectController' => 'PhabricatorController',
'PhabricatorSystemReadOnlyController' => 'PhabricatorController',
'PhabricatorSystemRemoveDestroyWorkflow' => 'PhabricatorSystemRemoveWorkflow',
'PhabricatorSystemRemoveLogWorkflow' => 'PhabricatorSystemRemoveWorkflow',
'PhabricatorSystemRemoveWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorSystemSelectEncodingController' => 'PhabricatorController',
'PhabricatorSystemSelectHighlightController' => 'PhabricatorController',
'PhabricatorSystemSelectViewAsController' => 'PhabricatorController',
'PhabricatorTOTPAuthFactor' => 'PhabricatorAuthFactor',
'PhabricatorTOTPAuthFactorTestCase' => 'PhabricatorTestCase',
'PhabricatorTaskmasterDaemon' => 'PhabricatorDaemon',
'PhabricatorTaskmasterDaemonModule' => 'PhutilDaemonOverseerModule',
'PhabricatorTestApplication' => 'PhabricatorApplication',
'PhabricatorTestCase' => 'PhutilTestCase',
'PhabricatorTestController' => 'PhabricatorController',
'PhabricatorTestDataGenerator' => 'Phobject',
'PhabricatorTestNoCycleEdgeType' => 'PhabricatorEdgeType',
'PhabricatorTestStorageEngine' => 'PhabricatorFileStorageEngine',
'PhabricatorTestWorker' => 'PhabricatorWorker',
'PhabricatorTextAreaEditField' => 'PhabricatorEditField',
'PhabricatorTextConfigType' => 'PhabricatorConfigType',
'PhabricatorTextDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorTextEditField' => 'PhabricatorEditField',
'PhabricatorTextExportFormat' => 'PhabricatorExportFormat',
'PhabricatorTextListConfigType' => 'PhabricatorTextConfigType',
'PhabricatorTime' => 'Phobject',
'PhabricatorTimeFormatSetting' => 'PhabricatorSelectSetting',
'PhabricatorTimeGuard' => 'Phobject',
'PhabricatorTimeTestCase' => 'PhabricatorTestCase',
'PhabricatorTimelineEngine' => 'Phobject',
'PhabricatorTimezoneIgnoreOffsetSetting' => 'PhabricatorInternalSetting',
'PhabricatorTimezoneSetting' => 'PhabricatorOptionGroupSetting',
'PhabricatorTimezoneSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorTitleGlyphsSetting' => 'PhabricatorSelectSetting',
'PhabricatorToken' => array(
'PhabricatorTokenDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorTokenController' => 'PhabricatorController',
'PhabricatorTokenCount' => 'PhabricatorTokenDAO',
'PhabricatorTokenCountQuery' => 'PhabricatorOffsetPagedQuery',
'PhabricatorTokenDAO' => 'PhabricatorLiskDAO',
'PhabricatorTokenDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
'PhabricatorTokenGiveController' => 'PhabricatorTokenController',
'PhabricatorTokenGiven' => array(
'PhabricatorTokenDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorTokenGivenController' => 'PhabricatorTokenController',
'PhabricatorTokenGivenEditor' => 'PhabricatorEditor',
'PhabricatorTokenGivenFeedStory' => 'PhabricatorFeedStory',
'PhabricatorTokenGivenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorTokenLeaderController' => 'PhabricatorTokenController',
'PhabricatorTokenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorTokenReceiverQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorTokenTokenPHIDType' => 'PhabricatorPHIDType',
'PhabricatorTokenUIEventListener' => 'PhabricatorEventListener',
'PhabricatorTokenizerEditField' => 'PhabricatorPHIDListEditField',
'PhabricatorTokensApplication' => 'PhabricatorApplication',
'PhabricatorTokensCurtainExtension' => 'PHUICurtainExtension',
'PhabricatorTokensSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorTokensToken' => array(
'PhabricatorTokenDAO',
'PhabricatorDestructibleInterface',
'PhabricatorSubscribableInterface',
'PhabricatorFlaggableInterface',
'PhabricatorConduitResultInterface',
),
'PhabricatorTransactionChange' => 'Phobject',
'PhabricatorTransactionFactEngine' => 'PhabricatorFactEngine',
'PhabricatorTransactionRemarkupChange' => 'PhabricatorTransactionChange',
'PhabricatorTransactionWarning' => 'Phobject',
'PhabricatorTransactions' => 'Phobject',
'PhabricatorTransactionsApplication' => 'PhabricatorApplication',
'PhabricatorTransactionsDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
'PhabricatorTransactionsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
'PhabricatorTransactionsObjectTypeDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorTransformedFile' => 'PhabricatorFileDAO',
'PhabricatorTranslationSetting' => 'PhabricatorOptionGroupSetting',
'PhabricatorTranslationsConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorTriggerAction' => 'Phobject',
'PhabricatorTriggerClock' => 'Phobject',
'PhabricatorTriggerClockTestCase' => 'PhabricatorTestCase',
'PhabricatorTriggerDaemon' => 'PhabricatorDaemon',
'PhabricatorTrivialTestCase' => 'PhabricatorTestCase',
'PhabricatorTwilioFuture' => 'FutureProxy',
'PhabricatorTwitchAuthProvider' => 'PhabricatorOAuth2AuthProvider',
'PhabricatorTwitterAuthProvider' => 'PhabricatorOAuth1AuthProvider',
'PhabricatorTypeaheadApplication' => 'PhabricatorApplication',
'PhabricatorTypeaheadCompositeDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorTypeaheadDatasource' => 'Phobject',
'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController',
'PhabricatorTypeaheadDatasourceTestCase' => 'PhabricatorTestCase',
'PhabricatorTypeaheadFunctionHelpController' => 'PhabricatorTypeaheadDatasourceController',
'PhabricatorTypeaheadInvalidTokenException' => 'Exception',
'PhabricatorTypeaheadModularDatasourceController' => 'PhabricatorTypeaheadDatasourceController',
'PhabricatorTypeaheadMonogramDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorTypeaheadProxyDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorTypeaheadResult' => 'Phobject',
'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorTypeaheadTestNumbersDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorTypeaheadTokenView' => 'AphrontTagView',
'PhabricatorUIConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorUIExample' => 'Phobject',
'PhabricatorUIExampleRenderController' => 'PhabricatorController',
'PhabricatorUIExamplesApplication' => 'PhabricatorApplication',
'PhabricatorURIExportField' => 'PhabricatorExportField',
'PhabricatorUSEnglishTranslation' => 'PhutilTranslation',
'PhabricatorUnifiedDiffsSetting' => 'PhabricatorSelectSetting',
'PhabricatorUnitTestContentSource' => 'PhabricatorContentSource',
'PhabricatorUnitsTestCase' => 'PhabricatorTestCase',
'PhabricatorUnknownContentSource' => 'PhabricatorContentSource',
'PhabricatorUnlockEngine' => 'Phobject',
'PhabricatorUnsubscribedFromObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorUser' => array(
'PhabricatorUserDAO',
'PhutilPerson',
'PhabricatorPolicyInterface',
'PhabricatorCustomFieldInterface',
'PhabricatorDestructibleInterface',
'PhabricatorSSHPublicKeyInterface',
'PhabricatorFlaggableInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
'PhabricatorConduitResultInterface',
'PhabricatorAuthPasswordHashInterface',
),
'PhabricatorUserApproveTransaction' => 'PhabricatorUserTransactionType',
'PhabricatorUserBadgesCacheType' => 'PhabricatorUserCacheType',
'PhabricatorUserBlurbField' => 'PhabricatorUserCustomField',
'PhabricatorUserCache' => 'PhabricatorUserDAO',
'PhabricatorUserCachePurger' => 'PhabricatorCachePurger',
'PhabricatorUserCacheType' => 'Phobject',
'PhabricatorUserCardView' => 'AphrontTagView',
'PhabricatorUserConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorUserConfiguredCustomField' => array(
'PhabricatorUserCustomField',
'PhabricatorStandardCustomFieldInterface',
),
'PhabricatorUserConfiguredCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
'PhabricatorUserCustomField' => 'PhabricatorCustomField',
'PhabricatorUserCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
'PhabricatorUserCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage',
'PhabricatorUserDAO' => 'PhabricatorLiskDAO',
'PhabricatorUserDisableTransaction' => 'PhabricatorUserTransactionType',
'PhabricatorUserEditEngine' => 'PhabricatorEditEngine',
'PhabricatorUserEditor' => 'PhabricatorEditor',
'PhabricatorUserEditorTestCase' => 'PhabricatorTestCase',
'PhabricatorUserEmail' => array(
'PhabricatorUserDAO',
'PhabricatorDestructibleInterface',
'PhabricatorPolicyInterface',
),
'PhabricatorUserEmailTestCase' => 'PhabricatorTestCase',
'PhabricatorUserEmpowerTransaction' => 'PhabricatorUserTransactionType',
'PhabricatorUserFerretEngine' => 'PhabricatorFerretEngine',
'PhabricatorUserFulltextEngine' => 'PhabricatorFulltextEngine',
'PhabricatorUserIconField' => 'PhabricatorUserCustomField',
'PhabricatorUserLog' => array(
'PhabricatorUserDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorUserLogType' => 'Phobject',
'PhabricatorUserLogTypeDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorUserLogView' => 'AphrontView',
'PhabricatorUserMessageCountCacheType' => 'PhabricatorUserCacheType',
'PhabricatorUserNotificationCountCacheType' => 'PhabricatorUserCacheType',
'PhabricatorUserNotifyTransaction' => 'PhabricatorUserTransactionType',
'PhabricatorUserPHIDResolver' => 'PhabricatorPHIDResolver',
'PhabricatorUserPreferences' => array(
'PhabricatorUserDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorApplicationTransactionInterface',
),
'PhabricatorUserPreferencesCacheType' => 'PhabricatorUserCacheType',
'PhabricatorUserPreferencesEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorUserPreferencesPHIDType' => 'PhabricatorPHIDType',
'PhabricatorUserPreferencesQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorUserPreferencesSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorUserPreferencesTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorUserPreferencesTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorUserProfile' => 'PhabricatorUserDAO',
'PhabricatorUserProfileImageCacheType' => 'PhabricatorUserCacheType',
'PhabricatorUserRealNameField' => 'PhabricatorUserCustomField',
'PhabricatorUserRolesField' => 'PhabricatorUserCustomField',
'PhabricatorUserSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorUserSinceField' => 'PhabricatorUserCustomField',
'PhabricatorUserStatusField' => 'PhabricatorUserCustomField',
'PhabricatorUserTestCase' => 'PhabricatorTestCase',
'PhabricatorUserTitleField' => 'PhabricatorUserCustomField',
'PhabricatorUserTransaction' => 'PhabricatorModularTransaction',
'PhabricatorUserTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorUserTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorUserUsernameTransaction' => 'PhabricatorUserTransactionType',
'PhabricatorUsersEditField' => 'PhabricatorTokenizerEditField',
'PhabricatorUsersPolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorUsersSearchField' => 'PhabricatorSearchTokenizerField',
'PhabricatorVCSResponse' => 'AphrontResponse',
'PhabricatorVerifyEmailUserLogType' => 'PhabricatorUserLogType',
'PhabricatorVersionedDraft' => 'PhabricatorDraftDAO',
'PhabricatorVeryWowEnglishTranslation' => 'PhutilTranslation',
'PhabricatorVideoDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorViewerDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorVoidDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorWatcherHasObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorWebContentSource' => 'PhabricatorContentSource',
'PhabricatorWebServerSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorWeekStartDaySetting' => 'PhabricatorSelectSetting',
'PhabricatorWildConfigType' => 'PhabricatorJSONConfigType',
'PhabricatorWordPressAuthProvider' => 'PhabricatorOAuth2AuthProvider',
'PhabricatorWorkboardViewState' => 'Phobject',
'PhabricatorWorker' => 'Phobject',
'PhabricatorWorkerActiveTask' => 'PhabricatorWorkerTask',
'PhabricatorWorkerActiveTaskQuery' => 'PhabricatorWorkerTaskQuery',
'PhabricatorWorkerArchiveTask' => 'PhabricatorWorkerTask',
'PhabricatorWorkerArchiveTaskQuery' => 'PhabricatorWorkerTaskQuery',
'PhabricatorWorkerBulkJob' => array(
'PhabricatorWorkerDAO',
'PhabricatorPolicyInterface',
'PhabricatorSubscribableInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorDestructibleInterface',
),
'PhabricatorWorkerBulkJobCreateWorker' => 'PhabricatorWorkerBulkJobWorker',
'PhabricatorWorkerBulkJobEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorWorkerBulkJobPHIDType' => 'PhabricatorPHIDType',
'PhabricatorWorkerBulkJobQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorWorkerBulkJobSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorWorkerBulkJobTaskWorker' => 'PhabricatorWorkerBulkJobWorker',
'PhabricatorWorkerBulkJobTestCase' => 'PhabricatorTestCase',
'PhabricatorWorkerBulkJobTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorWorkerBulkJobTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorWorkerBulkJobType' => 'Phobject',
'PhabricatorWorkerBulkJobWorker' => 'PhabricatorWorker',
'PhabricatorWorkerBulkTask' => 'PhabricatorWorkerDAO',
'PhabricatorWorkerDAO' => 'PhabricatorLiskDAO',
'PhabricatorWorkerDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
'PhabricatorWorkerLeaseQuery' => 'PhabricatorQuery',
'PhabricatorWorkerManagementCancelWorkflow' => 'PhabricatorWorkerManagementWorkflow',
'PhabricatorWorkerManagementDelayWorkflow' => 'PhabricatorWorkerManagementWorkflow',
'PhabricatorWorkerManagementExecuteWorkflow' => 'PhabricatorWorkerManagementWorkflow',
'PhabricatorWorkerManagementFloodWorkflow' => 'PhabricatorWorkerManagementWorkflow',
'PhabricatorWorkerManagementFreeWorkflow' => 'PhabricatorWorkerManagementWorkflow',
'PhabricatorWorkerManagementPriorityWorkflow' => 'PhabricatorWorkerManagementWorkflow',
'PhabricatorWorkerManagementRetryWorkflow' => 'PhabricatorWorkerManagementWorkflow',
'PhabricatorWorkerManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorWorkerPermanentFailureException' => 'Exception',
'PhabricatorWorkerSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorWorkerSingleBulkJobType' => 'PhabricatorWorkerBulkJobType',
'PhabricatorWorkerTask' => 'PhabricatorWorkerDAO',
'PhabricatorWorkerTaskData' => 'PhabricatorWorkerDAO',
'PhabricatorWorkerTaskDetailController' => 'PhabricatorDaemonController',
'PhabricatorWorkerTaskQuery' => 'PhabricatorQuery',
'PhabricatorWorkerTestCase' => 'PhabricatorTestCase',
'PhabricatorWorkerTrigger' => array(
'PhabricatorWorkerDAO',
'PhabricatorDestructibleInterface',
'PhabricatorPolicyInterface',
),
'PhabricatorWorkerTriggerEvent' => 'PhabricatorWorkerDAO',
'PhabricatorWorkerTriggerManagementFireWorkflow' => 'PhabricatorWorkerTriggerManagementWorkflow',
'PhabricatorWorkerTriggerManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorWorkerTriggerPHIDType' => 'PhabricatorPHIDType',
'PhabricatorWorkerTriggerQuery' => 'PhabricatorPolicyAwareQuery',
'PhabricatorWorkerYieldException' => 'Exception',
'PhabricatorWorkingCopyDiscoveryTestCase' => 'PhabricatorWorkingCopyTestCase',
'PhabricatorWorkingCopyPullTestCase' => 'PhabricatorWorkingCopyTestCase',
'PhabricatorWorkingCopyTestCase' => 'PhabricatorTestCase',
'PhabricatorXHPASTDAO' => 'PhabricatorLiskDAO',
'PhabricatorXHPASTParseTree' => 'PhabricatorXHPASTDAO',
'PhabricatorXHPASTViewController' => 'PhabricatorController',
'PhabricatorXHPASTViewFrameController' => 'PhabricatorXHPASTViewController',
'PhabricatorXHPASTViewFramesetController' => 'PhabricatorXHPASTViewController',
'PhabricatorXHPASTViewInputController' => 'PhabricatorXHPASTViewPanelController',
'PhabricatorXHPASTViewPanelController' => 'PhabricatorXHPASTViewController',
'PhabricatorXHPASTViewRunController' => 'PhabricatorXHPASTViewController',
'PhabricatorXHPASTViewStreamController' => 'PhabricatorXHPASTViewPanelController',
'PhabricatorXHPASTViewTreeController' => 'PhabricatorXHPASTViewPanelController',
'PhabricatorXHProfApplication' => 'PhabricatorApplication',
'PhabricatorXHProfController' => 'PhabricatorController',
'PhabricatorXHProfDAO' => 'PhabricatorLiskDAO',
'PhabricatorXHProfDropController' => 'PhabricatorXHProfController',
'PhabricatorXHProfProfileController' => 'PhabricatorXHProfController',
'PhabricatorXHProfProfileSymbolView' => 'PhabricatorXHProfProfileView',
'PhabricatorXHProfProfileTopLevelView' => 'PhabricatorXHProfProfileView',
'PhabricatorXHProfProfileView' => 'AphrontView',
'PhabricatorXHProfSample' => array(
'PhabricatorXHProfDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorXHProfSampleListController' => 'PhabricatorXHProfController',
'PhabricatorXHProfSampleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorXHProfSampleSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorYoutubeRemarkupRule' => 'PhutilRemarkupRule',
'PhabricatorZipSetupCheck' => 'PhabricatorSetupCheck',
'Phame404Response' => 'AphrontHTMLResponse',
'PhameBlog' => array(
'PhameDAO',
'PhabricatorPolicyInterface',
'PhabricatorMarkupInterface',
'PhabricatorSubscribableInterface',
'PhabricatorFlaggableInterface',
'PhabricatorProjectInterface',
'PhabricatorDestructibleInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorConduitResultInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
),
'PhameBlog404Controller' => 'PhameLiveController',
'PhameBlogArchiveController' => 'PhameBlogController',
'PhameBlogController' => 'PhameController',
'PhameBlogCreateCapability' => 'PhabricatorPolicyCapability',
'PhameBlogDatasource' => 'PhabricatorTypeaheadDatasource',
'PhameBlogDescriptionTransaction' => 'PhameBlogTransactionType',
'PhameBlogEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PhameBlogEditController' => 'PhameBlogController',
'PhameBlogEditEngine' => 'PhabricatorEditEngine',
'PhameBlogEditor' => 'PhabricatorApplicationTransactionEditor',
'PhameBlogFeedController' => 'PhameBlogController',
'PhameBlogFerretEngine' => 'PhabricatorFerretEngine',
'PhameBlogFullDomainTransaction' => 'PhameBlogTransactionType',
'PhameBlogFulltextEngine' => 'PhabricatorFulltextEngine',
'PhameBlogHeaderImageTransaction' => 'PhameBlogTransactionType',
'PhameBlogHeaderPictureController' => 'PhameBlogController',
'PhameBlogListController' => 'PhameBlogController',
'PhameBlogListView' => 'AphrontTagView',
'PhameBlogManageController' => 'PhameBlogController',
'PhameBlogNameTransaction' => 'PhameBlogTransactionType',
'PhameBlogParentDomainTransaction' => 'PhameBlogTransactionType',
'PhameBlogParentSiteTransaction' => 'PhameBlogTransactionType',
'PhameBlogProfileImageTransaction' => 'PhameBlogTransactionType',
'PhameBlogProfilePictureController' => 'PhameBlogController',
'PhameBlogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhameBlogReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PhameBlogSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhameBlogSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhameBlogSite' => 'PhameSite',
'PhameBlogStatusTransaction' => 'PhameBlogTransactionType',
'PhameBlogSubtitleTransaction' => 'PhameBlogTransactionType',
'PhameBlogTransaction' => 'PhabricatorModularTransaction',
'PhameBlogTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhameBlogTransactionType' => 'PhabricatorModularTransactionType',
'PhameBlogViewController' => 'PhameLiveController',
'PhameConstants' => 'Phobject',
'PhameController' => 'PhabricatorController',
'PhameDAO' => 'PhabricatorLiskDAO',
'PhameDescriptionView' => 'AphrontTagView',
'PhameDraftListView' => 'AphrontTagView',
'PhameHomeController' => 'PhamePostController',
'PhameInheritBlogPolicyRule' => 'PhabricatorPolicyRule',
'PhameLiveController' => 'PhameController',
'PhameNextPostView' => 'AphrontTagView',
'PhamePost' => array(
'PhameDAO',
'PhabricatorPolicyInterface',
'PhabricatorMarkupInterface',
'PhabricatorFlaggableInterface',
'PhabricatorProjectInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorSubscribableInterface',
'PhabricatorDestructibleInterface',
'PhabricatorTokenReceiverInterface',
'PhabricatorConduitResultInterface',
'PhabricatorEditEngineLockableInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
),
'PhamePostArchiveController' => 'PhamePostController',
'PhamePostBlogTransaction' => 'PhamePostTransactionType',
'PhamePostBodyTransaction' => 'PhamePostTransactionType',
'PhamePostController' => 'PhameController',
'PhamePostEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PhamePostEditController' => 'PhamePostController',
'PhamePostEditEngine' => 'PhabricatorEditEngine',
'PhamePostEditEngineLock' => 'PhabricatorEditEngineLock',
'PhamePostEditor' => 'PhabricatorApplicationTransactionEditor',
'PhamePostFerretEngine' => 'PhabricatorFerretEngine',
'PhamePostFulltextEngine' => 'PhabricatorFulltextEngine',
'PhamePostHeaderImageTransaction' => 'PhamePostTransactionType',
'PhamePostHeaderPictureController' => 'PhamePostController',
'PhamePostHistoryController' => 'PhamePostController',
'PhamePostListController' => 'PhamePostController',
'PhamePostListView' => 'AphrontTagView',
'PhamePostMailReceiver' => 'PhabricatorObjectMailReceiver',
'PhamePostMoveController' => 'PhamePostController',
'PhamePostPublishController' => 'PhamePostController',
'PhamePostQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhamePostRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'PhamePostReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PhamePostSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhamePostSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhamePostSubtitleTransaction' => 'PhamePostTransactionType',
'PhamePostTitleTransaction' => 'PhamePostTransactionType',
'PhamePostTransaction' => 'PhabricatorModularTransaction',
'PhamePostTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PhamePostTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhamePostTransactionType' => 'PhabricatorModularTransactionType',
'PhamePostViewController' => 'PhameLiveController',
'PhamePostVisibilityTransaction' => 'PhamePostTransactionType',
'PhameSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhameSite' => 'PhabricatorSite',
'PhluxController' => 'PhabricatorController',
'PhluxDAO' => 'PhabricatorLiskDAO',
'PhluxEditController' => 'PhluxController',
'PhluxListController' => 'PhluxController',
'PhluxSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhluxTransaction' => 'PhabricatorApplicationTransaction',
'PhluxTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhluxVariable' => array(
'PhluxDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorFlaggableInterface',
'PhabricatorPolicyInterface',
),
'PhluxVariableEditor' => 'PhabricatorApplicationTransactionEditor',
'PhluxVariablePHIDType' => 'PhabricatorPHIDType',
'PhluxVariableQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhluxViewController' => 'PhluxController',
'PholioController' => 'PhabricatorController',
'PholioDAO' => 'PhabricatorLiskDAO',
'PholioDefaultEditCapability' => 'PhabricatorPolicyCapability',
'PholioDefaultViewCapability' => 'PhabricatorPolicyCapability',
'PholioImage' => array(
'PholioDAO',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
),
'PholioImageDescriptionTransaction' => 'PholioImageTransactionType',
'PholioImageFileTransaction' => 'PholioImageTransactionType',
'PholioImageNameTransaction' => 'PholioImageTransactionType',
'PholioImagePHIDType' => 'PhabricatorPHIDType',
'PholioImageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PholioImageReplaceTransaction' => 'PholioImageTransactionType',
'PholioImageSequenceTransaction' => 'PholioImageTransactionType',
'PholioImageTransactionType' => 'PholioTransactionType',
'PholioImageUploadController' => 'PholioController',
'PholioInlineController' => 'PholioController',
'PholioInlineListController' => 'PholioController',
'PholioMock' => array(
'PholioDAO',
'PhabricatorPolicyInterface',
'PhabricatorSubscribableInterface',
'PhabricatorTokenReceiverInterface',
'PhabricatorFlaggableInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorTimelineInterface',
'PhabricatorProjectInterface',
'PhabricatorDestructibleInterface',
'PhabricatorSpacesInterface',
'PhabricatorMentionableInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
),
'PholioMockArchiveController' => 'PholioController',
'PholioMockAuthorHeraldField' => 'PholioMockHeraldField',
'PholioMockCommentController' => 'PholioController',
'PholioMockDescriptionHeraldField' => 'PholioMockHeraldField',
'PholioMockDescriptionTransaction' => 'PholioMockTransactionType',
'PholioMockEditController' => 'PholioController',
'PholioMockEditor' => 'PhabricatorApplicationTransactionEditor',
'PholioMockEmbedView' => 'AphrontView',
'PholioMockFerretEngine' => 'PhabricatorFerretEngine',
'PholioMockFulltextEngine' => 'PhabricatorFulltextEngine',
'PholioMockHasTaskEdgeType' => 'PhabricatorEdgeType',
'PholioMockHasTaskRelationship' => 'PholioMockRelationship',
'PholioMockHeraldField' => 'HeraldField',
'PholioMockHeraldFieldGroup' => 'HeraldFieldGroup',
'PholioMockImagesView' => 'AphrontView',
'PholioMockInlineTransaction' => 'PholioMockTransactionType',
'PholioMockListController' => 'PholioController',
'PholioMockMailReceiver' => 'PhabricatorObjectMailReceiver',
'PholioMockNameHeraldField' => 'PholioMockHeraldField',
'PholioMockNameTransaction' => 'PholioMockTransactionType',
'PholioMockPHIDType' => 'PhabricatorPHIDType',
'PholioMockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PholioMockRelationship' => 'PhabricatorObjectRelationship',
'PholioMockRelationshipSource' => 'PhabricatorObjectRelationshipSource',
'PholioMockSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PholioMockStatusTransaction' => 'PholioMockTransactionType',
'PholioMockThumbGridView' => 'AphrontView',
'PholioMockTimelineEngine' => 'PhabricatorTimelineEngine',
'PholioMockTransactionType' => 'PholioTransactionType',
'PholioMockViewController' => 'PholioController',
'PholioRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'PholioReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PholioSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PholioTransaction' => 'PhabricatorModularTransaction',
'PholioTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PholioTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PholioTransactionType' => 'PhabricatorModularTransactionType',
'PholioTransactionView' => 'PhabricatorApplicationTransactionView',
'PholioUploadedImageView' => 'AphrontView',
'PhortuneAccount' => array(
'PhortuneDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
),
'PhortuneAccountAddManagerController' => 'PhortuneAccountController',
'PhortuneAccountBillingAddressTransaction' => 'PhortuneAccountTransactionType',
'PhortuneAccountBillingNameTransaction' => 'PhortuneAccountTransactionType',
'PhortuneAccountChargeListController' => 'PhortuneAccountProfileController',
'PhortuneAccountChargesController' => 'PhortuneAccountProfileController',
'PhortuneAccountController' => 'PhortuneController',
'PhortuneAccountDetailsController' => 'PhortuneAccountProfileController',
'PhortuneAccountEditController' => 'PhortuneController',
'PhortuneAccountEditEngine' => 'PhabricatorEditEngine',
'PhortuneAccountEditor' => 'PhabricatorApplicationTransactionEditor',
'PhortuneAccountEmail' => array(
'PhortuneDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
),
'PhortuneAccountEmailAddressTransaction' => 'PhortuneAccountEmailTransactionType',
'PhortuneAccountEmailAddressesController' => 'PhortuneAccountProfileController',
'PhortuneAccountEmailEditController' => 'PhortuneAccountController',
'PhortuneAccountEmailEditEngine' => 'PhabricatorEditEngine',
'PhortuneAccountEmailEditor' => 'PhabricatorApplicationTransactionEditor',
'PhortuneAccountEmailPHIDType' => 'PhabricatorPHIDType',
'PhortuneAccountEmailQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortuneAccountEmailRotateController' => 'PhortuneAccountController',
'PhortuneAccountEmailRotateTransaction' => 'PhortuneAccountEmailTransactionType',
'PhortuneAccountEmailStatus' => 'Phobject',
'PhortuneAccountEmailStatusController' => 'PhortuneAccountController',
'PhortuneAccountEmailStatusTransaction' => 'PhortuneAccountEmailTransactionType',
'PhortuneAccountEmailTransaction' => 'PhabricatorModularTransaction',
'PhortuneAccountEmailTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhortuneAccountEmailTransactionType' => 'PhabricatorModularTransactionType',
'PhortuneAccountEmailViewController' => 'PhortuneAccountController',
'PhortuneAccountHasMemberEdgeType' => 'PhabricatorEdgeType',
'PhortuneAccountHasMerchantEdgeType' => 'PhabricatorEdgeType',
'PhortuneAccountListController' => 'PhortuneController',
'PhortuneAccountManagersController' => 'PhortuneAccountProfileController',
'PhortuneAccountNameTransaction' => 'PhortuneAccountTransactionType',
'PhortuneAccountOrderListController' => 'PhortuneAccountProfileController',
'PhortuneAccountOrdersController' => 'PhortuneAccountProfileController',
'PhortuneAccountOverviewController' => 'PhortuneAccountProfileController',
'PhortuneAccountPHIDType' => 'PhabricatorPHIDType',
'PhortuneAccountPaymentMethodController' => 'PhortuneAccountProfileController',
'PhortuneAccountPaymentMethodViewController' => 'PhortuneAccountController',
'PhortuneAccountProfileController' => 'PhortuneAccountController',
'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortuneAccountSubscriptionAutopayController' => 'PhortuneAccountController',
'PhortuneAccountSubscriptionController' => 'PhortuneAccountProfileController',
'PhortuneAccountSubscriptionViewController' => 'PhortuneAccountController',
'PhortuneAccountTransaction' => 'PhabricatorModularTransaction',
'PhortuneAccountTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhortuneAccountTransactionType' => 'PhabricatorModularTransactionType',
'PhortuneAdHocCart' => 'PhortuneCartImplementation',
'PhortuneAdHocProduct' => 'PhortuneProductImplementation',
'PhortuneAddPaymentMethodAction' => 'PhabricatorSystemAction',
'PhortuneCart' => array(
'PhortuneDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
),
'PhortuneCartAcceptController' => 'PhortuneCartController',
'PhortuneCartCancelController' => 'PhortuneCartController',
'PhortuneCartCheckoutController' => 'PhortuneCartController',
'PhortuneCartController' => 'PhortuneController',
'PhortuneCartEditor' => 'PhabricatorApplicationTransactionEditor',
'PhortuneCartImplementation' => 'Phobject',
'PhortuneCartPHIDType' => 'PhabricatorPHIDType',
'PhortuneCartQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortuneCartReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PhortuneCartSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhortuneCartTransaction' => 'PhabricatorApplicationTransaction',
'PhortuneCartTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhortuneCartUpdateController' => 'PhortuneCartController',
'PhortuneCartViewController' => 'PhortuneCartController',
'PhortuneCartVoidController' => 'PhortuneCartController',
'PhortuneCharge' => array(
'PhortuneDAO',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
),
'PhortuneChargePHIDType' => 'PhabricatorPHIDType',
'PhortuneChargeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortuneChargeSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhortuneChargeTableView' => 'AphrontView',
'PhortuneConstants' => 'Phobject',
'PhortuneController' => 'PhabricatorController',
'PhortuneCreditCardForm' => 'Phobject',
'PhortuneCurrency' => 'Phobject',
'PhortuneCurrencySerializer' => 'PhabricatorLiskSerializer',
'PhortuneCurrencyTestCase' => 'PhabricatorTestCase',
'PhortuneDAO' => 'PhabricatorLiskDAO',
'PhortuneDisplayException' => 'Exception',
'PhortuneErrCode' => 'PhortuneConstants',
'PhortuneExternalController' => 'PhortuneController',
'PhortuneExternalOrderController' => 'PhortuneExternalController',
'PhortuneExternalOverviewController' => 'PhortuneExternalController',
'PhortuneExternalUnsubscribeController' => 'PhortuneExternalController',
'PhortuneLandingController' => 'PhortuneController',
'PhortuneMemberHasAccountEdgeType' => 'PhabricatorEdgeType',
'PhortuneMemberHasMerchantEdgeType' => 'PhabricatorEdgeType',
'PhortuneMerchant' => array(
'PhortuneDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
),
'PhortuneMerchantAddManagerController' => 'PhortuneMerchantController',
'PhortuneMerchantCapability' => 'PhabricatorPolicyCapability',
'PhortuneMerchantContactInfoTransaction' => 'PhortuneMerchantTransactionType',
'PhortuneMerchantController' => 'PhortuneController',
'PhortuneMerchantDescriptionTransaction' => 'PhortuneMerchantTransactionType',
'PhortuneMerchantDetailsController' => 'PhortuneMerchantProfileController',
'PhortuneMerchantEditController' => 'PhortuneController',
'PhortuneMerchantEditEngine' => 'PhabricatorEditEngine',
'PhortuneMerchantEditor' => 'PhabricatorApplicationTransactionEditor',
'PhortuneMerchantHasAccountEdgeType' => 'PhabricatorEdgeType',
'PhortuneMerchantHasMemberEdgeType' => 'PhabricatorEdgeType',
'PhortuneMerchantInvoiceCreateController' => 'PhortuneMerchantController',
'PhortuneMerchantInvoiceEmailTransaction' => 'PhortuneMerchantTransactionType',
'PhortuneMerchantInvoiceFooterTransaction' => 'PhortuneMerchantTransactionType',
'PhortuneMerchantListController' => 'PhortuneController',
'PhortuneMerchantManagersController' => 'PhortuneMerchantProfileController',
'PhortuneMerchantNameTransaction' => 'PhortuneMerchantTransactionType',
'PhortuneMerchantOrderListController' => 'PhortuneMerchantProfileController',
'PhortuneMerchantOrdersController' => 'PhortuneMerchantProfileController',
'PhortuneMerchantOverviewController' => 'PhortuneMerchantProfileController',
'PhortuneMerchantPHIDType' => 'PhabricatorPHIDType',
'PhortuneMerchantPictureController' => 'PhortuneMerchantController',
'PhortuneMerchantPictureTransaction' => 'PhortuneMerchantTransactionType',
'PhortuneMerchantProfileController' => 'PhortuneMerchantController',
'PhortuneMerchantProviderDisableController' => 'PhortuneMerchantController',
'PhortuneMerchantProviderEditController' => 'PhortuneMerchantController',
'PhortuneMerchantProviderViewController' => 'PhortuneMerchantController',
'PhortuneMerchantProvidersController' => 'PhortuneMerchantProfileController',
'PhortuneMerchantQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortuneMerchantSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhortuneMerchantSubscriptionListController' => 'PhortuneMerchantProfileController',
'PhortuneMerchantSubscriptionsController' => 'PhortuneMerchantProfileController',
'PhortuneMerchantTransaction' => 'PhabricatorModularTransaction',
'PhortuneMerchantTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhortuneMerchantTransactionType' => 'PhabricatorModularTransactionType',
'PhortuneMonthYearExpiryControl' => 'AphrontFormControl',
'PhortuneOrderDescriptionView' => 'AphrontView',
'PhortuneOrderItemsView' => 'PhortuneOrderView',
'PhortuneOrderSummaryView' => 'PhortuneOrderView',
'PhortuneOrderTableView' => 'AphrontView',
'PhortuneOrderView' => 'AphrontView',
'PhortunePayPalPaymentProvider' => 'PhortunePaymentProvider',
'PhortunePaymentMethod' => array(
'PhortuneDAO',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
'PhabricatorPolicyCodexInterface',
'PhabricatorApplicationTransactionInterface',
),
'PhortunePaymentMethodCreateController' => 'PhortuneController',
'PhortunePaymentMethodDisableController' => 'PhortuneController',
'PhortunePaymentMethodEditController' => 'PhortuneController',
'PhortunePaymentMethodEditor' => 'PhabricatorApplicationTransactionEditor',
'PhortunePaymentMethodNameTransaction' => 'PhortunePaymentMethodTransactionType',
'PhortunePaymentMethodPHIDType' => 'PhabricatorPHIDType',
'PhortunePaymentMethodPolicyCodex' => 'PhabricatorPolicyCodex',
'PhortunePaymentMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortunePaymentMethodStatusTransaction' => 'PhortunePaymentMethodTransactionType',
'PhortunePaymentMethodTransaction' => 'PhabricatorModularTransaction',
'PhortunePaymentMethodTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhortunePaymentMethodTransactionType' => 'PhabricatorModularTransactionType',
'PhortunePaymentProvider' => 'Phobject',
'PhortunePaymentProviderConfig' => array(
'PhortuneDAO',
'PhabricatorPolicyInterface',
'PhabricatorApplicationTransactionInterface',
),
'PhortunePaymentProviderConfigEditor' => 'PhabricatorApplicationTransactionEditor',
'PhortunePaymentProviderConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortunePaymentProviderConfigTransaction' => 'PhabricatorApplicationTransaction',
'PhortunePaymentProviderConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhortunePaymentProviderPHIDType' => 'PhabricatorPHIDType',
'PhortunePaymentProviderTestCase' => 'PhabricatorTestCase',
'PhortuneProduct' => array(
'PhortuneDAO',
'PhabricatorPolicyInterface',
),
'PhortuneProductImplementation' => 'Phobject',
'PhortuneProductListController' => 'PhabricatorController',
'PhortuneProductPHIDType' => 'PhabricatorPHIDType',
'PhortuneProductQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortuneProductViewController' => 'PhortuneController',
'PhortuneProviderActionController' => 'PhortuneController',
'PhortunePurchase' => array(
'PhortuneDAO',
'PhabricatorPolicyInterface',
),
'PhortunePurchasePHIDType' => 'PhabricatorPHIDType',
'PhortunePurchaseQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortuneSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhortuneStripePaymentProvider' => 'PhortunePaymentProvider',
'PhortuneSubscription' => array(
'PhortuneDAO',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
'PhabricatorPolicyCodexInterface',
'PhabricatorApplicationTransactionInterface',
),
'PhortuneSubscriptionAutopayTransaction' => 'PhortuneSubscriptionTransactionType',
'PhortuneSubscriptionCart' => 'PhortuneCartImplementation',
'PhortuneSubscriptionEditor' => 'PhabricatorApplicationTransactionEditor',
'PhortuneSubscriptionImplementation' => 'Phobject',
'PhortuneSubscriptionPHIDType' => 'PhabricatorPHIDType',
'PhortuneSubscriptionPolicyCodex' => 'PhabricatorPolicyCodex',
'PhortuneSubscriptionProduct' => 'PhortuneProductImplementation',
'PhortuneSubscriptionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortuneSubscriptionSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhortuneSubscriptionTableView' => 'AphrontView',
'PhortuneSubscriptionTransaction' => 'PhabricatorModularTransaction',
'PhortuneSubscriptionTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhortuneSubscriptionTransactionType' => 'PhabricatorModularTransactionType',
'PhortuneSubscriptionWorker' => 'PhabricatorWorker',
'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider',
- 'PhragmentBrowseController' => 'PhragmentController',
- 'PhragmentCanCreateCapability' => 'PhabricatorPolicyCapability',
- 'PhragmentConduitAPIMethod' => 'ConduitAPIMethod',
- 'PhragmentController' => 'PhabricatorController',
- 'PhragmentCreateController' => 'PhragmentController',
- 'PhragmentDAO' => 'PhabricatorLiskDAO',
- 'PhragmentFragment' => array(
- 'PhragmentDAO',
- 'PhabricatorPolicyInterface',
- ),
- 'PhragmentFragmentPHIDType' => 'PhabricatorPHIDType',
- 'PhragmentFragmentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
- 'PhragmentFragmentVersion' => array(
- 'PhragmentDAO',
- 'PhabricatorPolicyInterface',
- ),
- 'PhragmentFragmentVersionPHIDType' => 'PhabricatorPHIDType',
- 'PhragmentFragmentVersionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
- 'PhragmentGetPatchConduitAPIMethod' => 'PhragmentConduitAPIMethod',
- 'PhragmentHistoryController' => 'PhragmentController',
- 'PhragmentPatchController' => 'PhragmentController',
- 'PhragmentPatchUtil' => 'Phobject',
- 'PhragmentPolicyController' => 'PhragmentController',
- 'PhragmentQueryFragmentsConduitAPIMethod' => 'PhragmentConduitAPIMethod',
- 'PhragmentRevertController' => 'PhragmentController',
- 'PhragmentSchemaSpec' => 'PhabricatorConfigSchemaSpec',
- 'PhragmentSnapshot' => array(
- 'PhragmentDAO',
- 'PhabricatorPolicyInterface',
- ),
- 'PhragmentSnapshotChild' => array(
- 'PhragmentDAO',
- 'PhabricatorPolicyInterface',
- ),
- 'PhragmentSnapshotChildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
- 'PhragmentSnapshotCreateController' => 'PhragmentController',
- 'PhragmentSnapshotDeleteController' => 'PhragmentController',
- 'PhragmentSnapshotPHIDType' => 'PhabricatorPHIDType',
- 'PhragmentSnapshotPromoteController' => 'PhragmentController',
- 'PhragmentSnapshotQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
- 'PhragmentSnapshotViewController' => 'PhragmentController',
- 'PhragmentUpdateController' => 'PhragmentController',
- 'PhragmentVersionController' => 'PhragmentController',
- 'PhragmentZIPController' => 'PhragmentController',
'PhrequentConduitAPIMethod' => 'ConduitAPIMethod',
'PhrequentController' => 'PhabricatorController',
'PhrequentCurtainExtension' => 'PHUICurtainExtension',
'PhrequentDAO' => 'PhabricatorLiskDAO',
'PhrequentListController' => 'PhrequentController',
'PhrequentPopConduitAPIMethod' => 'PhrequentConduitAPIMethod',
'PhrequentPushConduitAPIMethod' => 'PhrequentConduitAPIMethod',
'PhrequentSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhrequentTimeBlock' => 'Phobject',
'PhrequentTimeBlockTestCase' => 'PhabricatorTestCase',
'PhrequentTimeSlices' => 'Phobject',
'PhrequentTrackController' => 'PhrequentController',
'PhrequentTrackingConduitAPIMethod' => 'PhrequentConduitAPIMethod',
'PhrequentTrackingEditor' => 'PhabricatorEditor',
'PhrequentUIEventListener' => 'PhabricatorEventListener',
'PhrequentUserTime' => array(
'PhrequentDAO',
'PhabricatorPolicyInterface',
),
'PhrequentUserTimeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhrictionChangeType' => 'PhrictionConstants',
'PhrictionConduitAPIMethod' => 'ConduitAPIMethod',
'PhrictionConstants' => 'Phobject',
'PhrictionContent' => array(
'PhrictionDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorConduitResultInterface',
),
'PhrictionContentPHIDType' => 'PhabricatorPHIDType',
'PhrictionContentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhrictionContentSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhrictionContentSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhrictionContentSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhrictionController' => 'PhabricatorController',
'PhrictionCreateConduitAPIMethod' => 'PhrictionConduitAPIMethod',
'PhrictionDAO' => 'PhabricatorLiskDAO',
'PhrictionDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension',
'PhrictionDeleteController' => 'PhrictionController',
'PhrictionDiffController' => 'PhrictionController',
'PhrictionDocument' => array(
'PhrictionDAO',
'PhabricatorPolicyInterface',
'PhabricatorSubscribableInterface',
'PhabricatorFlaggableInterface',
'PhabricatorTokenReceiverInterface',
'PhabricatorDestructibleInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
'PhabricatorProjectInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorConduitResultInterface',
'PhabricatorPolicyCodexInterface',
'PhabricatorSpacesInterface',
),
'PhrictionDocumentAuthorHeraldField' => 'PhrictionDocumentHeraldField',
'PhrictionDocumentContentHeraldField' => 'PhrictionDocumentHeraldField',
'PhrictionDocumentContentTransaction' => 'PhrictionDocumentEditTransaction',
'PhrictionDocumentController' => 'PhrictionController',
'PhrictionDocumentDatasource' => 'PhabricatorTypeaheadDatasource',
'PhrictionDocumentDeleteTransaction' => 'PhrictionDocumentVersionTransaction',
'PhrictionDocumentDraftTransaction' => 'PhrictionDocumentEditTransaction',
'PhrictionDocumentEditEngine' => 'PhabricatorEditEngine',
'PhrictionDocumentEditTransaction' => 'PhrictionDocumentVersionTransaction',
'PhrictionDocumentFerretEngine' => 'PhabricatorFerretEngine',
'PhrictionDocumentFulltextEngine' => 'PhabricatorFulltextEngine',
'PhrictionDocumentHeraldAdapter' => 'HeraldAdapter',
'PhrictionDocumentHeraldField' => 'HeraldField',
'PhrictionDocumentHeraldFieldGroup' => 'HeraldFieldGroup',
'PhrictionDocumentMoveAwayTransaction' => 'PhrictionDocumentVersionTransaction',
'PhrictionDocumentMoveToTransaction' => 'PhrictionDocumentVersionTransaction',
'PhrictionDocumentPHIDType' => 'PhabricatorPHIDType',
'PhrictionDocumentPathHeraldField' => 'PhrictionDocumentHeraldField',
'PhrictionDocumentPolicyCodex' => 'PhabricatorPolicyCodex',
'PhrictionDocumentPublishTransaction' => 'PhrictionDocumentTransactionType',
'PhrictionDocumentPublishedHeraldField' => 'PhrictionDocumentHeraldField',
'PhrictionDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhrictionDocumentSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PhrictionDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhrictionDocumentStatus' => 'PhabricatorObjectStatus',
'PhrictionDocumentTitleHeraldField' => 'PhrictionDocumentHeraldField',
'PhrictionDocumentTitleTransaction' => 'PhrictionDocumentVersionTransaction',
'PhrictionDocumentTransactionType' => 'PhabricatorModularTransactionType',
'PhrictionDocumentVersionTransaction' => 'PhrictionDocumentTransactionType',
'PhrictionEditConduitAPIMethod' => 'PhrictionConduitAPIMethod',
'PhrictionEditController' => 'PhrictionController',
'PhrictionEditEngineController' => 'PhrictionController',
'PhrictionHistoryConduitAPIMethod' => 'PhrictionConduitAPIMethod',
'PhrictionHistoryController' => 'PhrictionController',
'PhrictionInfoConduitAPIMethod' => 'PhrictionConduitAPIMethod',
'PhrictionListController' => 'PhrictionController',
'PhrictionMarkupPreviewController' => 'PhabricatorController',
'PhrictionMoveController' => 'PhrictionController',
'PhrictionNewController' => 'PhrictionController',
'PhrictionPublishController' => 'PhrictionController',
'PhrictionRemarkupRule' => 'PhutilRemarkupRule',
'PhrictionReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PhrictionSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhrictionTransaction' => 'PhabricatorModularTransaction',
'PhrictionTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PhrictionTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'PhrictionTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhutilAPCKeyValueCache' => 'PhutilKeyValueCache',
'PhutilAmazonAuthAdapter' => 'PhutilOAuthAuthAdapter',
'PhutilAsanaAuthAdapter' => 'PhutilOAuthAuthAdapter',
'PhutilAuthAdapter' => 'Phobject',
'PhutilAuthConfigurationException' => 'PhutilAuthException',
'PhutilAuthCredentialException' => 'PhutilAuthException',
'PhutilAuthException' => 'Exception',
'PhutilAuthUserAbortedException' => 'PhutilAuthException',
'PhutilBitbucketAuthAdapter' => 'PhutilOAuth1AuthAdapter',
'PhutilCLikeCodeSnippetContextFreeGrammar' => 'PhutilCodeSnippetContextFreeGrammar',
'PhutilCalendarAbsoluteDateTime' => 'PhutilCalendarDateTime',
'PhutilCalendarContainerNode' => 'PhutilCalendarNode',
'PhutilCalendarDateTime' => 'Phobject',
'PhutilCalendarDateTimeTestCase' => 'PhutilTestCase',
'PhutilCalendarDocumentNode' => 'PhutilCalendarContainerNode',
'PhutilCalendarDuration' => 'Phobject',
'PhutilCalendarEventNode' => 'PhutilCalendarContainerNode',
'PhutilCalendarNode' => 'Phobject',
'PhutilCalendarProxyDateTime' => 'PhutilCalendarDateTime',
'PhutilCalendarRawNode' => 'PhutilCalendarContainerNode',
'PhutilCalendarRecurrenceList' => 'PhutilCalendarRecurrenceSource',
'PhutilCalendarRecurrenceRule' => 'PhutilCalendarRecurrenceSource',
'PhutilCalendarRecurrenceRuleTestCase' => 'PhutilTestCase',
'PhutilCalendarRecurrenceSet' => 'Phobject',
'PhutilCalendarRecurrenceSource' => 'Phobject',
'PhutilCalendarRecurrenceTestCase' => 'PhutilTestCase',
'PhutilCalendarRelativeDateTime' => 'PhutilCalendarProxyDateTime',
'PhutilCalendarRootNode' => 'PhutilCalendarContainerNode',
'PhutilCalendarUserNode' => 'PhutilCalendarNode',
'PhutilCodeSnippetContextFreeGrammar' => 'PhutilContextFreeGrammar',
'PhutilConsoleSyntaxHighlighter' => 'Phobject',
'PhutilContextFreeGrammar' => 'Phobject',
'PhutilDaemon' => 'Phobject',
'PhutilDaemonHandle' => 'Phobject',
'PhutilDaemonOverseer' => 'Phobject',
'PhutilDaemonOverseerModule' => 'Phobject',
'PhutilDaemonPool' => 'Phobject',
'PhutilDefaultSyntaxHighlighter' => 'Phobject',
'PhutilDefaultSyntaxHighlighterEngine' => 'PhutilSyntaxHighlighterEngine',
'PhutilDefaultSyntaxHighlighterEnginePygmentsFuture' => 'FutureProxy',
'PhutilDefaultSyntaxHighlighterEngineTestCase' => 'PhutilTestCase',
'PhutilDirectoryKeyValueCache' => 'PhutilKeyValueCache',
'PhutilDisqusAuthAdapter' => 'PhutilOAuthAuthAdapter',
'PhutilDivinerSyntaxHighlighter' => 'Phobject',
'PhutilEmptyAuthAdapter' => 'PhutilAuthAdapter',
'PhutilFacebookAuthAdapter' => 'PhutilOAuthAuthAdapter',
'PhutilGitHubAuthAdapter' => 'PhutilOAuthAuthAdapter',
'PhutilGoogleAuthAdapter' => 'PhutilOAuthAuthAdapter',
'PhutilICSParser' => 'Phobject',
'PhutilICSParserException' => 'Exception',
'PhutilICSParserTestCase' => 'PhutilTestCase',
'PhutilICSWriter' => 'Phobject',
'PhutilICSWriterTestCase' => 'PhutilTestCase',
'PhutilInRequestKeyValueCache' => 'PhutilKeyValueCache',
'PhutilInvisibleSyntaxHighlighter' => 'Phobject',
'PhutilJIRAAuthAdapter' => 'PhutilOAuth1AuthAdapter',
'PhutilJSONFragmentLexerHighlighterTestCase' => 'PhutilTestCase',
'PhutilJavaCodeSnippetContextFreeGrammar' => 'PhutilCLikeCodeSnippetContextFreeGrammar',
'PhutilKeyValueCache' => 'Phobject',
'PhutilKeyValueCacheNamespace' => 'PhutilKeyValueCacheProxy',
'PhutilKeyValueCacheProfiler' => 'PhutilKeyValueCacheProxy',
'PhutilKeyValueCacheProxy' => 'PhutilKeyValueCache',
'PhutilKeyValueCacheStack' => 'PhutilKeyValueCache',
'PhutilKeyValueCacheTestCase' => 'PhutilTestCase',
'PhutilLDAPAuthAdapter' => 'PhutilAuthAdapter',
'PhutilLexerSyntaxHighlighter' => 'PhutilSyntaxHighlighter',
'PhutilLipsumContextFreeGrammar' => 'PhutilContextFreeGrammar',
'PhutilMarkupEngine' => 'Phobject',
'PhutilMarkupTestCase' => 'PhutilTestCase',
'PhutilMemcacheKeyValueCache' => 'PhutilKeyValueCache',
'PhutilOAuth1AuthAdapter' => 'PhutilAuthAdapter',
'PhutilOAuthAuthAdapter' => 'PhutilAuthAdapter',
'PhutilOnDiskKeyValueCache' => 'PhutilKeyValueCache',
'PhutilPHPCodeSnippetContextFreeGrammar' => 'PhutilCLikeCodeSnippetContextFreeGrammar',
'PhutilPHPFragmentLexerHighlighterTestCase' => 'PhutilTestCase',
'PhutilPhabricatorAuthAdapter' => 'PhutilOAuthAuthAdapter',
'PhutilProseDiff' => 'Phobject',
'PhutilProseDiffTestCase' => 'PhabricatorTestCase',
'PhutilProseDifferenceEngine' => 'Phobject',
'PhutilPygmentizeParser' => 'Phobject',
'PhutilPygmentizeParserTestCase' => 'PhutilTestCase',
'PhutilPygmentsSyntaxHighlighter' => 'Phobject',
'PhutilQueryString' => 'Phobject',
'PhutilRainbowSyntaxHighlighter' => 'Phobject',
'PhutilRealNameContextFreeGrammar' => 'PhutilContextFreeGrammar',
'PhutilRemarkupAnchorRule' => 'PhutilRemarkupRule',
'PhutilRemarkupBlockInterpreter' => 'Phobject',
'PhutilRemarkupBlockRule' => 'Phobject',
'PhutilRemarkupBlockStorage' => 'Phobject',
'PhutilRemarkupBoldRule' => 'PhutilRemarkupRule',
'PhutilRemarkupCodeBlockRule' => 'PhutilRemarkupBlockRule',
'PhutilRemarkupDefaultBlockRule' => 'PhutilRemarkupBlockRule',
'PhutilRemarkupDelRule' => 'PhutilRemarkupRule',
'PhutilRemarkupDocumentLinkRule' => 'PhutilRemarkupRule',
'PhutilRemarkupEngine' => 'PhutilMarkupEngine',
'PhutilRemarkupEngineTestCase' => 'PhutilTestCase',
'PhutilRemarkupEscapeRemarkupRule' => 'PhutilRemarkupRule',
'PhutilRemarkupEvalRule' => 'PhutilRemarkupRule',
'PhutilRemarkupHeaderBlockRule' => 'PhutilRemarkupBlockRule',
'PhutilRemarkupHighlightRule' => 'PhutilRemarkupRule',
'PhutilRemarkupHorizontalRuleBlockRule' => 'PhutilRemarkupBlockRule',
'PhutilRemarkupHyperlinkEngineExtension' => 'Phobject',
'PhutilRemarkupHyperlinkRef' => 'Phobject',
'PhutilRemarkupHyperlinkRule' => 'PhutilRemarkupRule',
'PhutilRemarkupInlineBlockRule' => 'PhutilRemarkupBlockRule',
'PhutilRemarkupInterpreterBlockRule' => 'PhutilRemarkupBlockRule',
'PhutilRemarkupItalicRule' => 'PhutilRemarkupRule',
'PhutilRemarkupLinebreaksRule' => 'PhutilRemarkupRule',
'PhutilRemarkupListBlockRule' => 'PhutilRemarkupBlockRule',
'PhutilRemarkupLiteralBlockRule' => 'PhutilRemarkupBlockRule',
'PhutilRemarkupMonospaceRule' => 'PhutilRemarkupRule',
'PhutilRemarkupNoteBlockRule' => 'PhutilRemarkupBlockRule',
'PhutilRemarkupQuotedBlockRule' => 'PhutilRemarkupBlockRule',
'PhutilRemarkupQuotesBlockRule' => 'PhutilRemarkupQuotedBlockRule',
'PhutilRemarkupReplyBlockRule' => 'PhutilRemarkupQuotedBlockRule',
'PhutilRemarkupRule' => 'Phobject',
'PhutilRemarkupSimpleTableBlockRule' => 'PhutilRemarkupBlockRule',
'PhutilRemarkupTableBlockRule' => 'PhutilRemarkupBlockRule',
'PhutilRemarkupTestInterpreterRule' => 'PhutilRemarkupBlockInterpreter',
'PhutilRemarkupUnderlineRule' => 'PhutilRemarkupRule',
'PhutilSafeHTML' => 'Phobject',
'PhutilSafeHTMLTestCase' => 'PhutilTestCase',
'PhutilSearchQueryCompiler' => 'Phobject',
'PhutilSearchQueryCompilerSyntaxException' => 'Exception',
'PhutilSearchQueryCompilerTestCase' => 'PhutilTestCase',
'PhutilSearchQueryToken' => 'Phobject',
'PhutilSearchStemmer' => 'Phobject',
'PhutilSearchStemmerTestCase' => 'PhutilTestCase',
'PhutilSlackAuthAdapter' => 'PhutilOAuthAuthAdapter',
'PhutilSprite' => 'Phobject',
'PhutilSpriteSheet' => 'Phobject',
'PhutilSyntaxHighlighter' => 'Phobject',
'PhutilSyntaxHighlighterEngine' => 'Phobject',
'PhutilSyntaxHighlighterException' => 'Exception',
'PhutilTranslatedHTMLTestCase' => 'PhutilTestCase',
'PhutilTwitchAuthAdapter' => 'PhutilOAuthAuthAdapter',
'PhutilTwitterAuthAdapter' => 'PhutilOAuth1AuthAdapter',
'PhutilWordPressAuthAdapter' => 'PhutilOAuthAuthAdapter',
'PhutilXHPASTSyntaxHighlighter' => 'Phobject',
'PhutilXHPASTSyntaxHighlighterFuture' => 'FutureProxy',
'PhutilXHPASTSyntaxHighlighterTestCase' => 'PhutilTestCase',
'PolicyLockOptionType' => 'PhabricatorConfigJSONOptionType',
'PonderAddAnswerView' => 'AphrontView',
'PonderAnswer' => array(
'PonderDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorMarkupInterface',
'PhabricatorPolicyInterface',
'PhabricatorFlaggableInterface',
'PhabricatorSubscribableInterface',
'PhabricatorDestructibleInterface',
),
'PonderAnswerCommentController' => 'PonderController',
'PonderAnswerContentTransaction' => 'PonderAnswerTransactionType',
'PonderAnswerEditController' => 'PonderController',
'PonderAnswerEditor' => 'PonderEditor',
'PonderAnswerHistoryController' => 'PonderController',
'PonderAnswerMailReceiver' => 'PhabricatorObjectMailReceiver',
'PonderAnswerPHIDType' => 'PhabricatorPHIDType',
'PonderAnswerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PonderAnswerQuestionIDTransaction' => 'PonderAnswerTransactionType',
'PonderAnswerReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PonderAnswerSaveController' => 'PonderController',
'PonderAnswerStatus' => 'PonderConstants',
'PonderAnswerStatusTransaction' => 'PonderAnswerTransactionType',
'PonderAnswerTransaction' => 'PhabricatorModularTransaction',
'PonderAnswerTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PonderAnswerTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PonderAnswerTransactionType' => 'PhabricatorModularTransactionType',
'PonderAnswerView' => 'AphrontTagView',
'PonderConstants' => 'Phobject',
'PonderController' => 'PhabricatorController',
'PonderDAO' => 'PhabricatorLiskDAO',
'PonderDefaultViewCapability' => 'PhabricatorPolicyCapability',
'PonderEditor' => 'PhabricatorApplicationTransactionEditor',
'PonderFooterView' => 'AphrontTagView',
'PonderModerateCapability' => 'PhabricatorPolicyCapability',
'PonderQuestion' => array(
'PonderDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorMarkupInterface',
'PhabricatorSubscribableInterface',
'PhabricatorFlaggableInterface',
'PhabricatorPolicyInterface',
'PhabricatorTokenReceiverInterface',
'PhabricatorProjectInterface',
'PhabricatorDestructibleInterface',
'PhabricatorSpacesInterface',
'PhabricatorFulltextInterface',
'PhabricatorFerretInterface',
),
'PonderQuestionAnswerTransaction' => 'PonderQuestionTransactionType',
'PonderQuestionAnswerWikiTransaction' => 'PonderQuestionTransactionType',
'PonderQuestionCommentController' => 'PonderController',
'PonderQuestionContentTransaction' => 'PonderQuestionTransactionType',
'PonderQuestionCreateMailReceiver' => 'PhabricatorApplicationMailReceiver',
'PonderQuestionEditController' => 'PonderController',
'PonderQuestionEditEngine' => 'PhabricatorEditEngine',
'PonderQuestionEditor' => 'PonderEditor',
'PonderQuestionFerretEngine' => 'PhabricatorFerretEngine',
'PonderQuestionFulltextEngine' => 'PhabricatorFulltextEngine',
'PonderQuestionHistoryController' => 'PonderController',
'PonderQuestionListController' => 'PonderController',
'PonderQuestionMailReceiver' => 'PhabricatorObjectMailReceiver',
'PonderQuestionPHIDType' => 'PhabricatorPHIDType',
'PonderQuestionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PonderQuestionReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PonderQuestionSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PonderQuestionStatus' => 'PonderConstants',
'PonderQuestionStatusController' => 'PonderController',
'PonderQuestionStatusTransaction' => 'PonderQuestionTransactionType',
'PonderQuestionTitleTransaction' => 'PonderQuestionTransactionType',
'PonderQuestionTransaction' => 'PhabricatorModularTransaction',
'PonderQuestionTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PonderQuestionTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PonderQuestionTransactionType' => 'PhabricatorModularTransactionType',
'PonderQuestionViewController' => 'PonderController',
'PonderRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'PonderSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'ProjectAddProjectsEmailCommand' => 'MetaMTAEmailTransactionCommand',
'ProjectBoardTaskCard' => 'Phobject',
'ProjectCanLockProjectsCapability' => 'PhabricatorPolicyCapability',
'ProjectColumnSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'ProjectConduitAPIMethod' => 'ConduitAPIMethod',
'ProjectCreateConduitAPIMethod' => 'ProjectConduitAPIMethod',
'ProjectCreateProjectsCapability' => 'PhabricatorPolicyCapability',
'ProjectDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension',
'ProjectDefaultEditCapability' => 'PhabricatorPolicyCapability',
'ProjectDefaultJoinCapability' => 'PhabricatorPolicyCapability',
'ProjectDefaultViewCapability' => 'PhabricatorPolicyCapability',
'ProjectEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod',
'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase',
'ProjectReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'ProjectSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'QueryFormattingTestCase' => 'PhabricatorTestCase',
'QueryFuture' => 'Future',
- 'ReleephAuthorFieldSpecification' => 'ReleephFieldSpecification',
- 'ReleephBranch' => array(
- 'ReleephDAO',
- 'PhabricatorApplicationTransactionInterface',
- 'PhabricatorPolicyInterface',
- ),
- 'ReleephBranchAccessController' => 'ReleephBranchController',
- 'ReleephBranchCommitFieldSpecification' => 'ReleephFieldSpecification',
- 'ReleephBranchController' => 'ReleephController',
- 'ReleephBranchCreateController' => 'ReleephProductController',
- 'ReleephBranchEditController' => 'ReleephBranchController',
- 'ReleephBranchEditor' => 'PhabricatorEditor',
- 'ReleephBranchHistoryController' => 'ReleephBranchController',
- 'ReleephBranchNamePreviewController' => 'ReleephController',
- 'ReleephBranchPHIDType' => 'PhabricatorPHIDType',
- 'ReleephBranchPreviewView' => 'AphrontFormControl',
- 'ReleephBranchQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
- 'ReleephBranchSearchEngine' => 'PhabricatorApplicationSearchEngine',
- 'ReleephBranchTemplate' => 'Phobject',
- 'ReleephBranchTransaction' => 'PhabricatorApplicationTransaction',
- 'ReleephBranchTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
- 'ReleephBranchViewController' => 'ReleephBranchController',
- 'ReleephCommitFinder' => 'Phobject',
- 'ReleephCommitFinderException' => 'Exception',
- 'ReleephCommitMessageFieldSpecification' => 'ReleephFieldSpecification',
- 'ReleephConduitAPIMethod' => 'ConduitAPIMethod',
- 'ReleephController' => 'PhabricatorController',
- 'ReleephDAO' => 'PhabricatorLiskDAO',
- 'ReleephDefaultFieldSelector' => 'ReleephFieldSelector',
- 'ReleephDependsOnFieldSpecification' => 'ReleephFieldSpecification',
- 'ReleephDiffChurnFieldSpecification' => 'ReleephFieldSpecification',
- 'ReleephDiffMessageFieldSpecification' => 'ReleephFieldSpecification',
- 'ReleephDiffSizeFieldSpecification' => 'ReleephFieldSpecification',
- 'ReleephFieldParseException' => 'Exception',
- 'ReleephFieldSelector' => 'Phobject',
- 'ReleephFieldSpecification' => array(
- 'PhabricatorCustomField',
- 'PhabricatorMarkupInterface',
- ),
- 'ReleephGetBranchesConduitAPIMethod' => 'ReleephConduitAPIMethod',
- 'ReleephIntentFieldSpecification' => 'ReleephFieldSpecification',
- 'ReleephLevelFieldSpecification' => 'ReleephFieldSpecification',
- 'ReleephOriginalCommitFieldSpecification' => 'ReleephFieldSpecification',
- 'ReleephProductActionController' => 'ReleephProductController',
- 'ReleephProductController' => 'ReleephController',
- 'ReleephProductCreateController' => 'ReleephProductController',
- 'ReleephProductEditController' => 'ReleephProductController',
- 'ReleephProductEditor' => 'PhabricatorApplicationTransactionEditor',
- 'ReleephProductHistoryController' => 'ReleephProductController',
- 'ReleephProductListController' => 'ReleephController',
- 'ReleephProductPHIDType' => 'PhabricatorPHIDType',
- 'ReleephProductQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
- 'ReleephProductSearchEngine' => 'PhabricatorApplicationSearchEngine',
- 'ReleephProductTransaction' => 'PhabricatorApplicationTransaction',
- 'ReleephProductTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
- 'ReleephProductViewController' => 'ReleephProductController',
- 'ReleephProject' => array(
- 'ReleephDAO',
- 'PhabricatorApplicationTransactionInterface',
- 'PhabricatorPolicyInterface',
- ),
- 'ReleephQueryBranchesConduitAPIMethod' => 'ReleephConduitAPIMethod',
- 'ReleephQueryProductsConduitAPIMethod' => 'ReleephConduitAPIMethod',
- 'ReleephQueryRequestsConduitAPIMethod' => 'ReleephConduitAPIMethod',
- 'ReleephReasonFieldSpecification' => 'ReleephFieldSpecification',
- 'ReleephRequest' => array(
- 'ReleephDAO',
- 'PhabricatorApplicationTransactionInterface',
- 'PhabricatorPolicyInterface',
- 'PhabricatorCustomFieldInterface',
- ),
- 'ReleephRequestActionController' => 'ReleephRequestController',
- 'ReleephRequestCommentController' => 'ReleephRequestController',
- 'ReleephRequestConduitAPIMethod' => 'ReleephConduitAPIMethod',
- 'ReleephRequestController' => 'ReleephController',
- 'ReleephRequestDifferentialCreateController' => 'ReleephController',
- 'ReleephRequestEditController' => 'ReleephBranchController',
- 'ReleephRequestMailReceiver' => 'PhabricatorObjectMailReceiver',
- 'ReleephRequestPHIDType' => 'PhabricatorPHIDType',
- 'ReleephRequestQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
- 'ReleephRequestReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
- 'ReleephRequestSearchEngine' => 'PhabricatorApplicationSearchEngine',
- 'ReleephRequestStatus' => 'Phobject',
- 'ReleephRequestTransaction' => 'PhabricatorApplicationTransaction',
- 'ReleephRequestTransactionComment' => 'PhabricatorApplicationTransactionComment',
- 'ReleephRequestTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
- 'ReleephRequestTransactionalEditor' => 'PhabricatorApplicationTransactionEditor',
- 'ReleephRequestTypeaheadControl' => 'AphrontFormControl',
- 'ReleephRequestTypeaheadController' => 'PhabricatorTypeaheadDatasourceController',
- 'ReleephRequestView' => 'AphrontView',
- 'ReleephRequestViewController' => 'ReleephBranchController',
- 'ReleephRequestorFieldSpecification' => 'ReleephFieldSpecification',
- 'ReleephRevisionFieldSpecification' => 'ReleephFieldSpecification',
- 'ReleephSeverityFieldSpecification' => 'ReleephLevelFieldSpecification',
- 'ReleephSummaryFieldSpecification' => 'ReleephFieldSpecification',
- 'ReleephWorkCanPushConduitAPIMethod' => 'ReleephConduitAPIMethod',
- 'ReleephWorkGetAuthorInfoConduitAPIMethod' => 'ReleephConduitAPIMethod',
- 'ReleephWorkGetBranchCommitMessageConduitAPIMethod' => 'ReleephConduitAPIMethod',
- 'ReleephWorkGetBranchConduitAPIMethod' => 'ReleephConduitAPIMethod',
- 'ReleephWorkGetCommitMessageConduitAPIMethod' => 'ReleephConduitAPIMethod',
- 'ReleephWorkNextRequestConduitAPIMethod' => 'ReleephConduitAPIMethod',
- 'ReleephWorkRecordConduitAPIMethod' => 'ReleephConduitAPIMethod',
- 'ReleephWorkRecordPickStatusConduitAPIMethod' => 'ReleephConduitAPIMethod',
'RemarkupProcessConduitAPIMethod' => 'ConduitAPIMethod',
+ 'RemarkupValue' => 'Phobject',
'RepositoryConduitAPIMethod' => 'ConduitAPIMethod',
'RepositoryQueryConduitAPIMethod' => 'RepositoryConduitAPIMethod',
'ShellLogView' => 'AphrontView',
'SlowvoteConduitAPIMethod' => 'ConduitAPIMethod',
'SlowvoteEmbedView' => 'AphrontView',
'SlowvoteInfoConduitAPIMethod' => 'SlowvoteConduitAPIMethod',
+ 'SlowvotePollResponseVisibility' => 'Phobject',
+ 'SlowvotePollStatus' => 'Phobject',
+ 'SlowvotePollVotingMethod' => 'Phobject',
'SlowvoteRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'SlowvoteSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'SubscriptionListDialogBuilder' => 'Phobject',
'SubscriptionListStringBuilder' => 'Phobject',
'TokenConduitAPIMethod' => 'ConduitAPIMethod',
'TokenGiveConduitAPIMethod' => 'TokenConduitAPIMethod',
'TokenGivenConduitAPIMethod' => 'TokenConduitAPIMethod',
'TokenQueryConduitAPIMethod' => 'TokenConduitAPIMethod',
'TransactionSearchConduitAPIMethod' => 'ConduitAPIMethod',
'UserConduitAPIMethod' => 'ConduitAPIMethod',
'UserDisableConduitAPIMethod' => 'UserConduitAPIMethod',
'UserEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'UserEnableConduitAPIMethod' => 'UserConduitAPIMethod',
'UserFindConduitAPIMethod' => 'UserConduitAPIMethod',
'UserQueryConduitAPIMethod' => 'UserConduitAPIMethod',
'UserSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'UserWhoAmIConduitAPIMethod' => 'UserConduitAPIMethod',
),
));
diff --git a/src/aphront/AphrontRequest.php b/src/aphront/AphrontRequest.php
index a3fa05cca2..f35f3f1279 100644
--- a/src/aphront/AphrontRequest.php
+++ b/src/aphront/AphrontRequest.php
@@ -1,970 +1,970 @@
<?php
/**
* @task data Accessing Request Data
* @task cookie Managing Cookies
* @task cluster Working With a Phabricator Cluster
*/
final class AphrontRequest extends Phobject {
// NOTE: These magic request-type parameters are automatically included in
// certain requests (e.g., by phabricator_form(), JX.Request,
// JX.Workflow, and ConduitClient) and help us figure out what sort of
// response the client expects.
const TYPE_AJAX = '__ajax__';
const TYPE_FORM = '__form__';
const TYPE_CONDUIT = '__conduit__';
const TYPE_WORKFLOW = '__wflow__';
const TYPE_CONTINUE = '__continue__';
const TYPE_PREVIEW = '__preview__';
const TYPE_HISEC = '__hisec__';
const TYPE_QUICKSAND = '__quicksand__';
private $host;
private $path;
private $requestData;
private $user;
private $applicationConfiguration;
private $site;
private $controller;
private $uriData = array();
private $cookiePrefix;
private $submitKey;
public function __construct($host, $path) {
$this->host = $host;
$this->path = $path;
}
public function setURIMap(array $uri_data) {
$this->uriData = $uri_data;
return $this;
}
public function getURIMap() {
return $this->uriData;
}
public function getURIData($key, $default = null) {
return idx($this->uriData, $key, $default);
}
/**
* Read line range parameter data from the request.
*
* Applications like Paste, Diffusion, and Harbormaster use "$12-14" in the
* URI to allow users to link to particular lines.
*
* @param string URI data key to pull line range information from.
* @param int|null Maximum length of the range.
* @return null|pair<int, int> Null, or beginning and end of the range.
*/
public function getURILineRange($key, $limit) {
$range = $this->getURIData($key);
return self::parseURILineRange($range, $limit);
}
public static function parseURILineRange($range, $limit) {
if (!strlen($range)) {
return null;
}
$range = explode('-', $range, 2);
foreach ($range as $key => $value) {
$value = (int)$value;
if (!$value) {
// If either value is "0", discard the range.
return null;
}
$range[$key] = $value;
}
// If the range is like "$10", treat it like "$10-10".
if (count($range) == 1) {
$range[] = head($range);
}
// If the range is "$7-5", treat it like "$5-7".
if ($range[1] < $range[0]) {
$range = array_reverse($range);
}
// If the user specified something like "$1-999999999" and we have a limit,
// clamp it to a more reasonable range.
if ($limit !== null) {
if ($range[1] - $range[0] > $limit) {
$range[1] = $range[0] + $limit;
}
}
return $range;
}
public function setApplicationConfiguration(
$application_configuration) {
$this->applicationConfiguration = $application_configuration;
return $this;
}
public function getApplicationConfiguration() {
return $this->applicationConfiguration;
}
public function setPath($path) {
$this->path = $path;
return $this;
}
public function getPath() {
return $this->path;
}
public function getHost() {
// The "Host" header may include a port number, or may be a malicious
// header in the form "realdomain.com:ignored@evil.com". Invoke the full
// parser to extract the real domain correctly. See here for coverage of
// a similar issue in Django:
//
// https://www.djangoproject.com/weblog/2012/oct/17/security/
$uri = new PhutilURI('http://'.$this->host);
return $uri->getDomain();
}
public function setSite(AphrontSite $site) {
$this->site = $site;
return $this;
}
public function getSite() {
return $this->site;
}
public function setController(AphrontController $controller) {
$this->controller = $controller;
return $this;
}
public function getController() {
return $this->controller;
}
/* -( Accessing Request Data )--------------------------------------------- */
/**
* @task data
*/
public function setRequestData(array $request_data) {
$this->requestData = $request_data;
return $this;
}
/**
* @task data
*/
public function getRequestData() {
return $this->requestData;
}
/**
* @task data
*/
public function getInt($name, $default = null) {
if (isset($this->requestData[$name])) {
// Converting from array to int is "undefined". Don't rely on whatever
// PHP decides to do.
if (is_array($this->requestData[$name])) {
return $default;
}
return (int)$this->requestData[$name];
} else {
return $default;
}
}
/**
* @task data
*/
public function getBool($name, $default = null) {
if (isset($this->requestData[$name])) {
if ($this->requestData[$name] === 'true') {
return true;
} else if ($this->requestData[$name] === 'false') {
return false;
} else {
return (bool)$this->requestData[$name];
}
} else {
return $default;
}
}
/**
* @task data
*/
public function getStr($name, $default = null) {
if (isset($this->requestData[$name])) {
$str = (string)$this->requestData[$name];
// Normalize newline craziness.
$str = str_replace(
array("\r\n", "\r"),
array("\n", "\n"),
$str);
return $str;
} else {
return $default;
}
}
/**
* @task data
*/
public function getJSONMap($name, $default = array()) {
if (!isset($this->requestData[$name])) {
return $default;
}
$raw_data = phutil_string_cast($this->requestData[$name]);
$raw_data = trim($raw_data);
if (!strlen($raw_data)) {
return $default;
}
if ($raw_data[0] !== '{') {
throw new Exception(
pht(
'Request parameter "%s" is not formatted properly. Expected a '.
'JSON object, but value does not start with "{".',
$name));
}
try {
$json_object = phutil_json_decode($raw_data);
} catch (PhutilJSONParserException $ex) {
throw new Exception(
pht(
'Request parameter "%s" is not formatted properly. Expected a '.
'JSON object, but encountered a syntax error: %s.',
$name,
$ex->getMessage()));
}
return $json_object;
}
/**
* @task data
*/
public function getArr($name, $default = array()) {
if (isset($this->requestData[$name]) &&
is_array($this->requestData[$name])) {
return $this->requestData[$name];
} else {
return $default;
}
}
/**
* @task data
*/
public function getStrList($name, $default = array()) {
if (!isset($this->requestData[$name])) {
return $default;
}
$list = $this->getStr($name);
$list = preg_split('/[\s,]+/', $list, $limit = -1, PREG_SPLIT_NO_EMPTY);
return $list;
}
/**
* @task data
*/
public function getExists($name) {
return array_key_exists($name, $this->requestData);
}
public function getFileExists($name) {
return isset($_FILES[$name]) &&
(idx($_FILES[$name], 'error') !== UPLOAD_ERR_NO_FILE);
}
public function isHTTPGet() {
return ($_SERVER['REQUEST_METHOD'] == 'GET');
}
public function isHTTPPost() {
return ($_SERVER['REQUEST_METHOD'] == 'POST');
}
public function isAjax() {
return $this->getExists(self::TYPE_AJAX) && !$this->isQuicksand();
}
public function isWorkflow() {
return $this->getExists(self::TYPE_WORKFLOW) && !$this->isQuicksand();
}
public function isQuicksand() {
return $this->getExists(self::TYPE_QUICKSAND);
}
public function isConduit() {
return $this->getExists(self::TYPE_CONDUIT);
}
public static function getCSRFTokenName() {
return '__csrf__';
}
public static function getCSRFHeaderName() {
return 'X-Phabricator-Csrf';
}
public static function getViaHeaderName() {
return 'X-Phabricator-Via';
}
public function validateCSRF() {
$token_name = self::getCSRFTokenName();
$token = $this->getStr($token_name);
// No token in the request, check the HTTP header which is added for Ajax
// requests.
if (empty($token)) {
$token = self::getHTTPHeader(self::getCSRFHeaderName());
}
$valid = $this->getUser()->validateCSRFToken($token);
if (!$valid) {
// Add some diagnostic details so we can figure out if some CSRF issues
// are JS problems or people accessing Ajax URIs directly with their
// browsers.
$info = array();
$info[] = pht(
- 'You are trying to save some data to Phabricator, but the request '.
- 'your browser made included an incorrect token. Reload the page '.
- 'and try again. You may need to clear your cookies.');
+ 'You are trying to save some data to permanent storage, but the '.
+ 'request your browser made included an incorrect token. Reload the '.
+ 'page and try again. You may need to clear your cookies.');
if ($this->isAjax()) {
$info[] = pht('This was an Ajax request.');
} else {
$info[] = pht('This was a Web request.');
}
if ($token) {
$info[] = pht('This request had an invalid CSRF token.');
} else {
$info[] = pht('This request had no CSRF token.');
}
// Give a more detailed explanation of how to avoid the exception
// in developer mode.
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
// TODO: Clean this up, see T1921.
$info[] = pht(
"To avoid this error, use %s to construct forms. If you are already ".
"using %s, make sure the form 'action' uses a relative URI (i.e., ".
"begins with a '%s'). Forms using absolute URIs do not include CSRF ".
"tokens, to prevent leaking tokens to external sites.\n\n".
"If this page performs writes which do not require CSRF protection ".
"(usually, filling caches or logging), you can use %s to ".
"temporarily bypass CSRF protection while writing. You should use ".
"this only for writes which can not be protected with normal CSRF ".
"mechanisms.\n\n".
"Some UI elements (like %s) also have methods which will allow you ".
"to render links as forms (like %s).",
'phabricator_form()',
'phabricator_form()',
'/',
'AphrontWriteGuard::beginScopedUnguardedWrites()',
'PhabricatorActionListView',
'setRenderAsForm(true)');
}
$message = implode("\n", $info);
// This should only be able to happen if you load a form, pull your
// internet for 6 hours, and then reconnect and immediately submit,
// but give the user some indication of what happened since the workflow
// is incredibly confusing otherwise.
throw new AphrontMalformedRequestException(
pht('Invalid Request (CSRF)'),
$message,
true);
}
return true;
}
public function isFormPost() {
$post = $this->getExists(self::TYPE_FORM) &&
!$this->getExists(self::TYPE_HISEC) &&
$this->isHTTPPost();
if (!$post) {
return false;
}
return $this->validateCSRF();
}
public function hasCSRF() {
try {
$this->validateCSRF();
return true;
} catch (AphrontMalformedRequestException $ex) {
return false;
}
}
public function isFormOrHisecPost() {
$post = $this->getExists(self::TYPE_FORM) &&
$this->isHTTPPost();
if (!$post) {
return false;
}
return $this->validateCSRF();
}
public function setCookiePrefix($prefix) {
$this->cookiePrefix = $prefix;
return $this;
}
private function getPrefixedCookieName($name) {
if (strlen($this->cookiePrefix)) {
return $this->cookiePrefix.'_'.$name;
} else {
return $name;
}
}
public function getCookie($name, $default = null) {
$name = $this->getPrefixedCookieName($name);
$value = idx($_COOKIE, $name, $default);
// Internally, PHP deletes cookies by setting them to the value 'deleted'
// with an expiration date in the past.
// At least in Safari, the browser may send this cookie anyway in some
// circumstances. After logging out, the 302'd GET to /login/ consistently
// includes deleted cookies on my local install. If a cookie value is
// literally 'deleted', pretend it does not exist.
if ($value === 'deleted') {
return null;
}
return $value;
}
public function clearCookie($name) {
$this->setCookieWithExpiration($name, '', time() - (60 * 60 * 24 * 30));
unset($_COOKIE[$name]);
}
/**
* Get the domain which cookies should be set on for this request, or null
* if the request does not correspond to a valid cookie domain.
*
* @return PhutilURI|null Domain URI, or null if no valid domain exists.
*
* @task cookie
*/
private function getCookieDomainURI() {
if (PhabricatorEnv::getEnvConfig('security.require-https') &&
!$this->isHTTPS()) {
return null;
}
$host = $this->getHost();
// If there's no base domain configured, just use whatever the request
// domain is. This makes setup easier, and we'll tell administrators to
// configure a base domain during the setup process.
$base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
if (!strlen($base_uri)) {
return new PhutilURI('http://'.$host.'/');
}
$alternates = PhabricatorEnv::getEnvConfig('phabricator.allowed-uris');
$allowed_uris = array_merge(
array($base_uri),
$alternates);
foreach ($allowed_uris as $allowed_uri) {
$uri = new PhutilURI($allowed_uri);
if ($uri->getDomain() == $host) {
return $uri;
}
}
return null;
}
/**
* Determine if security policy rules will allow cookies to be set when
* responding to the request.
*
* @return bool True if setCookie() will succeed. If this method returns
* false, setCookie() will throw.
*
* @task cookie
*/
public function canSetCookies() {
return (bool)$this->getCookieDomainURI();
}
/**
* Set a cookie which does not expire for a long time.
*
* To set a temporary cookie, see @{method:setTemporaryCookie}.
*
* @param string Cookie name.
* @param string Cookie value.
* @return this
* @task cookie
*/
public function setCookie($name, $value) {
$far_future = time() + (60 * 60 * 24 * 365 * 5);
return $this->setCookieWithExpiration($name, $value, $far_future);
}
/**
* Set a cookie which expires soon.
*
* To set a durable cookie, see @{method:setCookie}.
*
* @param string Cookie name.
* @param string Cookie value.
* @return this
* @task cookie
*/
public function setTemporaryCookie($name, $value) {
return $this->setCookieWithExpiration($name, $value, 0);
}
/**
* Set a cookie with a given expiration policy.
*
* @param string Cookie name.
* @param string Cookie value.
* @param int Epoch timestamp for cookie expiration.
* @return this
* @task cookie
*/
private function setCookieWithExpiration(
$name,
$value,
$expire) {
$is_secure = false;
$base_domain_uri = $this->getCookieDomainURI();
if (!$base_domain_uri) {
$configured_as = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
$accessed_as = $this->getHost();
throw new AphrontMalformedRequestException(
pht('Bad Host Header'),
pht(
- 'This Phabricator install is configured as "%s", but you are '.
- 'using the domain name "%s" to access a page which is trying to '.
- 'set a cookie. Access Phabricator on the configured primary '.
- 'domain or a configured alternate domain. Phabricator will not '.
- 'set cookies on other domains for security reasons.',
+ 'This server is configured as "%s", but you are using the domain '.
+ 'name "%s" to access a page which is trying to set a cookie. '.
+ 'Access this service on the configured primary domain or a '.
+ 'configured alternate domain. Cookies will not be set on other '.
+ 'domains for security reasons.',
$configured_as,
$accessed_as),
true);
}
$base_domain = $base_domain_uri->getDomain();
$is_secure = ($base_domain_uri->getProtocol() == 'https');
$name = $this->getPrefixedCookieName($name);
if (php_sapi_name() == 'cli') {
// Do nothing, to avoid triggering "Cannot modify header information"
// warnings.
// TODO: This is effectively a test for whether we're running in a unit
// test or not. Move this actual call to HTTPSink?
} else {
setcookie(
$name,
$value,
$expire,
$path = '/',
$base_domain,
$is_secure,
$http_only = true);
}
$_COOKIE[$name] = $value;
return $this;
}
public function setUser($user) {
$this->user = $user;
return $this;
}
public function getUser() {
return $this->user;
}
public function getViewer() {
return $this->user;
}
public function getRequestURI() {
$uri_path = phutil_escape_uri($this->getPath());
$uri_query = idx($_SERVER, 'QUERY_STRING', '');
return id(new PhutilURI($uri_path.'?'.$uri_query))
->removeQueryParam('__path__');
}
public function getAbsoluteRequestURI() {
$uri = $this->getRequestURI();
$uri->setDomain($this->getHost());
if ($this->isHTTPS()) {
$protocol = 'https';
} else {
$protocol = 'http';
}
$uri->setProtocol($protocol);
// If the request used a nonstandard port, preserve it while building the
// absolute URI.
// First, get the default port for the request protocol.
$default_port = id(new PhutilURI($protocol.'://example.com/'))
->getPortWithProtocolDefault();
// NOTE: See note in getHost() about malicious "Host" headers. This
// construction defuses some obscure potential attacks.
$port = id(new PhutilURI($protocol.'://'.$this->host))
->getPort();
if (($port !== null) && ($port !== $default_port)) {
$uri->setPort($port);
}
return $uri;
}
public function isDialogFormPost() {
return $this->isFormPost() && $this->getStr('__dialog__');
}
public function getRemoteAddress() {
$address = PhabricatorEnv::getRemoteAddress();
if (!$address) {
return null;
}
return $address->getAddress();
}
public function isHTTPS() {
if (empty($_SERVER['HTTPS'])) {
return false;
}
if (!strcasecmp($_SERVER['HTTPS'], 'off')) {
return false;
}
return true;
}
public function isContinueRequest() {
return $this->isFormOrHisecPost() && $this->getStr('__continue__');
}
public function isPreviewRequest() {
return $this->isFormPost() && $this->getStr('__preview__');
}
/**
* Get application request parameters in a flattened form suitable for
* inclusion in an HTTP request, excluding parameters with special meanings.
* This is primarily useful if you want to ask the user for more input and
* then resubmit their request.
*
* @return dict<string, string> Original request parameters.
*/
public function getPassthroughRequestParameters($include_quicksand = false) {
return self::flattenData(
$this->getPassthroughRequestData($include_quicksand));
}
/**
* Get request data other than "magic" parameters.
*
* @return dict<string, wild> Request data, with magic filtered out.
*/
public function getPassthroughRequestData($include_quicksand = false) {
$data = $this->getRequestData();
// Remove magic parameters like __dialog__ and __ajax__.
foreach ($data as $key => $value) {
if ($include_quicksand && $key == self::TYPE_QUICKSAND) {
continue;
}
if (!strncmp($key, '__', 2)) {
unset($data[$key]);
}
}
return $data;
}
/**
* Flatten an array of key-value pairs (possibly including arrays as values)
* into a list of key-value pairs suitable for submitting via HTTP request
* (with arrays flattened).
*
* @param dict<string, wild> Data to flatten.
* @return dict<string, string> Flat data suitable for inclusion in an HTTP
* request.
*/
public static function flattenData(array $data) {
$result = array();
foreach ($data as $key => $value) {
if (is_array($value)) {
foreach (self::flattenData($value) as $fkey => $fvalue) {
$fkey = '['.preg_replace('/(?=\[)|$/', ']', $fkey, $limit = 1);
$result[$key.$fkey] = $fvalue;
}
} else {
$result[$key] = (string)$value;
}
}
ksort($result);
return $result;
}
/**
* Read the value of an HTTP header from `$_SERVER`, or a similar datasource.
*
* This function accepts a canonical header name, like `"Accept-Encoding"`,
* and looks up the appropriate value in `$_SERVER` (in this case,
* `"HTTP_ACCEPT_ENCODING"`).
*
* @param string Canonical header name, like `"Accept-Encoding"`.
* @param wild Default value to return if header is not present.
* @param array? Read this instead of `$_SERVER`.
* @return string|wild Header value if present, or `$default` if not.
*/
public static function getHTTPHeader($name, $default = null, $data = null) {
// PHP mangles HTTP headers by uppercasing them and replacing hyphens with
// underscores, then prepending 'HTTP_'.
$php_index = strtoupper($name);
$php_index = str_replace('-', '_', $php_index);
$try_names = array();
$try_names[] = 'HTTP_'.$php_index;
if ($php_index == 'CONTENT_TYPE' || $php_index == 'CONTENT_LENGTH') {
// These headers may be available under alternate names. See
// http://www.php.net/manual/en/reserved.variables.server.php#110763
$try_names[] = $php_index;
}
if ($data === null) {
$data = $_SERVER;
}
foreach ($try_names as $try_name) {
if (array_key_exists($try_name, $data)) {
return $data[$try_name];
}
}
return $default;
}
/* -( Working With a Phabricator Cluster )--------------------------------- */
/**
* Is this a proxied request originating from within the Phabricator cluster?
*
* IMPORTANT: This means the request is dangerous!
*
* These requests are **more dangerous** than normal requests (they can not
* be safely proxied, because proxying them may cause a loop). Cluster
* requests are not guaranteed to come from a trusted source, and should
* never be treated as safer than normal requests. They are strictly less
* safe.
*/
public function isProxiedClusterRequest() {
return (bool)self::getHTTPHeader('X-Phabricator-Cluster');
}
/**
* Build a new @{class:HTTPSFuture} which proxies this request to another
* node in the cluster.
*
* IMPORTANT: This is very dangerous!
*
* The future forwards authentication information present in the request.
* Proxied requests must only be sent to trusted hosts. (We attempt to
* enforce this.)
*
* This is not a general-purpose proxying method; it is a specialized
* method with niche applications and severe security implications.
*
* @param string URI identifying the host we are proxying the request to.
* @return HTTPSFuture New proxy future.
*
* @phutil-external-symbol class PhabricatorStartup
*/
public function newClusterProxyFuture($uri) {
$uri = new PhutilURI($uri);
$domain = $uri->getDomain();
$ip = gethostbyname($domain);
if (!$ip) {
throw new Exception(
pht(
'Unable to resolve domain "%s"!',
$domain));
}
if (!PhabricatorEnv::isClusterAddress($ip)) {
throw new Exception(
pht(
'Refusing to proxy a request to IP address ("%s") which is not '.
'in the cluster address block (this address was derived by '.
'resolving the domain "%s").',
$ip,
$domain));
}
$uri->setPath($this->getPath());
$uri->removeAllQueryParams();
foreach (self::flattenData($_GET) as $query_key => $query_value) {
$uri->appendQueryParam($query_key, $query_value);
}
$input = PhabricatorStartup::getRawInput();
$future = id(new HTTPSFuture($uri))
->addHeader('Host', self::getHost())
->addHeader('X-Phabricator-Cluster', true)
->setMethod($_SERVER['REQUEST_METHOD'])
->write($input);
if (isset($_SERVER['PHP_AUTH_USER'])) {
$future->setHTTPBasicAuthCredentials(
$_SERVER['PHP_AUTH_USER'],
new PhutilOpaqueEnvelope(idx($_SERVER, 'PHP_AUTH_PW', '')));
}
$headers = array();
$seen = array();
// NOTE: apache_request_headers() might provide a nicer way to do this,
// but isn't available under FCGI until PHP 5.4.0.
foreach ($_SERVER as $key => $value) {
if (!preg_match('/^HTTP_/', $key)) {
continue;
}
// Unmangle the header as best we can.
$key = substr($key, strlen('HTTP_'));
$key = str_replace('_', ' ', $key);
$key = strtolower($key);
$key = ucwords($key);
$key = str_replace(' ', '-', $key);
// By default, do not forward headers.
$should_forward = false;
// Forward "X-Hgarg-..." headers.
if (preg_match('/^X-Hgarg-/', $key)) {
$should_forward = true;
}
if ($should_forward) {
$headers[] = array($key, $value);
$seen[$key] = true;
}
}
// In some situations, this may not be mapped into the HTTP_X constants.
// CONTENT_LENGTH is similarly affected, but we trust cURL to take care
// of that if it matters, since we're handing off a request body.
if (empty($seen['Content-Type'])) {
if (isset($_SERVER['CONTENT_TYPE'])) {
$headers[] = array('Content-Type', $_SERVER['CONTENT_TYPE']);
}
}
foreach ($headers as $header) {
list($key, $value) = $header;
switch ($key) {
case 'Host':
case 'Authorization':
// Don't forward these headers, we've already handled them elsewhere.
unset($headers[$key]);
break;
default:
break;
}
}
foreach ($headers as $header) {
list($key, $value) = $header;
$future->addHeader($key, $value);
}
return $future;
}
public function updateEphemeralCookies() {
$submit_cookie = PhabricatorCookies::COOKIE_SUBMIT;
$submit_key = $this->getCookie($submit_cookie);
if (strlen($submit_key)) {
$this->clearCookie($submit_cookie);
$this->submitKey = $submit_key;
}
}
public function getSubmitKey() {
return $this->submitKey;
}
}
diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php
index 9e22ca7f47..550a5a0316 100644
--- a/src/aphront/configuration/AphrontApplicationConfiguration.php
+++ b/src/aphront/configuration/AphrontApplicationConfiguration.php
@@ -1,879 +1,879 @@
<?php
/**
* @task routing URI Routing
* @task response Response Handling
* @task exception Exception Handling
*/
final class AphrontApplicationConfiguration
extends Phobject {
private $request;
private $host;
private $path;
private $console;
public function buildRequest() {
$parser = new PhutilQueryStringParser();
$data = array();
$data += $_POST;
$data += $parser->parseQueryString(idx($_SERVER, 'QUERY_STRING', ''));
$cookie_prefix = PhabricatorEnv::getEnvConfig('phabricator.cookie-prefix');
$request = new AphrontRequest($this->getHost(), $this->getPath());
$request->setRequestData($data);
$request->setApplicationConfiguration($this);
$request->setCookiePrefix($cookie_prefix);
$request->updateEphemeralCookies();
return $request;
}
public function buildRedirectController($uri, $external) {
return array(
new PhabricatorRedirectController(),
array(
'uri' => $uri,
'external' => $external,
),
);
}
public function setRequest(AphrontRequest $request) {
$this->request = $request;
return $this;
}
public function getRequest() {
return $this->request;
}
public function getConsole() {
return $this->console;
}
public function setConsole($console) {
$this->console = $console;
return $this;
}
public function setHost($host) {
$this->host = $host;
return $this;
}
public function getHost() {
return $this->host;
}
public function setPath($path) {
$this->path = $path;
return $this;
}
public function getPath() {
return $this->path;
}
/**
* @phutil-external-symbol class PhabricatorStartup
*/
public static function runHTTPRequest(AphrontHTTPSink $sink) {
- if (isset($_SERVER['HTTP_X_PHABRICATOR_SELFCHECK'])) {
+ if (isset($_SERVER['HTTP_X_SETUP_SELFCHECK'])) {
$response = self::newSelfCheckResponse();
return self::writeResponse($sink, $response);
}
PhabricatorStartup::beginStartupPhase('multimeter');
$multimeter = MultimeterControl::newInstance();
$multimeter->setEventContext('<http-init>');
$multimeter->setEventViewer('<none>');
// Build a no-op write guard for the setup phase. We'll replace this with a
// real write guard later on, but we need to survive setup and build a
// request object first.
$write_guard = new AphrontWriteGuard('id');
PhabricatorStartup::beginStartupPhase('preflight');
$response = PhabricatorSetupCheck::willPreflightRequest();
if ($response) {
return self::writeResponse($sink, $response);
}
PhabricatorStartup::beginStartupPhase('env.init');
self::readHTTPPOSTData();
try {
PhabricatorEnv::initializeWebEnvironment();
$database_exception = null;
} catch (PhabricatorClusterStrandedException $ex) {
$database_exception = $ex;
}
// If we're in developer mode, set a flag so that top-level exception
// handlers can add more information.
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
$sink->setShowStackTraces(true);
}
if ($database_exception) {
$issue = PhabricatorSetupIssue::newDatabaseConnectionIssue(
$database_exception,
true);
$response = PhabricatorSetupCheck::newIssueResponse($issue);
return self::writeResponse($sink, $response);
}
$multimeter->setSampleRate(
PhabricatorEnv::getEnvConfig('debug.sample-rate'));
$debug_time_limit = PhabricatorEnv::getEnvConfig('debug.time-limit');
if ($debug_time_limit) {
PhabricatorStartup::setDebugTimeLimit($debug_time_limit);
}
// This is the earliest we can get away with this, we need env config first.
PhabricatorStartup::beginStartupPhase('log.access');
PhabricatorAccessLog::init();
$access_log = PhabricatorAccessLog::getLog();
PhabricatorStartup::setAccessLog($access_log);
$address = PhabricatorEnv::getRemoteAddress();
if ($address) {
$address_string = $address->getAddress();
} else {
$address_string = '-';
}
$access_log->setData(
array(
'R' => AphrontRequest::getHTTPHeader('Referer', '-'),
'r' => $address_string,
'M' => idx($_SERVER, 'REQUEST_METHOD', '-'),
));
DarkConsoleXHProfPluginAPI::hookProfiler();
// We just activated the profiler, so we don't need to keep track of
// startup phases anymore: it can take over from here.
PhabricatorStartup::beginStartupPhase('startup.done');
DarkConsoleErrorLogPluginAPI::registerErrorHandler();
$response = PhabricatorSetupCheck::willProcessRequest();
if ($response) {
return self::writeResponse($sink, $response);
}
$host = AphrontRequest::getHTTPHeader('Host');
$path = PhabricatorStartup::getRequestPath();
$application = new self();
$application->setHost($host);
$application->setPath($path);
$request = $application->buildRequest();
// Now that we have a request, convert the write guard into one which
// actually checks CSRF tokens.
$write_guard->dispose();
$write_guard = new AphrontWriteGuard(array($request, 'validateCSRF'));
// Build the server URI implied by the request headers. If an administrator
// has not configured "phabricator.base-uri" yet, we'll use this to generate
// links.
$request_protocol = ($request->isHTTPS() ? 'https' : 'http');
$request_base_uri = "{$request_protocol}://{$host}/";
PhabricatorEnv::setRequestBaseURI($request_base_uri);
$access_log->setData(
array(
'U' => (string)$request->getRequestURI()->getPath(),
));
$processing_exception = null;
try {
$response = $application->processRequest(
$request,
$access_log,
$sink,
$multimeter);
$response_code = $response->getHTTPResponseCode();
} catch (Exception $ex) {
$processing_exception = $ex;
$response_code = 500;
}
$write_guard->dispose();
$access_log->setData(
array(
'c' => $response_code,
'T' => PhabricatorStartup::getMicrosecondsSinceStart(),
));
$multimeter->newEvent(
MultimeterEvent::TYPE_REQUEST_TIME,
$multimeter->getEventContext(),
PhabricatorStartup::getMicrosecondsSinceStart());
$access_log->write();
$multimeter->saveEvents();
DarkConsoleXHProfPluginAPI::saveProfilerSample($access_log);
PhabricatorStartup::disconnectRateLimits(
array(
'viewer' => $request->getUser(),
));
if ($processing_exception) {
throw $processing_exception;
}
}
public function processRequest(
AphrontRequest $request,
PhutilDeferredLog $access_log,
AphrontHTTPSink $sink,
MultimeterControl $multimeter) {
$this->setRequest($request);
list($controller, $uri_data) = $this->buildController();
$controller_class = get_class($controller);
$access_log->setData(
array(
'C' => $controller_class,
));
$multimeter->setEventContext('web.'.$controller_class);
$request->setController($controller);
$request->setURIMap($uri_data);
$controller->setRequest($request);
// If execution throws an exception and then trying to render that
// exception throws another exception, we want to show the original
// exception, as it is likely the root cause of the rendering exception.
$original_exception = null;
try {
$response = $controller->willBeginExecution();
if ($request->getUser() && $request->getUser()->getPHID()) {
$access_log->setData(
array(
'u' => $request->getUser()->getUserName(),
'P' => $request->getUser()->getPHID(),
));
$multimeter->setEventViewer('user.'.$request->getUser()->getPHID());
}
if (!$response) {
$controller->willProcessRequest($uri_data);
$response = $controller->handleRequest($request);
$this->validateControllerResponse($controller, $response);
}
} catch (Exception $ex) {
$original_exception = $ex;
} catch (Throwable $ex) {
$original_exception = $ex;
}
$response_exception = null;
try {
if ($original_exception) {
$response = $this->handleThrowable($original_exception);
}
$response = $this->produceResponse($request, $response);
$response = $controller->willSendResponse($response);
$response->setRequest($request);
self::writeResponse($sink, $response);
} catch (Exception $ex) {
$response_exception = $ex;
} catch (Throwable $ex) {
$response_exception = $ex;
}
if ($response_exception) {
// If we encountered an exception while building a normal response, then
// encountered another exception while building a response for the first
// exception, throw an aggregate exception that will be unpacked by the
// higher-level handler. This is above our pay grade.
if ($original_exception) {
throw new PhutilAggregateException(
pht(
'Encountered a processing exception, then another exception when '.
'trying to build a response for the first exception.'),
array(
$response_exception,
$original_exception,
));
}
// If we built a response successfully and then ran into an exception
// trying to render it, try to handle and present that exception to the
// user using the standard handler.
// The problem here might be in rendering (more common) or in the actual
// response mechanism (less common). If it's in rendering, we can likely
// still render a nice exception page: the majority of rendering issues
// are in main page content, not content shared with the exception page.
$handling_exception = null;
try {
$response = $this->handleThrowable($response_exception);
$response = $this->produceResponse($request, $response);
$response = $controller->willSendResponse($response);
$response->setRequest($request);
self::writeResponse($sink, $response);
} catch (Exception $ex) {
$handling_exception = $ex;
} catch (Throwable $ex) {
$handling_exception = $ex;
}
// If we didn't have any luck with that, raise the original response
// exception. As above, this is the root cause exception and more likely
// to be useful. This will go to the fallback error handler at top
// level.
if ($handling_exception) {
throw $response_exception;
}
}
return $response;
}
private static function writeResponse(
AphrontHTTPSink $sink,
AphrontResponse $response) {
$unexpected_output = PhabricatorStartup::endOutputCapture();
if ($unexpected_output) {
$unexpected_output = pht(
"Unexpected output:\n\n%s",
$unexpected_output);
phlog($unexpected_output);
if ($response instanceof AphrontWebpageResponse) {
$response->setUnexpectedOutput($unexpected_output);
}
}
$sink->writeResponse($response);
}
/* -( URI Routing )-------------------------------------------------------- */
/**
* Build a controller to respond to the request.
*
* @return pair<AphrontController,dict> Controller and dictionary of request
* parameters.
* @task routing
*/
private function buildController() {
$request = $this->getRequest();
// If we're configured to operate in cluster mode, reject requests which
// were not received on a cluster interface.
//
// For example, a host may have an internal address like "170.0.0.1", and
// also have a public address like "51.23.95.16". Assuming the cluster
// is configured on a range like "170.0.0.0/16", we want to reject the
// requests received on the public interface.
//
// Ideally, nodes in a cluster should only be listening on internal
// interfaces, but they may be configured in such a way that they also
// listen on external interfaces, since this is easy to forget about or
// get wrong. As a broad security measure, reject requests received on any
// interfaces which aren't on the whitelist.
$cluster_addresses = PhabricatorEnv::getEnvConfig('cluster.addresses');
if ($cluster_addresses) {
$server_addr = idx($_SERVER, 'SERVER_ADDR');
if (!$server_addr) {
if (php_sapi_name() == 'cli') {
// This is a command line script (probably something like a unit
// test) so it's fine that we don't have SERVER_ADDR defined.
} else {
throw new AphrontMalformedRequestException(
pht('No %s', 'SERVER_ADDR'),
pht(
- 'Phabricator is configured to operate in cluster mode, but '.
+ 'This service is configured to operate in cluster mode, but '.
'%s is not defined in the request context. Your webserver '.
- 'configuration needs to forward %s to PHP so Phabricator can '.
+ 'configuration needs to forward %s to PHP so the software can '.
'reject requests received on external interfaces.',
'SERVER_ADDR',
'SERVER_ADDR'));
}
} else {
if (!PhabricatorEnv::isClusterAddress($server_addr)) {
throw new AphrontMalformedRequestException(
pht('External Interface'),
pht(
- 'Phabricator is configured in cluster mode and the address '.
+ 'This service is configured in cluster mode and the address '.
'this request was received on ("%s") is not whitelisted as '.
'a cluster address.',
$server_addr));
}
}
}
$site = $this->buildSiteForRequest($request);
if ($site->shouldRequireHTTPS()) {
if (!$request->isHTTPS()) {
// Don't redirect intracluster requests: doing so drops headers and
// parameters, imposes a performance penalty, and indicates a
// misconfiguration.
if ($request->isProxiedClusterRequest()) {
throw new AphrontMalformedRequestException(
pht('HTTPS Required'),
pht(
'This request reached a site which requires HTTPS, but the '.
'request is not marked as HTTPS.'));
}
$https_uri = $request->getRequestURI();
$https_uri->setDomain($request->getHost());
$https_uri->setProtocol('https');
// In this scenario, we'll be redirecting to HTTPS using an absolute
// URI, so we need to permit an external redirect.
return $this->buildRedirectController($https_uri, true);
}
}
$maps = $site->getRoutingMaps();
$path = $request->getPath();
$result = $this->routePath($maps, $path);
if ($result) {
return $result;
}
// If we failed to match anything but don't have a trailing slash, try
// to add a trailing slash and issue a redirect if that resolves.
// NOTE: We only do this for GET, since redirects switch to GET and drop
// data like POST parameters.
if (!preg_match('@/$@', $path) && $request->isHTTPGet()) {
$result = $this->routePath($maps, $path.'/');
if ($result) {
$target_uri = $request->getAbsoluteRequestURI();
// We need to restore URI encoding because the webserver has
// interpreted it. For example, this allows us to redirect a path
// like `/tag/aa%20bb` to `/tag/aa%20bb/`, which may eventually be
// resolved meaningfully by an application.
$target_path = phutil_escape_uri($path.'/');
$target_uri->setPath($target_path);
$target_uri = (string)$target_uri;
return $this->buildRedirectController($target_uri, true);
}
}
$result = $site->new404Controller($request);
if ($result) {
return array($result, array());
}
throw new Exception(
pht(
'Aphront site ("%s") failed to build a 404 controller.',
get_class($site)));
}
/**
* Map a specific path to the corresponding controller. For a description
* of routing, see @{method:buildController}.
*
* @param list<AphrontRoutingMap> List of routing maps.
* @param string Path to route.
* @return pair<AphrontController,dict> Controller and dictionary of request
* parameters.
* @task routing
*/
private function routePath(array $maps, $path) {
foreach ($maps as $map) {
$result = $map->routePath($path);
if ($result) {
return array($result->getController(), $result->getURIData());
}
}
}
private function buildSiteForRequest(AphrontRequest $request) {
$sites = PhabricatorSite::getAllSites();
$site = null;
foreach ($sites as $candidate) {
$site = $candidate->newSiteForRequest($request);
if ($site) {
break;
}
}
if (!$site) {
$path = $request->getPath();
$host = $request->getHost();
throw new AphrontMalformedRequestException(
pht('Site Not Found'),
pht(
'This request asked for "%s" on host "%s", but no site is '.
'configured which can serve this request.',
$path,
$host),
true);
}
$request->setSite($site);
return $site;
}
/* -( Response Handling )-------------------------------------------------- */
/**
* Tests if a response is of a valid type.
*
* @param wild Supposedly valid response.
* @return bool True if the object is of a valid type.
* @task response
*/
private function isValidResponseObject($response) {
if ($response instanceof AphrontResponse) {
return true;
}
if ($response instanceof AphrontResponseProducerInterface) {
return true;
}
return false;
}
/**
* Verifies that the return value from an @{class:AphrontController} is
* of an allowed type.
*
* @param AphrontController Controller which returned the response.
* @param wild Supposedly valid response.
* @return void
* @task response
*/
private function validateControllerResponse(
AphrontController $controller,
$response) {
if ($this->isValidResponseObject($response)) {
return;
}
throw new Exception(
pht(
'Controller "%s" returned an invalid response from call to "%s". '.
'This method must return an object of class "%s", or an object '.
'which implements the "%s" interface.',
get_class($controller),
'handleRequest()',
'AphrontResponse',
'AphrontResponseProducerInterface'));
}
/**
* Verifies that the return value from an
* @{class:AphrontResponseProducerInterface} is of an allowed type.
*
* @param AphrontResponseProducerInterface Object which produced
* this response.
* @param wild Supposedly valid response.
* @return void
* @task response
*/
private function validateProducerResponse(
AphrontResponseProducerInterface $producer,
$response) {
if ($this->isValidResponseObject($response)) {
return;
}
throw new Exception(
pht(
'Producer "%s" returned an invalid response from call to "%s". '.
'This method must return an object of class "%s", or an object '.
'which implements the "%s" interface.',
get_class($producer),
'produceAphrontResponse()',
'AphrontResponse',
'AphrontResponseProducerInterface'));
}
/**
* Verifies that the return value from an
* @{class:AphrontRequestExceptionHandler} is of an allowed type.
*
* @param AphrontRequestExceptionHandler Object which produced this
* response.
* @param wild Supposedly valid response.
* @return void
* @task response
*/
private function validateErrorHandlerResponse(
AphrontRequestExceptionHandler $handler,
$response) {
if ($this->isValidResponseObject($response)) {
return;
}
throw new Exception(
pht(
'Exception handler "%s" returned an invalid response from call to '.
'"%s". This method must return an object of class "%s", or an object '.
'which implements the "%s" interface.',
get_class($handler),
'handleRequestException()',
'AphrontResponse',
'AphrontResponseProducerInterface'));
}
/**
* Resolves a response object into an @{class:AphrontResponse}.
*
* Controllers are permitted to return actual responses of class
* @{class:AphrontResponse}, or other objects which implement
* @{interface:AphrontResponseProducerInterface} and can produce a response.
*
* If a controller returns a response producer, invoke it now and produce
* the real response.
*
* @param AphrontRequest Request being handled.
* @param AphrontResponse|AphrontResponseProducerInterface Response, or
* response producer.
* @return AphrontResponse Response after any required production.
* @task response
*/
private function produceResponse(AphrontRequest $request, $response) {
$original = $response;
// Detect cycles on the exact same objects. It's still possible to produce
// infinite responses as long as they're all unique, but we can only
// reasonably detect cycles, not guarantee that response production halts.
$seen = array();
while (true) {
// NOTE: It is permissible for an object to be both a response and a
// response producer. If so, being a producer is "stronger". This is
// used by AphrontProxyResponse.
// If this response is a valid response, hand over the request first.
if ($response instanceof AphrontResponse) {
$response->setRequest($request);
}
// If this isn't a producer, we're all done.
if (!($response instanceof AphrontResponseProducerInterface)) {
break;
}
$hash = spl_object_hash($response);
if (isset($seen[$hash])) {
throw new Exception(
pht(
'Failure while producing response for object of class "%s": '.
'encountered production cycle (identical object, of class "%s", '.
'was produced twice).',
get_class($original),
get_class($response)));
}
$seen[$hash] = true;
$new_response = $response->produceAphrontResponse();
$this->validateProducerResponse($response, $new_response);
$response = $new_response;
}
return $response;
}
/* -( Error Handling )----------------------------------------------------- */
/**
* Convert an exception which has escaped the controller into a response.
*
* This method delegates exception handling to available subclasses of
* @{class:AphrontRequestExceptionHandler}.
*
* @param Throwable Exception which needs to be handled.
* @return wild Response or response producer, or null if no available
* handler can produce a response.
* @task exception
*/
private function handleThrowable($throwable) {
$handlers = AphrontRequestExceptionHandler::getAllHandlers();
$request = $this->getRequest();
foreach ($handlers as $handler) {
if ($handler->canHandleRequestThrowable($request, $throwable)) {
$response = $handler->handleRequestThrowable($request, $throwable);
$this->validateErrorHandlerResponse($handler, $response);
return $response;
}
}
throw $throwable;
}
private static function newSelfCheckResponse() {
$path = PhabricatorStartup::getRequestPath();
$query = idx($_SERVER, 'QUERY_STRING', '');
$pairs = id(new PhutilQueryStringParser())
->parseQueryStringToPairList($query);
$params = array();
foreach ($pairs as $v) {
$params[] = array(
'name' => $v[0],
'value' => $v[1],
);
}
$raw_input = @file_get_contents('php://input');
if ($raw_input !== false) {
$base64_input = base64_encode($raw_input);
} else {
$base64_input = null;
}
$result = array(
'path' => $path,
'params' => $params,
'user' => idx($_SERVER, 'PHP_AUTH_USER'),
'pass' => idx($_SERVER, 'PHP_AUTH_PW'),
'raw.base64' => $base64_input,
// This just makes sure that the response compresses well, so reasonable
// algorithms should want to gzip or deflate it.
'filler' => str_repeat('Q', 1024 * 16),
);
return id(new AphrontJSONResponse())
->setAddJSONShield(false)
->setContent($result);
}
private static function readHTTPPOSTData() {
$request_method = idx($_SERVER, 'REQUEST_METHOD');
if ($request_method === 'PUT') {
// For PUT requests, do nothing: in particular, do NOT read input. This
// allows us to stream input later and process very large PUT requests,
// like those coming from Git LFS.
return;
}
// For POST requests, we're going to read the raw input ourselves here
// if we can. Among other things, this corrects variable names with
// the "." character in them, which PHP normally converts into "_".
// If "enable_post_data_reading" is on, the documentation suggests we
// can not read the body. In practice, we seem to be able to. This may
// need to be resolved at some point, likely by instructing installs
// to disable this option.
// If the content type is "multipart/form-data", we need to build both
// $_POST and $_FILES, which is involved. The body itself is also more
// difficult to parse than other requests.
$raw_input = PhabricatorStartup::getRawInput();
$parser = new PhutilQueryStringParser();
if (strlen($raw_input)) {
$content_type = idx($_SERVER, 'CONTENT_TYPE');
$is_multipart = preg_match('@^multipart/form-data@i', $content_type);
if ($is_multipart) {
$multipart_parser = id(new AphrontMultipartParser())
->setContentType($content_type);
$multipart_parser->beginParse();
$multipart_parser->continueParse($raw_input);
$parts = $multipart_parser->endParse();
// We're building and then parsing a query string so that requests
// with arrays (like "x[]=apple&x[]=banana") work correctly. This also
// means we can't use "phutil_build_http_querystring()", since it
// can't build a query string with duplicate names.
$query_string = array();
foreach ($parts as $part) {
if (!$part->isVariable()) {
continue;
}
$name = $part->getName();
$value = $part->getVariableValue();
$query_string[] = rawurlencode($name).'='.rawurlencode($value);
}
$query_string = implode('&', $query_string);
$post = $parser->parseQueryString($query_string);
$files = array();
foreach ($parts as $part) {
if ($part->isVariable()) {
continue;
}
$files[$part->getName()] = $part->getPHPFileDictionary();
}
$_FILES = $files;
} else {
$post = $parser->parseQueryString($raw_input);
}
$_POST = $post;
PhabricatorStartup::rebuildRequest();
} else if ($_POST) {
$post = filter_input_array(INPUT_POST, FILTER_UNSAFE_RAW);
if (is_array($post)) {
$_POST = $post;
PhabricatorStartup::rebuildRequest();
}
}
}
}
diff --git a/src/aphront/httpparametertype/AphrontJSONHTTPParameterType.php b/src/aphront/httpparametertype/AphrontJSONHTTPParameterType.php
new file mode 100644
index 0000000000..3a1081bd75
--- /dev/null
+++ b/src/aphront/httpparametertype/AphrontJSONHTTPParameterType.php
@@ -0,0 +1,31 @@
+<?php
+
+final class AphrontJSONHTTPParameterType
+ extends AphrontHTTPParameterType {
+
+ protected function getParameterDefault() {
+ return array();
+ }
+
+ protected function getParameterValue(AphrontRequest $request, $key) {
+ $str = $request->getStr($key);
+ return phutil_json_decode($str);
+ }
+
+ protected function getParameterTypeName() {
+ return 'string (json object)';
+ }
+
+ protected function getParameterFormatDescriptions() {
+ return array(
+ pht('A JSON-encoded object.'),
+ );
+ }
+
+ protected function getParameterExamples() {
+ return array(
+ 'v={...}',
+ );
+ }
+
+}
diff --git a/src/aphront/httpparametertype/AphrontRemarkupHTTPParameterType.php b/src/aphront/httpparametertype/AphrontRemarkupHTTPParameterType.php
new file mode 100644
index 0000000000..7970878f49
--- /dev/null
+++ b/src/aphront/httpparametertype/AphrontRemarkupHTTPParameterType.php
@@ -0,0 +1,50 @@
+<?php
+
+final class AphrontRemarkupHTTPParameterType
+ extends AphrontHTTPParameterType {
+
+ protected function getParameterDefault() {
+ return $this->newRemarkupValue();
+ }
+
+ protected function getParameterValue(AphrontRequest $request, $key) {
+ $corpus_key = $key;
+ $corpus_type = new AphrontStringHTTPParameterType();
+ $corpus_value = $this->getValueWithType(
+ $corpus_type,
+ $request,
+ $corpus_key);
+
+ $metadata_key = $key.'_metadata';
+ $metadata_type = new AphrontJSONHTTPParameterType();
+ $metadata_value = $this->getValueWithType(
+ $metadata_type,
+ $request,
+ $metadata_key);
+
+ return $this->newRemarkupValue()
+ ->setCorpus($corpus_value)
+ ->setMetadata($metadata_value);
+ }
+
+ protected function getParameterTypeName() {
+ return 'string (remarkup)';
+ }
+
+ protected function getParameterFormatDescriptions() {
+ return array(
+ pht('Remarkup text.'),
+ );
+ }
+
+ protected function getParameterExamples() {
+ return array(
+ 'v=Lorem...',
+ );
+ }
+
+ private function newRemarkupValue() {
+ return new RemarkupValue();
+ }
+
+}
diff --git a/src/applications/almanac/management/AlmanacManagementRegisterWorkflow.php b/src/applications/almanac/management/AlmanacManagementRegisterWorkflow.php
index ebe992469f..545a8c3931 100644
--- a/src/applications/almanac/management/AlmanacManagementRegisterWorkflow.php
+++ b/src/applications/almanac/management/AlmanacManagementRegisterWorkflow.php
@@ -1,215 +1,214 @@
<?php
final class AlmanacManagementRegisterWorkflow
extends AlmanacManagementWorkflow {
protected function didConstruct() {
$this
->setName('register')
->setSynopsis(pht('Register this host as an Almanac device.'))
->setArguments(
array(
array(
'name' => 'device',
'param' => 'name',
'help' => pht('Almanac device name to register.'),
),
array(
'name' => 'private-key',
'param' => 'key',
'help' => pht('Path to a private key for the host.'),
),
array(
'name' => 'identify-as',
'param' => 'name',
'help' => pht(
'Specify an alternate host identity. This is an advanced '.
'feature which allows a pool of devices to share credentials.'),
),
array(
'name' => 'force',
'help' => pht(
'Register this host even if keys already exist on disk.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$viewer = $this->getViewer();
$device_name = $args->getArg('device');
if (!strlen($device_name)) {
throw new PhutilArgumentUsageException(
pht('Specify a device with --device.'));
}
$device = id(new AlmanacDeviceQuery())
->setViewer($viewer)
->withNames(array($device_name))
->executeOne();
if (!$device) {
throw new PhutilArgumentUsageException(
pht('No such device "%s" exists!', $device_name));
}
$identify_as = $args->getArg('identify-as');
$raw_device = $device_name;
if (strlen($identify_as)) {
$raw_device = $identify_as;
}
$identity_device = id(new AlmanacDeviceQuery())
->setViewer($viewer)
->withNames(array($raw_device))
->executeOne();
if (!$identity_device) {
throw new PhutilArgumentUsageException(
pht(
'No such device "%s" exists!', $raw_device));
}
$private_key_path = $args->getArg('private-key');
if (!strlen($private_key_path)) {
throw new PhutilArgumentUsageException(
pht('Specify a private key with --private-key.'));
}
if (!Filesystem::pathExists($private_key_path)) {
throw new PhutilArgumentUsageException(
pht('No private key exists at path "%s"!', $private_key_path));
}
$raw_private_key = Filesystem::readFile($private_key_path);
$phd_user = PhabricatorEnv::getEnvConfig('phd.user');
if (!$phd_user) {
throw new PhutilArgumentUsageException(
pht(
'Config option "phd.user" is not set. You must set this option '.
'so the private key can be stored with the correct permissions.'));
}
$tmp = new TempFile();
list($err) = exec_manual('chown %s %s', $phd_user, $tmp);
if ($err) {
throw new PhutilArgumentUsageException(
pht(
'Unable to change ownership of an identity file to daemon user '.
'"%s". Run this command as %s or root.',
$phd_user,
$phd_user));
}
$stored_public_path = AlmanacKeys::getKeyPath('device.pub');
$stored_private_path = AlmanacKeys::getKeyPath('device.key');
$stored_device_path = AlmanacKeys::getKeyPath('device.id');
if (!$args->getArg('force')) {
if (Filesystem::pathExists($stored_public_path)) {
throw new PhutilArgumentUsageException(
pht(
'This host already has a registered public key ("%s"). '.
'Remove this key before registering the host, or use '.
'--force to overwrite it.',
Filesystem::readablePath($stored_public_path)));
}
if (Filesystem::pathExists($stored_private_path)) {
throw new PhutilArgumentUsageException(
pht(
'This host already has a registered private key ("%s"). '.
'Remove this key before registering the host, or use '.
'--force to overwrite it.',
Filesystem::readablePath($stored_private_path)));
}
}
// NOTE: We're writing the private key here so we can change permissions
// on it without causing weird side effects to the file specified with
// the `--private-key` flag. The file needs to have restrictive permissions
// before `ssh-keygen` will willingly operate on it.
$tmp_private = new TempFile();
Filesystem::changePermissions($tmp_private, 0600);
execx('chown %s %s', $phd_user, $tmp_private);
Filesystem::writeFile($tmp_private, $raw_private_key);
list($raw_public_key) = execx('ssh-keygen -y -f %s', $tmp_private);
$key_object = PhabricatorAuthSSHPublicKey::newFromRawKey($raw_public_key);
$public_key = id(new PhabricatorAuthSSHKeyQuery())
->setViewer($this->getViewer())
->withKeys(array($key_object))
->withIsActive(true)
->executeOne();
if (!$public_key) {
throw new PhutilArgumentUsageException(
pht(
- 'The public key corresponding to the given private key is not '.
- 'yet known to Phabricator. Associate the public key with an '.
- 'Almanac device in the web interface before registering hosts '.
- 'with it.'));
+ 'The public key corresponding to the given private key is unknown. '.
+ 'Associate the public key with an Almanac device in the web '.
+ 'interface before registering hosts with it.'));
}
if ($public_key->getObjectPHID() !== $device->getPHID()) {
$public_phid = $public_key->getObjectPHID();
$public_handles = $viewer->loadHandles(array($public_phid));
$public_handle = $public_handles[$public_phid];
throw new PhutilArgumentUsageException(
pht(
'The public key corresponding to the given private key is already '.
'associated with an object ("%s") other than the specified '.
'device ("%s"). You can not use a single private key to identify '.
'multiple devices or users.',
$public_handle->getFullName(),
$device->getName()));
}
if (!$public_key->getIsTrusted()) {
throw new PhutilArgumentUsageException(
pht(
'The public key corresponding to the given private key is '.
'properly associated with the device, but is not yet trusted. '.
'Trust this key before registering devices with it.'));
}
echo tsprintf(
"%s\n",
pht('Installing public key...'));
$tmp_public = new TempFile();
Filesystem::changePermissions($tmp_public, 0600);
execx('chown %s %s', $phd_user, $tmp_public);
Filesystem::writeFile($tmp_public, $raw_public_key);
execx('mv -f %s %s', $tmp_public, $stored_public_path);
echo tsprintf(
"%s\n",
pht('Installing private key...'));
execx('mv -f %s %s', $tmp_private, $stored_private_path);
echo tsprintf(
"%s\n",
pht('Installing device %s...', $raw_device));
// The permissions on this file are more open because the webserver also
// needs to read it.
$tmp_device = new TempFile();
Filesystem::changePermissions($tmp_device, 0644);
execx('chown %s %s', $phd_user, $tmp_device);
Filesystem::writeFile($tmp_device, $raw_device);
execx('mv -f %s %s', $tmp_device, $stored_device_path);
echo tsprintf(
"**<bg:green> %s </bg>** %s\n",
pht('HOST REGISTERED'),
pht(
'This host has been registered as "%s" and a trusted keypair '.
'has been installed.',
$raw_device));
}
}
diff --git a/src/applications/almanac/management/AlmanacManagementTrustKeyWorkflow.php b/src/applications/almanac/management/AlmanacManagementTrustKeyWorkflow.php
index 631cef8c96..f730d0b40f 100644
--- a/src/applications/almanac/management/AlmanacManagementTrustKeyWorkflow.php
+++ b/src/applications/almanac/management/AlmanacManagementTrustKeyWorkflow.php
@@ -1,92 +1,92 @@
<?php
final class AlmanacManagementTrustKeyWorkflow
extends AlmanacManagementWorkflow {
protected function didConstruct() {
$this
->setName('trust-key')
->setSynopsis(pht('Mark a public key as trusted.'))
->setArguments(
array(
array(
'name' => 'id',
'param' => 'id',
'help' => pht('ID of the key to trust.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$id = $args->getArg('id');
if (!$id) {
throw new PhutilArgumentUsageException(
pht('Specify a public key to trust with --id.'));
}
$key = id(new PhabricatorAuthSSHKeyQuery())
->setViewer($this->getViewer())
->withIDs(array($id))
->executeOne();
if (!$key) {
throw new PhutilArgumentUsageException(
pht('No public key exists with ID "%s".', $id));
}
if (!$key->getIsActive()) {
throw new PhutilArgumentUsageException(
pht('Public key "%s" is not an active key.', $id));
}
if ($key->getIsTrusted()) {
throw new PhutilArgumentUsageException(
pht('Public key with ID %s is already trusted.', $id));
}
if (!($key->getObject() instanceof AlmanacDevice)) {
throw new PhutilArgumentUsageException(
pht('You can only trust keys associated with Almanac devices.'));
}
$handle = id(new PhabricatorHandleQuery())
->setViewer($this->getViewer())
->withPHIDs(array($key->getObject()->getPHID()))
->executeOne();
$console->writeOut(
"**<bg:red> %s </bg>**\n\n%s\n\n%s\n\n%s",
pht('IMPORTANT!'),
phutil_console_wrap(
pht(
'Trusting a public key gives anyone holding the corresponding '.
- 'private key complete, unrestricted access to all data in '.
- 'Phabricator. The private key will be able to sign requests that '.
- 'skip policy and security checks.')),
+ 'private key complete, unrestricted access to all data. The '.
+ 'private key will be able to sign requests that bypass policy and '.
+ 'security checks.')),
phutil_console_wrap(
pht(
'This is an advanced feature which should normally be used only '.
- 'when building a Phabricator cluster. This feature is very '.
- 'dangerous if misused.')),
+ 'when building a cluster. This feature is very dangerous if '.
+ 'misused.')),
pht('This key is associated with device "%s".', $handle->getName()));
$prompt = pht(
'Really trust this key?');
if (!phutil_console_confirm($prompt)) {
throw new PhutilArgumentUsageException(
pht('User aborted workflow.'));
}
$key->setIsTrusted(1);
$key->save();
PhabricatorAuthSSHKeyQuery::deleteSSHKeyCache();
$console->writeOut(
"**<bg:green> %s </bg>** %s\n",
pht('TRUSTED'),
pht('Key %s has been marked as trusted.', $id));
}
}
diff --git a/src/applications/almanac/query/AlmanacBindingQuery.php b/src/applications/almanac/query/AlmanacBindingQuery.php
index 574967f7bb..c3c7940525 100644
--- a/src/applications/almanac/query/AlmanacBindingQuery.php
+++ b/src/applications/almanac/query/AlmanacBindingQuery.php
@@ -1,179 +1,175 @@
<?php
final class AlmanacBindingQuery
extends AlmanacQuery {
private $ids;
private $phids;
private $servicePHIDs;
private $devicePHIDs;
private $interfacePHIDs;
private $isActive;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withServicePHIDs(array $phids) {
$this->servicePHIDs = $phids;
return $this;
}
public function withDevicePHIDs(array $phids) {
$this->devicePHIDs = $phids;
return $this;
}
public function withInterfacePHIDs(array $phids) {
$this->interfacePHIDs = $phids;
return $this;
}
public function withIsActive($active) {
$this->isActive = $active;
return $this;
}
public function newResultObject() {
return new AlmanacBinding();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $bindings) {
$service_phids = mpull($bindings, 'getServicePHID');
$device_phids = mpull($bindings, 'getDevicePHID');
$interface_phids = mpull($bindings, 'getInterfacePHID');
$services = id(new AlmanacServiceQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($service_phids)
->needProperties($this->getNeedProperties())
->execute();
$services = mpull($services, null, 'getPHID');
$devices = id(new AlmanacDeviceQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($device_phids)
->needProperties($this->getNeedProperties())
->execute();
$devices = mpull($devices, null, 'getPHID');
$interfaces = id(new AlmanacInterfaceQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($interface_phids)
->needProperties($this->getNeedProperties())
->execute();
$interfaces = mpull($interfaces, null, 'getPHID');
foreach ($bindings as $key => $binding) {
$service = idx($services, $binding->getServicePHID());
$device = idx($devices, $binding->getDevicePHID());
$interface = idx($interfaces, $binding->getInterfacePHID());
if (!$service || !$device || !$interface) {
$this->didRejectResult($binding);
unset($bindings[$key]);
continue;
}
$binding->attachService($service);
$binding->attachDevice($device);
$binding->attachInterface($interface);
}
return $bindings;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'binding.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'binding.phid IN (%Ls)',
$this->phids);
}
if ($this->servicePHIDs !== null) {
$where[] = qsprintf(
$conn,
'binding.servicePHID IN (%Ls)',
$this->servicePHIDs);
}
if ($this->devicePHIDs !== null) {
$where[] = qsprintf(
$conn,
'binding.devicePHID IN (%Ls)',
$this->devicePHIDs);
}
if ($this->interfacePHIDs !== null) {
$where[] = qsprintf(
$conn,
'binding.interfacePHID IN (%Ls)',
$this->interfacePHIDs);
}
if ($this->isActive !== null) {
if ($this->isActive) {
$where[] = qsprintf(
$conn,
'(binding.isDisabled = 0) AND (device.status IN (%Ls))',
AlmanacDeviceStatus::getActiveStatusList());
} else {
$where[] = qsprintf(
$conn,
'(binding.isDisabled = 1) OR (device.status IN (%Ls))',
AlmanacDeviceStatus::getDisabledStatusList());
}
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->shouldJoinDeviceTable()) {
$device_table = new AlmanacDevice();
$joins[] = qsprintf(
$conn,
'JOIN %R device ON binding.devicePHID = device.phid',
$device_table);
}
return $joins;
}
private function shouldJoinDeviceTable() {
if ($this->isActive !== null) {
return true;
}
return false;
}
protected function getPrimaryTableAlias() {
return 'binding';
}
}
diff --git a/src/applications/almanac/query/AlmanacDeviceQuery.php b/src/applications/almanac/query/AlmanacDeviceQuery.php
index 7fcf219571..42796c4839 100644
--- a/src/applications/almanac/query/AlmanacDeviceQuery.php
+++ b/src/applications/almanac/query/AlmanacDeviceQuery.php
@@ -1,158 +1,154 @@
<?php
final class AlmanacDeviceQuery
extends AlmanacQuery {
private $ids;
private $phids;
private $names;
private $namePrefix;
private $nameSuffix;
private $isClusterDevice;
private $statuses;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withNames(array $names) {
$this->names = $names;
return $this;
}
public function withNamePrefix($prefix) {
$this->namePrefix = $prefix;
return $this;
}
public function withNameSuffix($suffix) {
$this->nameSuffix = $suffix;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint(
new AlmanacDeviceNameNgrams(),
$ngrams);
}
public function withIsClusterDevice($is_cluster_device) {
$this->isClusterDevice = $is_cluster_device;
return $this;
}
public function newResultObject() {
return new AlmanacDevice();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'device.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'device.phid IN (%Ls)',
$this->phids);
}
if ($this->names !== null) {
$hashes = array();
foreach ($this->names as $name) {
$hashes[] = PhabricatorHash::digestForIndex($name);
}
$where[] = qsprintf(
$conn,
'device.nameIndex IN (%Ls)',
$hashes);
}
if ($this->namePrefix !== null) {
$where[] = qsprintf(
$conn,
'device.name LIKE %>',
$this->namePrefix);
}
if ($this->nameSuffix !== null) {
$where[] = qsprintf(
$conn,
'device.name LIKE %<',
$this->nameSuffix);
}
if ($this->isClusterDevice !== null) {
$where[] = qsprintf(
$conn,
'device.isBoundToClusterService = %d',
(int)$this->isClusterDevice);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'device.status IN (%Ls)',
$this->statuses);
}
return $where;
}
protected function getPrimaryTableAlias() {
return 'device';
}
public function getOrderableColumns() {
return parent::getOrderableColumns() + array(
'name' => array(
'table' => $this->getPrimaryTableAlias(),
'column' => 'name',
'type' => 'string',
'unique' => true,
'reverse' => true,
),
);
}
protected function newPagingMapFromPartialObject($object) {
return array(
'id' => (int)$object->getID(),
'name' => $object->getName(),
);
}
public function getBuiltinOrders() {
return array(
'name' => array(
'vector' => array('name'),
'name' => pht('Device Name'),
),
) + parent::getBuiltinOrders();
}
public function getQueryApplicationClass() {
return 'PhabricatorAlmanacApplication';
}
}
diff --git a/src/applications/almanac/query/AlmanacInterfaceQuery.php b/src/applications/almanac/query/AlmanacInterfaceQuery.php
index 5738108ffc..dbbc0cd53e 100644
--- a/src/applications/almanac/query/AlmanacInterfaceQuery.php
+++ b/src/applications/almanac/query/AlmanacInterfaceQuery.php
@@ -1,211 +1,207 @@
<?php
final class AlmanacInterfaceQuery
extends AlmanacQuery {
private $ids;
private $phids;
private $networkPHIDs;
private $devicePHIDs;
private $addresses;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withNetworkPHIDs(array $phids) {
$this->networkPHIDs = $phids;
return $this;
}
public function withDevicePHIDs(array $phids) {
$this->devicePHIDs = $phids;
return $this;
}
public function withAddresses(array $addresses) {
$this->addresses = $addresses;
return $this;
}
public function newResultObject() {
return new AlmanacInterface();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $interfaces) {
$network_phids = mpull($interfaces, 'getNetworkPHID');
$device_phids = mpull($interfaces, 'getDevicePHID');
$networks = id(new AlmanacNetworkQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($network_phids)
->needProperties($this->getNeedProperties())
->execute();
$networks = mpull($networks, null, 'getPHID');
$devices = id(new AlmanacDeviceQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($device_phids)
->needProperties($this->getNeedProperties())
->execute();
$devices = mpull($devices, null, 'getPHID');
foreach ($interfaces as $key => $interface) {
$network = idx($networks, $interface->getNetworkPHID());
$device = idx($devices, $interface->getDevicePHID());
if (!$network || !$device) {
$this->didRejectResult($interface);
unset($interfaces[$key]);
continue;
}
$interface->attachNetwork($network);
$interface->attachDevice($device);
}
return $interfaces;
}
protected function buildSelectClauseParts(AphrontDatabaseConnection $conn) {
$select = parent::buildSelectClauseParts($conn);
if ($this->shouldJoinDeviceTable()) {
$select[] = qsprintf($conn, 'device.name');
}
return $select;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'interface.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'interface.phid IN (%Ls)',
$this->phids);
}
if ($this->networkPHIDs !== null) {
$where[] = qsprintf(
$conn,
'interface.networkPHID IN (%Ls)',
$this->networkPHIDs);
}
if ($this->devicePHIDs !== null) {
$where[] = qsprintf(
$conn,
'interface.devicePHID IN (%Ls)',
$this->devicePHIDs);
}
if ($this->addresses !== null) {
$parts = array();
foreach ($this->addresses as $address) {
$parts[] = qsprintf(
$conn,
'(interface.networkPHID = %s '.
'AND interface.address = %s '.
'AND interface.port = %d)',
$address->getNetworkPHID(),
$address->getAddress(),
$address->getPort());
}
$where[] = qsprintf($conn, '%LO', $parts);
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->shouldJoinDeviceTable()) {
$joins[] = qsprintf(
$conn,
'JOIN %T device ON device.phid = interface.devicePHID',
id(new AlmanacDevice())->getTableName());
}
return $joins;
}
protected function shouldGroupQueryResultRows() {
if ($this->shouldJoinDeviceTable()) {
return true;
}
return parent::shouldGroupQueryResultRows();
}
private function shouldJoinDeviceTable() {
$vector = $this->getOrderVector();
if ($vector->containsKey('name')) {
return true;
}
return false;
}
protected function getPrimaryTableAlias() {
return 'interface';
}
public function getQueryApplicationClass() {
return 'PhabricatorAlmanacApplication';
}
public function getBuiltinOrders() {
return array(
'name' => array(
'vector' => array('name', 'id'),
'name' => pht('Device Name'),
),
) + parent::getBuiltinOrders();
}
public function getOrderableColumns() {
return parent::getOrderableColumns() + array(
'name' => array(
'table' => 'device',
'column' => 'name',
'type' => 'string',
'reverse' => true,
),
);
}
protected function newPagingMapFromCursorObject(
PhabricatorQueryCursor $cursor,
array $keys) {
$interface = $cursor->getObject();
return array(
'id' => (int)$interface->getID(),
'name' => $cursor->getRawRowProperty('device.name'),
);
}
}
diff --git a/src/applications/almanac/query/AlmanacNamespaceQuery.php b/src/applications/almanac/query/AlmanacNamespaceQuery.php
index d4378e17c7..e6c99bf6ea 100644
--- a/src/applications/almanac/query/AlmanacNamespaceQuery.php
+++ b/src/applications/almanac/query/AlmanacNamespaceQuery.php
@@ -1,102 +1,98 @@
<?php
final class AlmanacNamespaceQuery
extends AlmanacQuery {
private $ids;
private $phids;
private $names;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withNames(array $names) {
$this->names = $names;
return $this;
}
public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint(
new AlmanacNamespaceNameNgrams(),
$ngrams);
}
public function newResultObject() {
return new AlmanacNamespace();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'namespace.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'namespace.phid IN (%Ls)',
$this->phids);
}
if ($this->names !== null) {
$where[] = qsprintf(
$conn,
'namespace.name IN (%Ls)',
$this->names);
}
return $where;
}
protected function getPrimaryTableAlias() {
return 'namespace';
}
public function getOrderableColumns() {
return parent::getOrderableColumns() + array(
'name' => array(
'table' => $this->getPrimaryTableAlias(),
'column' => 'name',
'type' => 'string',
'unique' => true,
'reverse' => true,
),
);
}
protected function newPagingMapFromPartialObject($object) {
return array(
'id' => (int)$object->getID(),
'name' => $object->getName(),
);
}
public function getBuiltinOrders() {
return array(
'name' => array(
'vector' => array('name'),
'name' => pht('Namespace Name'),
),
) + parent::getBuiltinOrders();
}
public function getQueryApplicationClass() {
return 'PhabricatorAlmanacApplication';
}
}
diff --git a/src/applications/almanac/query/AlmanacNetworkQuery.php b/src/applications/almanac/query/AlmanacNetworkQuery.php
index 13176f7de1..7af9db8585 100644
--- a/src/applications/almanac/query/AlmanacNetworkQuery.php
+++ b/src/applications/almanac/query/AlmanacNetworkQuery.php
@@ -1,74 +1,70 @@
<?php
final class AlmanacNetworkQuery
extends AlmanacQuery {
private $ids;
private $phids;
private $names;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function newResultObject() {
return new AlmanacNetwork();
}
public function withNames(array $names) {
$this->names = $names;
return $this;
}
public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint(
new AlmanacNetworkNameNgrams(),
$ngrams);
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'network.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'network.phid IN (%Ls)',
$this->phids);
}
if ($this->names !== null) {
$where[] = qsprintf(
$conn,
'network.name IN (%Ls)',
$this->names);
}
return $where;
}
protected function getPrimaryTableAlias() {
return 'network';
}
public function getQueryApplicationClass() {
return 'PhabricatorAlmanacApplication';
}
}
diff --git a/src/applications/almanac/query/AlmanacPropertyQuery.php b/src/applications/almanac/query/AlmanacPropertyQuery.php
index 17f1e87c07..4261c70fec 100644
--- a/src/applications/almanac/query/AlmanacPropertyQuery.php
+++ b/src/applications/almanac/query/AlmanacPropertyQuery.php
@@ -1,109 +1,105 @@
<?php
final class AlmanacPropertyQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $objectPHIDs;
private $objects;
private $names;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withObjectPHIDs(array $phids) {
$this->objectPHIDs = $phids;
return $this;
}
public function withObjects(array $objects) {
$this->objects = mpull($objects, null, 'getPHID');
$this->objectPHIDs = array_keys($this->objects);
return $this;
}
public function withNames(array $names) {
$this->names = $names;
return $this;
}
public function newResultObject() {
return new AlmanacProperty();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $properties) {
$object_phids = mpull($properties, 'getObjectPHID');
$object_phids = array_fuse($object_phids);
if ($this->objects !== null) {
$object_phids = array_diff_key($object_phids, $this->objects);
}
if ($object_phids) {
$objects = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($object_phids)
->execute();
$objects = mpull($objects, null, 'getPHID');
} else {
$objects = array();
}
$objects += $this->objects;
foreach ($properties as $key => $property) {
$object = idx($objects, $property->getObjectPHID());
if (!$object) {
unset($properties[$key]);
continue;
}
$property->attachObject($object);
}
return $properties;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->objectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'objectPHID IN (%Ls)',
$this->objectPHIDs);
}
if ($this->names !== null) {
$hashes = array();
foreach ($this->names as $name) {
$hashes[] = PhabricatorHash::digestForIndex($name);
}
$where[] = qsprintf(
$conn,
'fieldIndex IN (%Ls)',
$hashes);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorAlmanacApplication';
}
}
diff --git a/src/applications/almanac/query/AlmanacServiceQuery.php b/src/applications/almanac/query/AlmanacServiceQuery.php
index 4e374ec90c..eaca9d4a76 100644
--- a/src/applications/almanac/query/AlmanacServiceQuery.php
+++ b/src/applications/almanac/query/AlmanacServiceQuery.php
@@ -1,248 +1,244 @@
<?php
final class AlmanacServiceQuery
extends AlmanacQuery {
private $ids;
private $phids;
private $names;
private $serviceTypes;
private $devicePHIDs;
private $namePrefix;
private $nameSuffix;
private $needBindings;
private $needActiveBindings;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withNames(array $names) {
$this->names = $names;
return $this;
}
public function withServiceTypes(array $types) {
$this->serviceTypes = $types;
return $this;
}
public function withDevicePHIDs(array $phids) {
$this->devicePHIDs = $phids;
return $this;
}
public function withNamePrefix($prefix) {
$this->namePrefix = $prefix;
return $this;
}
public function withNameSuffix($suffix) {
$this->nameSuffix = $suffix;
return $this;
}
public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint(
new AlmanacServiceNameNgrams(),
$ngrams);
}
public function needBindings($need_bindings) {
$this->needBindings = $need_bindings;
return $this;
}
public function needActiveBindings($need_active) {
$this->needActiveBindings = $need_active;
return $this;
}
public function newResultObject() {
return new AlmanacService();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->shouldJoinBindingTable()) {
$joins[] = qsprintf(
$conn,
'JOIN %T binding ON service.phid = binding.servicePHID',
id(new AlmanacBinding())->getTableName());
}
return $joins;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'service.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'service.phid IN (%Ls)',
$this->phids);
}
if ($this->names !== null) {
$hashes = array();
foreach ($this->names as $name) {
$hashes[] = PhabricatorHash::digestForIndex($name);
}
$where[] = qsprintf(
$conn,
'service.nameIndex IN (%Ls)',
$hashes);
}
if ($this->serviceTypes !== null) {
$where[] = qsprintf(
$conn,
'service.serviceType IN (%Ls)',
$this->serviceTypes);
}
if ($this->devicePHIDs !== null) {
$where[] = qsprintf(
$conn,
'binding.devicePHID IN (%Ls)',
$this->devicePHIDs);
}
if ($this->namePrefix !== null) {
$where[] = qsprintf(
$conn,
'service.name LIKE %>',
$this->namePrefix);
}
if ($this->nameSuffix !== null) {
$where[] = qsprintf(
$conn,
'service.name LIKE %<',
$this->nameSuffix);
}
return $where;
}
protected function willFilterPage(array $services) {
$service_map = AlmanacServiceType::getAllServiceTypes();
foreach ($services as $key => $service) {
$implementation = idx($service_map, $service->getServiceType());
if (!$implementation) {
$this->didRejectResult($service);
unset($services[$key]);
continue;
}
$implementation = clone $implementation;
$service->attachServiceImplementation($implementation);
}
return $services;
}
protected function didFilterPage(array $services) {
$need_all = $this->needBindings;
$need_active = $this->needActiveBindings;
$need_any = ($need_all || $need_active);
$only_active = ($need_active && !$need_all);
if ($need_any) {
$service_phids = mpull($services, 'getPHID');
$bindings_query = id(new AlmanacBindingQuery())
->setViewer($this->getViewer())
->withServicePHIDs($service_phids)
->needProperties($this->getNeedProperties());
if ($only_active) {
$bindings_query->withIsActive(true);
}
$bindings = $bindings_query->execute();
$bindings = mgroup($bindings, 'getServicePHID');
foreach ($services as $service) {
$service_bindings = idx($bindings, $service->getPHID(), array());
if ($only_active) {
$service->attachActiveBindings($service_bindings);
} else {
$service->attachBindings($service_bindings);
}
}
}
return parent::didFilterPage($services);
}
private function shouldJoinBindingTable() {
return ($this->devicePHIDs !== null);
}
protected function shouldGroupQueryResultRows() {
if ($this->shouldJoinBindingTable()) {
return true;
}
return parent::shouldGroupQueryResultRows();
}
protected function getPrimaryTableAlias() {
return 'service';
}
public function getOrderableColumns() {
return parent::getOrderableColumns() + array(
'name' => array(
'table' => $this->getPrimaryTableAlias(),
'column' => 'name',
'type' => 'string',
'unique' => true,
'reverse' => true,
),
);
}
protected function newPagingMapFromPartialObject($object) {
return array(
'id' => (int)$object->getID(),
'name' => $object->getName(),
);
}
public function getBuiltinOrders() {
return array(
'name' => array(
'vector' => array('name'),
'name' => pht('Service Name'),
),
) + parent::getBuiltinOrders();
}
}
diff --git a/src/applications/almanac/servicetype/AlmanacClusterDatabaseServiceType.php b/src/applications/almanac/servicetype/AlmanacClusterDatabaseServiceType.php
index ba06a78d52..10c84a628a 100644
--- a/src/applications/almanac/servicetype/AlmanacClusterDatabaseServiceType.php
+++ b/src/applications/almanac/servicetype/AlmanacClusterDatabaseServiceType.php
@@ -1,21 +1,21 @@
<?php
final class AlmanacClusterDatabaseServiceType
extends AlmanacClusterServiceType {
const SERVICETYPE = 'cluster.database';
public function getServiceTypeShortName() {
return pht('Cluster Database');
}
public function getServiceTypeName() {
- return pht('Phabricator Cluster: Database');
+ return pht('Cluster: Database');
}
public function getServiceTypeDescription() {
return pht(
- 'Defines a database service for use in a Phabricator cluster.');
+ 'Defines a database service for use in a cluster.');
}
}
diff --git a/src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php b/src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php
index 98947f56c1..2f5717f202 100644
--- a/src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php
+++ b/src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php
@@ -1,68 +1,68 @@
<?php
final class AlmanacClusterRepositoryServiceType
extends AlmanacClusterServiceType {
const SERVICETYPE = 'cluster.repository';
public function getServiceTypeShortName() {
return pht('Cluster Repository');
}
public function getServiceTypeName() {
- return pht('Phabricator Cluster: Repository');
+ return pht('Cluster: Repository');
}
public function getServiceTypeDescription() {
return pht(
- 'Defines a repository service for use in a Phabricator cluster.');
+ 'Defines a repository service for use in a cluster.');
}
public function getFieldSpecifications() {
return array(
'closed' => id(new PhabricatorBoolEditField())
->setOptions(
pht('Allow New Repositories'),
pht('Prevent New Repositories'))
->setValue(false),
);
}
public function getBindingFieldSpecifications(AlmanacBinding $binding) {
$protocols = array(
array(
'value' => 'http',
'port' => 80,
),
array(
'value' => 'https',
'port' => 443,
),
array(
'value' => 'ssh',
'port' => 22,
),
);
$default_value = 'http';
if ($binding->hasInterface()) {
$interface = $binding->getInterface();
$port = $interface->getPort();
$default_ports = ipull($protocols, 'value', 'port');
$default_value = idx($default_ports, $port, $default_value);
}
return array(
'protocol' => id(new PhabricatorSelectEditField())
->setOptions(ipull($protocols, 'value', 'value'))
->setValue($default_value),
'writable' => id(new PhabricatorBoolEditField())
->setOptions(
pht('Prevent Writes'),
pht('Allow Writes'))
->setValue(true),
);
}
}
diff --git a/src/applications/auth/adapter/PhutilPhabricatorAuthAdapter.php b/src/applications/auth/adapter/PhutilPhabricatorAuthAdapter.php
index e66ba32f94..8543675a62 100644
--- a/src/applications/auth/adapter/PhutilPhabricatorAuthAdapter.php
+++ b/src/applications/auth/adapter/PhutilPhabricatorAuthAdapter.php
@@ -1,102 +1,101 @@
<?php
/**
* Authentication adapter for Phabricator OAuth2.
*/
final class PhutilPhabricatorAuthAdapter extends PhutilOAuthAuthAdapter {
private $phabricatorBaseURI;
private $adapterDomain;
public function setPhabricatorBaseURI($uri) {
$this->phabricatorBaseURI = $uri;
return $this;
}
public function getPhabricatorBaseURI() {
return $this->phabricatorBaseURI;
}
public function getAdapterDomain() {
return $this->adapterDomain;
}
public function setAdapterDomain($domain) {
$this->adapterDomain = $domain;
return $this;
}
public function getAdapterType() {
return 'phabricator';
}
public function getAccountID() {
return $this->getOAuthAccountData('phid');
}
public function getAccountEmail() {
return $this->getOAuthAccountData('primaryEmail');
}
public function getAccountName() {
return $this->getOAuthAccountData('userName');
}
public function getAccountImageURI() {
return $this->getOAuthAccountData('image');
}
public function getAccountURI() {
return $this->getOAuthAccountData('uri');
}
public function getAccountRealName() {
return $this->getOAuthAccountData('realName');
}
protected function getAuthenticateBaseURI() {
return $this->getPhabricatorURI('oauthserver/auth/');
}
protected function getTokenBaseURI() {
return $this->getPhabricatorURI('oauthserver/token/');
}
public function getScope() {
return '';
}
public function getExtraAuthenticateParameters() {
return array(
'response_type' => 'code',
);
}
public function getExtraTokenParameters() {
return array(
'grant_type' => 'authorization_code',
);
}
protected function loadOAuthAccountData() {
$uri = id(new PhutilURI($this->getPhabricatorURI('api/user.whoami')))
->replaceQueryParam('access_token', $this->getAccessToken());
list($body) = id(new HTTPSFuture($uri))->resolvex();
try {
$data = phutil_json_decode($body);
return $data['result'];
} catch (PhutilJSONParserException $ex) {
throw new Exception(
pht(
- 'Expected valid JSON response from Phabricator %s request.',
- 'user.whoami'),
+ 'Expected valid JSON response from "user.whoami" request.'),
$ex);
}
}
private function getPhabricatorURI($path) {
return rtrim($this->phabricatorBaseURI, '/').'/'.ltrim($path, '/');
}
}
diff --git a/src/applications/auth/controller/PhabricatorAuthConfirmLinkController.php b/src/applications/auth/controller/PhabricatorAuthConfirmLinkController.php
index 9ceb10df8b..3d79bcfb81 100644
--- a/src/applications/auth/controller/PhabricatorAuthConfirmLinkController.php
+++ b/src/applications/auth/controller/PhabricatorAuthConfirmLinkController.php
@@ -1,79 +1,80 @@
<?php
final class PhabricatorAuthConfirmLinkController
extends PhabricatorAuthController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$accountkey = $request->getURIData('akey');
$result = $this->loadAccountForRegistrationOrLinking($accountkey);
list($account, $provider, $response) = $result;
if ($response) {
return $response;
}
if (!$provider->shouldAllowAccountLink()) {
return $this->renderError(pht('This account is not linkable.'));
}
$panel_uri = '/settings/panel/external/';
if ($request->isFormOrHisecPost()) {
$workflow_key = sprintf(
'account.link(%s)',
$account->getPHID());
$hisec_token = id(new PhabricatorAuthSessionEngine())
->setWorkflowKey($workflow_key)
->requireHighSecurityToken($viewer, $request, $panel_uri);
$account->setUserPHID($viewer->getPHID());
$account->save();
$this->clearRegistrationCookies();
// TODO: Send the user email about the new account link.
return id(new AphrontRedirectResponse())->setURI($panel_uri);
}
$dialog = $this->newDialog()
->setTitle(pht('Confirm %s Account Link', $provider->getProviderName()))
->addCancelButton($panel_uri)
->addSubmitButton(pht('Confirm Account Link'));
$form = id(new PHUIFormLayoutView())
->setFullWidth(true)
->appendChild(
phutil_tag(
'div',
array(
'class' => 'aphront-form-instructions',
),
pht(
'Confirm the link with this %s account. This account will be '.
- 'able to log in to your Phabricator account.',
- $provider->getProviderName())))
+ 'able to log in to your %s account.',
+ $provider->getProviderName(),
+ PlatformSymbols::getPlatformServerName())))
->appendChild(
id(new PhabricatorAuthAccountView())
->setUser($viewer)
->setExternalAccount($account)
->setAuthProvider($provider));
$dialog->appendChild($form);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Confirm Link'), $panel_uri);
$crumbs->addTextCrumb($provider->getProviderName());
$crumbs->setBorder(true);
return $this->newPage()
->setTitle(pht('Confirm External Account Link'))
->setCrumbs($crumbs)
->appendChild($dialog);
}
}
diff --git a/src/applications/auth/controller/PhabricatorAuthController.php b/src/applications/auth/controller/PhabricatorAuthController.php
index c3dc4e2d98..aee93b98d6 100644
--- a/src/applications/auth/controller/PhabricatorAuthController.php
+++ b/src/applications/auth/controller/PhabricatorAuthController.php
@@ -1,295 +1,299 @@
<?php
abstract class PhabricatorAuthController extends PhabricatorController {
protected function renderErrorPage($title, array $messages) {
$view = new PHUIInfoView();
$view->setTitle($title);
$view->setErrors($messages);
return $this->newPage()
->setTitle($title)
->appendChild($view);
}
/**
* Returns true if this install is newly setup (i.e., there are no user
* accounts yet). In this case, we enter a special mode to permit creation
* of the first account form the web UI.
*/
protected function isFirstTimeSetup() {
// If there are any auth providers, this isn't first time setup, even if
// we don't have accounts.
if (PhabricatorAuthProvider::getAllEnabledProviders()) {
return false;
}
// Otherwise, check if there are any user accounts. If not, we're in first
// time setup.
$any_users = id(new PhabricatorPeopleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->setLimit(1)
->execute();
return !$any_users;
}
/**
* Log a user into a web session and return an @{class:AphrontResponse} which
* corresponds to continuing the login process.
*
* Normally, this is a redirect to the validation controller which makes sure
* the user's cookies are set. However, event listeners can intercept this
* event and do something else if they prefer.
*
* @param PhabricatorUser User to log the viewer in as.
* @param bool True to issue a full session immediately, bypassing MFA.
* @return AphrontResponse Response which continues the login process.
*/
protected function loginUser(
PhabricatorUser $user,
$force_full_session = false) {
$response = $this->buildLoginValidateResponse($user);
$session_type = PhabricatorAuthSession::TYPE_WEB;
if ($force_full_session) {
$partial_session = false;
} else {
$partial_session = true;
}
$session_key = id(new PhabricatorAuthSessionEngine())
->establishSession($session_type, $user->getPHID(), $partial_session);
// NOTE: We allow disabled users to login and roadblock them later, so
// there's no check for users being disabled here.
$request = $this->getRequest();
$request->setCookie(
PhabricatorCookies::COOKIE_USERNAME,
$user->getUsername());
$request->setCookie(
PhabricatorCookies::COOKIE_SESSION,
$session_key);
$this->clearRegistrationCookies();
return $response;
}
protected function clearRegistrationCookies() {
$request = $this->getRequest();
// Clear the registration key.
$request->clearCookie(PhabricatorCookies::COOKIE_REGISTRATION);
// Clear the client ID / OAuth state key.
$request->clearCookie(PhabricatorCookies::COOKIE_CLIENTID);
// Clear the invite cookie.
$request->clearCookie(PhabricatorCookies::COOKIE_INVITE);
}
private function buildLoginValidateResponse(PhabricatorUser $user) {
$validate_uri = new PhutilURI($this->getApplicationURI('validate/'));
$validate_uri->replaceQueryParam('expect', $user->getUsername());
return id(new AphrontRedirectResponse())->setURI((string)$validate_uri);
}
protected function renderError($message) {
return $this->renderErrorPage(
pht('Authentication Error'),
array(
$message,
));
}
protected function loadAccountForRegistrationOrLinking($account_key) {
$request = $this->getRequest();
$viewer = $request->getUser();
$account = null;
$provider = null;
$response = null;
if (!$account_key) {
$response = $this->renderError(
pht('Request did not include account key.'));
return array($account, $provider, $response);
}
// NOTE: We're using the omnipotent user because the actual user may not
// be logged in yet, and because we want to tailor an error message to
// distinguish between "not usable" and "does not exist". We do explicit
// checks later on to make sure this account is valid for the intended
// operation. This requires edit permission for completeness and consistency
// but it won't actually be meaningfully checked because we're using the
// omnipotent user.
$account = id(new PhabricatorExternalAccountQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withAccountSecrets(array($account_key))
->needImages(true)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$account) {
$response = $this->renderError(pht('No valid linkable account.'));
return array($account, $provider, $response);
}
if ($account->getUserPHID()) {
if ($account->getUserPHID() != $viewer->getPHID()) {
$response = $this->renderError(
pht(
'The account you are attempting to register or link is already '.
'linked to another user.'));
} else {
$response = $this->renderError(
pht(
'The account you are attempting to link is already linked '.
'to your account.'));
}
return array($account, $provider, $response);
}
$registration_key = $request->getCookie(
PhabricatorCookies::COOKIE_REGISTRATION);
// NOTE: This registration key check is not strictly necessary, because
// we're only creating new accounts, not linking existing accounts. It
// might be more hassle than it is worth, especially for email.
//
// The attack this prevents is getting to the registration screen, then
// copy/pasting the URL and getting someone else to click it and complete
// the process. They end up with an account bound to credentials you
// control. This doesn't really let you do anything meaningful, though,
// since you could have simply completed the process yourself.
if (!$registration_key) {
$response = $this->renderError(
pht(
'Your browser did not submit a registration key with the request. '.
'You must use the same browser to begin and complete registration. '.
'Check that cookies are enabled and try again.'));
return array($account, $provider, $response);
}
// We store the digest of the key rather than the key itself to prevent a
// theoretical attacker with read-only access to the database from
// hijacking registration sessions.
$actual = $account->getProperty('registrationKey');
$expect = PhabricatorHash::weakDigest($registration_key);
if (!phutil_hashes_are_identical($actual, $expect)) {
$response = $this->renderError(
pht(
'Your browser submitted a different registration key than the one '.
'associated with this account. You may need to clear your cookies.'));
return array($account, $provider, $response);
}
$config = $account->getProviderConfig();
if (!$config->getIsEnabled()) {
$response = $this->renderError(
pht(
'The account you are attempting to register with uses a disabled '.
'authentication provider ("%s"). An administrator may have '.
'recently disabled this provider.',
$config->getDisplayName()));
return array($account, $provider, $response);
}
$provider = $config->getProvider();
return array($account, $provider, null);
}
protected function loadInvite() {
$invite_cookie = PhabricatorCookies::COOKIE_INVITE;
$invite_code = $this->getRequest()->getCookie($invite_cookie);
if (!$invite_code) {
return null;
}
$engine = id(new PhabricatorAuthInviteEngine())
->setViewer($this->getViewer())
->setUserHasConfirmedVerify(true);
try {
return $engine->processInviteCode($invite_code);
} catch (Exception $ex) {
// If this fails for any reason, just drop the invite. In normal
// circumstances, we gave them a detailed explanation of any error
// before they jumped into this workflow.
return null;
}
}
protected function renderInviteHeader(PhabricatorAuthInvite $invite) {
$viewer = $this->getViewer();
// Since the user hasn't registered yet, they may not be able to see other
// user accounts. Load the inviting user with the omnipotent viewer.
$omnipotent_viewer = PhabricatorUser::getOmnipotentUser();
$invite_author = id(new PhabricatorPeopleQuery())
->setViewer($omnipotent_viewer)
->withPHIDs(array($invite->getAuthorPHID()))
->needProfileImage(true)
->executeOne();
// If we can't load the author for some reason, just drop this message.
// We lose the value of contextualizing things without author details.
if (!$invite_author) {
return null;
}
$invite_item = id(new PHUIObjectItemView())
- ->setHeader(pht('Welcome to Phabricator!'))
+ ->setHeader(
+ pht(
+ 'Welcome to %s!',
+ PlatformSymbols::getPlatformServerName()))
->setImageURI($invite_author->getProfileImageURI())
->addAttribute(
pht(
- '%s has invited you to join Phabricator.',
- $invite_author->getFullName()));
+ '%s has invited you to join %s.',
+ $invite_author->getFullName(),
+ PlatformSymbols::getPlatformServerName()));
$invite_list = id(new PHUIObjectItemListView())
->addItem($invite_item)
->setFlush(true);
return id(new PHUIBoxView())
->addMargin(PHUI::MARGIN_LARGE)
->appendChild($invite_list);
}
final protected function newCustomStartMessage() {
$viewer = $this->getViewer();
$text = PhabricatorAuthMessage::loadMessageText(
$viewer,
PhabricatorAuthLoginMessageType::MESSAGEKEY);
if (!strlen($text)) {
return null;
}
$remarkup_view = new PHUIRemarkupView($viewer, $text);
return phutil_tag(
'div',
array(
'class' => 'auth-custom-message',
),
$remarkup_view);
}
}
diff --git a/src/applications/auth/controller/PhabricatorAuthLinkController.php b/src/applications/auth/controller/PhabricatorAuthLinkController.php
index 4b127b9ad1..7e1a5f5c67 100644
--- a/src/applications/auth/controller/PhabricatorAuthLinkController.php
+++ b/src/applications/auth/controller/PhabricatorAuthLinkController.php
@@ -1,128 +1,128 @@
<?php
final class PhabricatorAuthLinkController
extends PhabricatorAuthController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$action = $request->getURIData('action');
$id = $request->getURIData('id');
$config = id(new PhabricatorAuthProviderConfigQuery())
->setViewer($viewer)
->withIDs(array($id))
->withIsEnabled(true)
->executeOne();
if (!$config) {
return new Aphront404Response();
}
$provider = $config->getProvider();
switch ($action) {
case 'link':
if (!$provider->shouldAllowAccountLink()) {
return $this->renderErrorPage(
pht('Account Not Linkable'),
array(
pht('This provider is not configured to allow linking.'),
));
}
break;
case 'refresh':
if (!$provider->shouldAllowAccountRefresh()) {
return $this->renderErrorPage(
pht('Account Not Refreshable'),
array(
pht('This provider does not allow refreshing.'),
));
}
break;
default:
return new Aphront400Response();
}
$accounts = id(new PhabricatorExternalAccountQuery())
->setViewer($viewer)
->withUserPHIDs(array($viewer->getPHID()))
->withProviderConfigPHIDs(array($config->getPHID()))
->execute();
switch ($action) {
case 'link':
if ($accounts) {
return $this->renderErrorPage(
pht('Account Already Linked'),
array(
pht(
- 'Your Phabricator account is already linked to an external '.
- 'account for this provider.'),
+ 'Your account is already linked to an external account for '.
+ 'this provider.'),
));
}
break;
case 'refresh':
if (!$accounts) {
return $this->renderErrorPage(
pht('No Account Linked'),
array(
pht(
'You do not have a linked account on this provider, and thus '.
'can not refresh it.'),
));
}
break;
default:
return new Aphront400Response();
}
$panel_uri = '/settings/panel/external/';
PhabricatorCookies::setClientIDCookie($request);
switch ($action) {
case 'link':
$form = $provider->buildLinkForm($this);
break;
case 'refresh':
$form = $provider->buildRefreshForm($this);
break;
default:
return new Aphront400Response();
}
if ($provider->isLoginFormAButton()) {
require_celerity_resource('auth-css');
$form = phutil_tag(
'div',
array(
'class' => 'phabricator-link-button pl',
),
$form);
}
switch ($action) {
case 'link':
$name = pht('Link Account');
$title = pht('Link %s Account', $provider->getProviderName());
break;
case 'refresh':
$name = pht('Refresh Account');
$title = pht('Refresh %s Account', $provider->getProviderName());
break;
default:
return new Aphront400Response();
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Link Account'), $panel_uri);
$crumbs->addTextCrumb($provider->getProviderName($name));
$crumbs->setBorder(true);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($form);
}
}
diff --git a/src/applications/auth/controller/PhabricatorAuthLoginController.php b/src/applications/auth/controller/PhabricatorAuthLoginController.php
index 4f957b28a4..2a1ecf97c4 100644
--- a/src/applications/auth/controller/PhabricatorAuthLoginController.php
+++ b/src/applications/auth/controller/PhabricatorAuthLoginController.php
@@ -1,281 +1,286 @@
<?php
final class PhabricatorAuthLoginController
extends PhabricatorAuthController {
private $providerKey;
private $extraURIData;
private $provider;
public function shouldRequireLogin() {
return false;
}
public function shouldAllowRestrictedParameter($parameter_name) {
// Whitelist the OAuth 'code' parameter.
if ($parameter_name == 'code') {
return true;
}
return parent::shouldAllowRestrictedParameter($parameter_name);
}
public function getExtraURIData() {
return $this->extraURIData;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$this->providerKey = $request->getURIData('pkey');
$this->extraURIData = $request->getURIData('extra');
$response = $this->loadProvider();
if ($response) {
return $response;
}
$invite = $this->loadInvite();
$provider = $this->provider;
try {
list($account, $response) = $provider->processLoginRequest($this);
} catch (PhutilAuthUserAbortedException $ex) {
if ($viewer->isLoggedIn()) {
// If a logged-in user cancels, take them back to the external accounts
// panel.
$next_uri = '/settings/panel/external/';
} else {
// If a logged-out user cancels, take them back to the auth start page.
$next_uri = '/';
}
// User explicitly hit "Cancel".
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht('Authentication Canceled'))
->appendChild(
pht('You canceled authentication.'))
->addCancelButton($next_uri, pht('Continue'));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
if ($response) {
return $response;
}
if (!$account) {
throw new Exception(
pht(
'Auth provider failed to load an account from %s!',
'processLoginRequest()'));
}
if ($account->getUserPHID()) {
// The account is already attached to a Phabricator user, so this is
// either a login or a bad account link request.
if (!$viewer->isLoggedIn()) {
if ($provider->shouldAllowLogin()) {
return $this->processLoginUser($account);
} else {
return $this->renderError(
pht(
- 'The external account ("%s") you just authenticated with is '.
- 'not configured to allow logins on this Phabricator install. '.
- 'An administrator may have recently disabled it.',
+ 'The external service ("%s") you just authenticated with is '.
+ 'not configured to allow logins on this server. An '.
+ 'administrator may have recently disabled it.',
$provider->getProviderName()));
}
} else if ($viewer->getPHID() == $account->getUserPHID()) {
// This is either an attempt to re-link an existing and already
// linked account (which is silly) or a refresh of an external account
// (e.g., an OAuth account).
return id(new AphrontRedirectResponse())
->setURI('/settings/panel/external/');
} else {
return $this->renderError(
pht(
- 'The external account ("%s") you just used to log in is already '.
- 'associated with another Phabricator user account. Log in to the '.
- 'other Phabricator account and unlink the external account before '.
- 'linking it to a new Phabricator account.',
- $provider->getProviderName()));
+ 'The external service ("%s") you just used to log in is already '.
+ 'associated with another %s user account. Log in to the '.
+ 'other %s account and unlink the external account before '.
+ 'linking it to a new %s account.',
+ $provider->getProviderName(),
+ PlatformSymbols::getPlatformServerName(),
+ PlatformSymbols::getPlatformServerName(),
+ PlatformSymbols::getPlatformServerName()));
}
} else {
// The account is not yet attached to a Phabricator user, so this is
// either a registration or an account link request.
if (!$viewer->isLoggedIn()) {
if ($provider->shouldAllowRegistration() || $invite) {
return $this->processRegisterUser($account);
} else {
return $this->renderError(
pht(
- 'The external account ("%s") you just authenticated with is '.
- 'not configured to allow registration on this Phabricator '.
- 'install. An administrator may have recently disabled it.',
+ 'The external service ("%s") you just authenticated with is '.
+ 'not configured to allow registration on this server. An '.
+ 'administrator may have recently disabled it.',
$provider->getProviderName()));
}
} else {
// If the user already has a linked account on this provider, prevent
// them from linking a second account. This can happen if they swap
// logins and then refresh the account link.
// There's no technical reason we can't allow you to link multiple
// accounts from a single provider; disallowing this is currently a
// product deciison. See T2549.
$existing_accounts = id(new PhabricatorExternalAccountQuery())
->setViewer($viewer)
->withUserPHIDs(array($viewer->getPHID()))
->withProviderConfigPHIDs(
array(
$provider->getProviderConfigPHID(),
))
->execute();
if ($existing_accounts) {
return $this->renderError(
pht(
- 'Your Phabricator account is already connected to an external '.
- 'account on this provider ("%s"), but you are currently logged '.
- 'in to the provider with a different account. Log out of the '.
+ 'Your %s account is already connected to an external '.
+ 'account on this service ("%s"), but you are currently logged '.
+ 'in to the service with a different account. Log out of the '.
'external service, then log back in with the correct account '.
'before refreshing the account link.',
+ PlatformSymbols::getPlatformServerName(),
$provider->getProviderName()));
}
if ($provider->shouldAllowAccountLink()) {
return $this->processLinkUser($account);
} else {
return $this->renderError(
pht(
- 'The external account ("%s") you just authenticated with is '.
- 'not configured to allow account linking on this Phabricator '.
- 'install. An administrator may have recently disabled it.',
+ 'The external service ("%s") you just authenticated with is '.
+ 'not configured to allow account linking on this server. An '.
+ 'administrator may have recently disabled it.',
$provider->getProviderName()));
}
}
}
// This should be unreachable, but fail explicitly if we get here somehow.
return new Aphront400Response();
}
private function processLoginUser(PhabricatorExternalAccount $account) {
$user = id(new PhabricatorUser())->loadOneWhere(
'phid = %s',
$account->getUserPHID());
if (!$user) {
return $this->renderError(
pht(
'The external account you just logged in with is not associated '.
- 'with a valid Phabricator user.'));
+ 'with a valid %s user account.',
+ PlatformSymbols::getPlatformServerName()));
}
return $this->loginUser($user);
}
private function processRegisterUser(PhabricatorExternalAccount $account) {
$account_secret = $account->getAccountSecret();
$register_uri = $this->getApplicationURI('register/'.$account_secret.'/');
return $this->setAccountKeyAndContinue($account, $register_uri);
}
private function processLinkUser(PhabricatorExternalAccount $account) {
$account_secret = $account->getAccountSecret();
$confirm_uri = $this->getApplicationURI('confirmlink/'.$account_secret.'/');
return $this->setAccountKeyAndContinue($account, $confirm_uri);
}
private function setAccountKeyAndContinue(
PhabricatorExternalAccount $account,
$next_uri) {
if ($account->getUserPHID()) {
throw new Exception(pht('Account is already registered or linked.'));
}
// Regenerate the registration secret key, set it on the external account,
// set a cookie on the user's machine, and redirect them to registration.
// See PhabricatorAuthRegisterController for discussion of the registration
// key.
$registration_key = Filesystem::readRandomCharacters(32);
$account->setProperty(
'registrationKey',
PhabricatorHash::weakDigest($registration_key));
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$account->save();
unset($unguarded);
$this->getRequest()->setTemporaryCookie(
PhabricatorCookies::COOKIE_REGISTRATION,
$registration_key);
return id(new AphrontRedirectResponse())->setURI($next_uri);
}
private function loadProvider() {
$provider = PhabricatorAuthProvider::getEnabledProviderByKey(
$this->providerKey);
if (!$provider) {
return $this->renderError(
pht(
'The account you are attempting to log in with uses a nonexistent '.
'or disabled authentication provider (with key "%s"). An '.
'administrator may have recently disabled this provider.',
$this->providerKey));
}
$this->provider = $provider;
return null;
}
protected function renderError($message) {
return $this->renderErrorPage(
pht('Login Failed'),
array($message));
}
public function buildProviderPageResponse(
PhabricatorAuthProvider $provider,
$content) {
$crumbs = $this->buildApplicationCrumbs();
$viewer = $this->getViewer();
if ($viewer->isLoggedIn()) {
$crumbs->addTextCrumb(pht('Link Account'), $provider->getSettingsURI());
} else {
$crumbs->addTextCrumb(pht('Login'), $this->getApplicationURI('start/'));
$content = array(
$this->newCustomStartMessage(),
$content,
);
}
$crumbs->addTextCrumb($provider->getProviderName());
$crumbs->setBorder(true);
return $this->newPage()
->setTitle(pht('Login'))
->setCrumbs($crumbs)
->appendChild($content);
}
public function buildProviderErrorResponse(
PhabricatorAuthProvider $provider,
$message) {
$message = pht(
'Authentication provider ("%s") encountered an error while attempting '.
'to log in. %s', $provider->getProviderName(), $message);
return $this->renderError($message);
}
}
diff --git a/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php b/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php
index 259e4c6743..0f86614b19 100644
--- a/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php
+++ b/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php
@@ -1,250 +1,250 @@
<?php
final class PhabricatorAuthNeedsMultiFactorController
extends PhabricatorAuthController {
public function shouldRequireMultiFactorEnrollment() {
// Users need access to this controller in order to enroll in multi-factor
// auth.
return false;
}
public function shouldRequireEnabledUser() {
// Users who haven't been approved yet are allowed to enroll in MFA. We'll
// kick disabled users out later.
return false;
}
public function shouldRequireEmailVerification() {
// Users who haven't verified their email addresses yet can still enroll
// in MFA.
return false;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
if ($viewer->getIsDisabled()) {
// We allowed unapproved and disabled users to hit this controller, but
// want to kick out disabled users now.
return new Aphront400Response();
}
$panels = $this->loadPanels();
$multifactor_key = id(new PhabricatorMultiFactorSettingsPanel())
->getPanelKey();
$panel_key = $request->getURIData('pageKey');
if (!strlen($panel_key)) {
$panel_key = $multifactor_key;
}
if (!isset($panels[$panel_key])) {
return new Aphront404Response();
}
$nav = $this->newNavigation();
$nav->selectFilter($panel_key);
$panel = $panels[$panel_key];
$viewer->updateMultiFactorEnrollment();
if ($panel_key === $multifactor_key) {
$header_text = pht('Add Multi-Factor Auth');
$help = $this->newGuidance();
$panel->setIsEnrollment(true);
} else {
$header_text = $panel->getPanelName();
$help = null;
}
$response = $panel
->setController($this)
->setNavigation($nav)
->processRequest($request);
if (($response instanceof AphrontResponse) ||
($response instanceof AphrontResponseProducerInterface)) {
return $response;
}
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Add Multi-Factor Auth'))
->setBorder(true);
$header = id(new PHUIHeaderView())
->setHeader($header_text);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(
array(
$help,
$response,
));
return $this->newPage()
->setTitle(pht('Add Multi-Factor Authentication'))
->setCrumbs($crumbs)
->setNavigation($nav)
->appendChild($view);
}
private function loadPanels() {
$viewer = $this->getViewer();
$preferences = PhabricatorUserPreferences::loadUserPreferences($viewer);
$panels = PhabricatorSettingsPanel::getAllDisplayPanels();
$base_uri = $this->newEnrollBaseURI();
$result = array();
foreach ($panels as $key => $panel) {
$panel
->setPreferences($preferences)
->setViewer($viewer)
->setUser($viewer)
->setOverrideURI(urisprintf('%s%s/', $base_uri, $key));
if (!$panel->isEnabled()) {
continue;
}
if (!$panel->isUserPanel()) {
continue;
}
if (!$panel->isMultiFactorEnrollmentPanel()) {
continue;
}
if (!empty($result[$key])) {
throw new Exception(pht(
"Two settings panels share the same panel key ('%s'): %s, %s.",
$key,
get_class($panel),
get_class($result[$key])));
}
$result[$key] = $panel;
}
return $result;
}
private function newNavigation() {
$viewer = $this->getViewer();
$enroll_uri = $this->newEnrollBaseURI();
$nav = id(new AphrontSideNavFilterView())
->setBaseURI(new PhutilURI($enroll_uri));
$multifactor_key = id(new PhabricatorMultiFactorSettingsPanel())
->getPanelKey();
$nav->addFilter(
$multifactor_key,
pht('Enroll in MFA'),
null,
'fa-exclamation-triangle blue');
$panels = $this->loadPanels();
if ($panels) {
$nav->addLabel(pht('Settings'));
}
foreach ($panels as $panel_key => $panel) {
if ($panel_key === $multifactor_key) {
continue;
}
$nav->addFilter(
$panel->getPanelKey(),
$panel->getPanelName(),
null,
$panel->getPanelMenuIcon());
}
return $nav;
}
private function newEnrollBaseURI() {
return $this->getApplicationURI('enroll/');
}
private function newGuidance() {
$viewer = $this->getViewer();
if ($viewer->getIsEnrolledInMultiFactor()) {
$guidance = pht(
'{icon check, color="green"} **Setup Complete!**'.
"\n\n".
'You have successfully configured multi-factor authentication '.
'for your account.'.
"\n\n".
'You can make adjustments from the [[ /settings/ | Settings ]] panel '.
'later.');
return $this->newDialog()
->setTitle(pht('Multi-Factor Authentication Setup Complete'))
->setWidth(AphrontDialogView::WIDTH_FULL)
->appendChild(new PHUIRemarkupView($viewer, $guidance))
->addCancelButton('/', pht('Continue'));
}
$views = array();
$messages = array();
$messages[] = pht(
- 'Before you can use Phabricator, you need to add multi-factor '.
+ 'Before you can use this software, you need to add multi-factor '.
'authentication to your account. Multi-factor authentication helps '.
'secure your account by making it more difficult for attackers to '.
'gain access or take sensitive actions.');
$view = id(new PHUIInfoView())
->setTitle(pht('Add Multi-Factor Authentication To Your Account'))
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors($messages);
$views[] = $view;
$providers = id(new PhabricatorAuthFactorProviderQuery())
->setViewer($viewer)
->withStatuses(
array(
PhabricatorAuthFactorProviderStatus::STATUS_ACTIVE,
))
->execute();
if (!$providers) {
$messages = array();
$required_key = 'security.require-multi-factor-auth';
$messages[] = pht(
'This install has the configuration option "%s" enabled, but does '.
'not have any active multifactor providers configured. This means '.
'you are required to add MFA, but are also prevented from doing so. '.
'An administrator must disable "%s" or enable an MFA provider to '.
'allow you to continue.',
$required_key,
$required_key);
$view = id(new PHUIInfoView())
->setTitle(pht('Multi-Factor Authentication is Misconfigured'))
->setSeverity(PHUIInfoView::SEVERITY_ERROR)
->setErrors($messages);
$views[] = $view;
}
return $views;
}
}
diff --git a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php
index d176a67119..8a3ad1afc9 100644
--- a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php
+++ b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php
@@ -1,275 +1,279 @@
<?php
final class PhabricatorAuthOneTimeLoginController
extends PhabricatorAuthController {
public function shouldRequireLogin() {
return false;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$link_type = $request->getURIData('type');
$key = $request->getURIData('key');
$email_id = $request->getURIData('emailID');
$target_user = id(new PhabricatorPeopleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withIDs(array($id))
->executeOne();
if (!$target_user) {
return new Aphront404Response();
}
// NOTE: We allow you to use a one-time login link for your own current
// login account. This supports the "Set Password" flow.
$is_logged_in = false;
if ($viewer->isLoggedIn()) {
if ($viewer->getPHID() !== $target_user->getPHID()) {
return $this->renderError(
pht('You are already logged in.'));
} else {
$is_logged_in = true;
}
}
// NOTE: As a convenience to users, these one-time login URIs may also
// be associated with an email address which will be verified when the
// URI is used.
// This improves the new user experience for users receiving "Welcome"
// emails on installs that require verification: if we did not verify the
// email, they'd immediately get roadblocked with a "Verify Your Email"
// error and have to go back to their email account, wait for a
// "Verification" email, and then click that link to actually get access to
// their account. This is hugely unwieldy, and if the link was only sent
// to the user's email in the first place we can safely verify it as a
// side effect of login.
// The email hashed into the URI so users can't verify some email they
// do not own by doing this:
//
// - Add some address you do not own;
// - request a password reset;
// - change the URI in the email to the address you don't own;
// - login via the email link; and
// - get a "verified" address you don't control.
$target_email = null;
if ($email_id) {
$target_email = id(new PhabricatorUserEmail())->loadOneWhere(
'userPHID = %s AND id = %d',
$target_user->getPHID(),
$email_id);
if (!$target_email) {
return new Aphront404Response();
}
}
$engine = new PhabricatorAuthSessionEngine();
$token = $engine->loadOneTimeLoginKey(
$target_user,
$target_email,
$key);
if (!$token) {
return $this->newDialog()
->setTitle(pht('Unable to Log In'))
->setShortTitle(pht('Login Failure'))
->appendParagraph(
pht(
'The login link you clicked is invalid, out of date, or has '.
'already been used.'))
->appendParagraph(
pht(
'Make sure you are copy-and-pasting the entire link into '.
'your browser. Login links are only valid for 24 hours, and '.
'can only be used once.'))
->appendParagraph(
pht('You can try again, or request a new link via email.'))
->addCancelButton('/login/email/', pht('Send Another Email'));
}
if (!$target_user->canEstablishWebSessions()) {
return $this->newDialog()
->setTitle(pht('Unable to Establish Web Session'))
->setShortTitle(pht('Login Failure'))
->appendParagraph(
pht(
'You are trying to gain access to an account ("%s") that can not '.
'establish a web session.',
$target_user->getUsername()))
->appendParagraph(
pht(
'Special users like daemons and mailing lists are not permitted '.
'to log in via the web. Log in as a normal user instead.'))
->addCancelButton('/');
}
if ($request->isFormPost() || $is_logged_in) {
// If we have an email bound into this URI, verify email so that clicking
// the link in the "Welcome" email is good enough, without requiring users
// to go through a second round of email verification.
$editor = id(new PhabricatorUserEditor())
->setActor($target_user);
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
// Nuke the token and all other outstanding password reset tokens.
// There is no particular security benefit to destroying them all, but
// it should reduce HackerOne reports of nebulous harm.
$editor->revokePasswordResetLinks($target_user);
if ($target_email) {
$editor->verifyEmail($target_user, $target_email);
}
unset($unguarded);
$next_uri = $this->getNextStepURI($target_user);
// If the user is already logged in, we're just doing a "password set"
// flow. Skip directly to the next step.
if ($is_logged_in) {
return id(new AphrontRedirectResponse())->setURI($next_uri);
}
PhabricatorCookies::setNextURICookie($request, $next_uri, $force = true);
$force_full_session = false;
if ($link_type === PhabricatorAuthSessionEngine::ONETIME_RECOVER) {
$force_full_session = $token->getShouldForceFullSession();
}
return $this->loginUser($target_user, $force_full_session);
}
// NOTE: We need to CSRF here so attackers can't generate an email link,
// then log a user in to an account they control via sneaky invisible
// form submissions.
switch ($link_type) {
case PhabricatorAuthSessionEngine::ONETIME_WELCOME:
- $title = pht('Welcome to Phabricator');
+ $title = pht(
+ 'Welcome to %s',
+ PlatformSymbols::getPlatformServerName());
break;
case PhabricatorAuthSessionEngine::ONETIME_RECOVER:
$title = pht('Account Recovery');
break;
case PhabricatorAuthSessionEngine::ONETIME_USERNAME:
case PhabricatorAuthSessionEngine::ONETIME_RESET:
default:
- $title = pht('Log in to Phabricator');
+ $title = pht(
+ 'Log in to %s',
+ PlatformSymbols::getPlatformServerName());
break;
}
$body = array();
$body[] = pht(
'Use the button below to log in as: %s',
phutil_tag('strong', array(), $target_user->getUsername()));
if ($target_email && !$target_email->getIsVerified()) {
$body[] = pht(
'Logging in will verify %s as an email address you own.',
phutil_tag('strong', array(), $target_email->getAddress()));
}
$body[] = pht(
'After logging in you should set a password for your account, or '.
'link your account to an external account that you can use to '.
'authenticate in the future.');
$dialog = $this->newDialog()
->setTitle($title)
->addSubmitButton(pht('Log In (%s)', $target_user->getUsername()))
->addCancelButton('/');
foreach ($body as $paragraph) {
$dialog->appendParagraph($paragraph);
}
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function getNextStepURI(PhabricatorUser $user) {
$request = $this->getRequest();
// If we have password auth, let the user set or reset their password after
// login.
$have_passwords = PhabricatorPasswordAuthProvider::getPasswordProvider();
if ($have_passwords) {
// We're going to let the user reset their password without knowing
// the old one. Generate a one-time token for that.
$key = Filesystem::readRandomCharacters(16);
$password_type =
PhabricatorAuthPasswordResetTemporaryTokenType::TOKENTYPE;
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
id(new PhabricatorAuthTemporaryToken())
->setTokenResource($user->getPHID())
->setTokenType($password_type)
->setTokenExpires(time() + phutil_units('1 hour in seconds'))
->setTokenCode(PhabricatorHash::weakDigest($key))
->save();
unset($unguarded);
$panel_uri = '/auth/password/';
$request->setTemporaryCookie(PhabricatorCookies::COOKIE_HISEC, 'yes');
$params = array(
'key' => $key,
);
return (string)new PhutilURI($panel_uri, $params);
}
// Check if the user already has external accounts linked. If they do,
// it's not obvious why they aren't using them to log in, but assume they
// know what they're doing. We won't send them to the link workflow.
$accounts = id(new PhabricatorExternalAccountQuery())
->setViewer($user)
->withUserPHIDs(array($user->getPHID()))
->execute();
$configs = id(new PhabricatorAuthProviderConfigQuery())
->setViewer($user)
->withIsEnabled(true)
->execute();
$linkable = array();
foreach ($configs as $config) {
if (!$config->getShouldAllowLink()) {
continue;
}
$provider = $config->getProvider();
if (!$provider->isLoginFormAButton()) {
continue;
}
$linkable[] = $provider;
}
// If there's at least one linkable provider, and the user doesn't already
// have accounts, send the user to the link workflow.
if (!$accounts && $linkable) {
return '/auth/external/';
}
// If there are no configured providers and the user is an administrator,
// send them to Auth to configure a provider. This is probably where they
// want to go. You can end up in this state by accidentally losing your
// first session during initial setup, or after restoring exported data
// from a hosted instance.
if (!$configs && $user->getIsAdmin()) {
return '/auth/';
}
// If we didn't find anywhere better to send them, give up and just send
// them to the home page.
return '/';
}
}
diff --git a/src/applications/auth/controller/PhabricatorAuthRegisterController.php b/src/applications/auth/controller/PhabricatorAuthRegisterController.php
index 30aa770f30..9fbf30d092 100644
--- a/src/applications/auth/controller/PhabricatorAuthRegisterController.php
+++ b/src/applications/auth/controller/PhabricatorAuthRegisterController.php
@@ -1,751 +1,758 @@
<?php
final class PhabricatorAuthRegisterController
extends PhabricatorAuthController {
public function shouldRequireLogin() {
return false;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$account_key = $request->getURIData('akey');
if ($viewer->isLoggedIn()) {
return id(new AphrontRedirectResponse())->setURI('/');
}
$invite = $this->loadInvite();
$is_setup = false;
if (strlen($account_key)) {
$result = $this->loadAccountForRegistrationOrLinking($account_key);
list($account, $provider, $response) = $result;
$is_default = false;
} else if ($this->isFirstTimeSetup()) {
$account = null;
$provider = null;
$response = null;
$is_default = true;
$is_setup = true;
} else {
list($account, $provider, $response) = $this->loadDefaultAccount($invite);
$is_default = true;
}
if ($response) {
return $response;
}
if (!$is_setup) {
if (!$provider->shouldAllowRegistration()) {
if ($invite) {
// If the user has an invite, we allow them to register with any
// provider, even a login-only provider.
} else {
// TODO: This is a routine error if you click "Login" on an external
// auth source which doesn't allow registration. The error should be
// more tailored.
return $this->renderError(
pht(
'The account you are attempting to register with uses an '.
'authentication provider ("%s") which does not allow '.
'registration. An administrator may have recently disabled '.
'registration with this provider.',
$provider->getProviderName()));
}
}
}
$errors = array();
$user = new PhabricatorUser();
if ($is_setup) {
$default_username = null;
$default_realname = null;
$default_email = null;
} else {
$default_username = $account->getUsername();
$default_realname = $account->getRealName();
$default_email = $account->getEmail();
}
$account_type = PhabricatorAuthPassword::PASSWORD_TYPE_ACCOUNT;
$content_source = PhabricatorContentSource::newFromRequest($request);
if ($invite) {
$default_email = $invite->getEmailAddress();
}
if ($default_email !== null) {
if (!PhabricatorUserEmail::isValidAddress($default_email)) {
$errors[] = pht(
'The email address associated with this external account ("%s") is '.
- 'not a valid email address and can not be used to register a '.
- 'Phabricator account. Choose a different, valid address.',
+ 'not a valid email address and can not be used to register an '.
+ 'account. Choose a different, valid address.',
phutil_tag('strong', array(), $default_email));
$default_email = null;
}
}
if ($default_email !== null) {
// We should bypass policy here because e.g. limiting an application use
// to a subset of users should not allow the others to overwrite
// configured application emails.
$application_email = id(new PhabricatorMetaMTAApplicationEmailQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withAddresses(array($default_email))
->executeOne();
if ($application_email) {
$errors[] = pht(
'The email address associated with this account ("%s") is '.
'already in use by an application and can not be used to '.
- 'register a new Phabricator account. Choose a different, valid '.
- 'address.',
+ 'register a new account. Choose a different, valid address.',
phutil_tag('strong', array(), $default_email));
$default_email = null;
}
}
$show_existing = null;
if ($default_email !== null) {
// If the account source provided an email, but it's not allowed by
// the configuration, roadblock the user. Previously, we let the user
// pick a valid email address instead, but this does not align well with
// user expectation and it's not clear the cases it enables are valuable.
// See discussion in T3472.
if (!PhabricatorUserEmail::isAllowedAddress($default_email)) {
$debug_email = new PHUIInvisibleCharacterView($default_email);
return $this->renderError(
array(
pht(
'The account you are attempting to register with has an invalid '.
- 'email address (%s). This Phabricator install only allows '.
- 'registration with specific email addresses:',
+ 'email address (%s). This server only allows registration with '.
+ 'specific email addresses:',
$debug_email),
phutil_tag('br'),
phutil_tag('br'),
PhabricatorUserEmail::describeAllowedAddresses(),
));
}
// If the account source provided an email, but another account already
// has that email, just pretend we didn't get an email.
if ($default_email !== null) {
$same_email = id(new PhabricatorUserEmail())->loadOneWhere(
'address = %s',
$default_email);
if ($same_email) {
if ($invite) {
// We're allowing this to continue. The fact that we loaded the
// invite means that the address is nonprimary and unverified and
// we're OK to steal it.
} else {
$show_existing = $default_email;
$default_email = null;
}
}
}
}
if ($show_existing !== null) {
if (!$request->getInt('phase')) {
return $this->newDialog()
->setTitle(pht('Email Address Already in Use'))
->addHiddenInput('phase', 1)
->appendParagraph(
pht(
- 'You are creating a new Phabricator account linked to an '.
- 'existing external account from outside Phabricator.'))
+ 'You are creating a new account linked to an existing '.
+ 'external account.'))
->appendParagraph(
pht(
'The email address ("%s") associated with the external account '.
- 'is already in use by an existing Phabricator account. Multiple '.
- 'Phabricator accounts may not have the same email address, so '.
- 'you can not use this email address to register a new '.
- 'Phabricator account.',
- phutil_tag('strong', array(), $show_existing)))
+ 'is already in use by an existing %s account. Multiple '.
+ '%s accounts may not have the same email address, so '.
+ 'you can not use this email address to register a new account.',
+ phutil_tag('strong', array(), $show_existing),
+ PlatformSymbols::getPlatformServerName(),
+ PlatformSymbols::getPlatformServerName()))
->appendParagraph(
pht(
'If you want to register a new account, continue with this '.
'registration workflow and choose a new, unique email address '.
'for the new account.'))
->appendParagraph(
pht(
- 'If you want to link an existing Phabricator account to this '.
+ 'If you want to link an existing %s account to this '.
'external account, do not continue. Instead: log in to your '.
'existing account, then go to "Settings" and link the account '.
- 'in the "External Accounts" panel.'))
+ 'in the "External Accounts" panel.',
+ PlatformSymbols::getPlatformServerName()))
->appendParagraph(
pht(
'If you continue, you will create a new account. You will not '.
'be able to link this external account to an existing account.'))
->addCancelButton('/auth/login/', pht('Cancel'))
->addSubmitButton(pht('Create New Account'));
} else {
$errors[] = pht(
'The external account you are registering with has an email address '.
- 'that is already in use ("%s") by an existing Phabricator account. '.
- 'Choose a new, valid email address to register a new Phabricator '.
- 'account.',
- phutil_tag('strong', array(), $show_existing));
+ 'that is already in use ("%s") by an existing %s account. '.
+ 'Choose a new, valid email address to register a new account.',
+ phutil_tag('strong', array(), $show_existing),
+ PlatformSymbols::getPlatformServerName());
}
}
$profile = id(new PhabricatorRegistrationProfile())
->setDefaultUsername($default_username)
->setDefaultEmail($default_email)
->setDefaultRealName($default_realname)
->setCanEditUsername(true)
->setCanEditEmail(($default_email === null))
->setCanEditRealName(true)
->setShouldVerifyEmail(false);
$event_type = PhabricatorEventType::TYPE_AUTH_WILLREGISTERUSER;
$event_data = array(
'account' => $account,
'profile' => $profile,
);
$event = id(new PhabricatorEvent($event_type, $event_data))
->setUser($user);
PhutilEventEngine::dispatchEvent($event);
$default_username = $profile->getDefaultUsername();
$default_email = $profile->getDefaultEmail();
$default_realname = $profile->getDefaultRealName();
$can_edit_username = $profile->getCanEditUsername();
$can_edit_email = $profile->getCanEditEmail();
$can_edit_realname = $profile->getCanEditRealName();
if ($is_setup) {
$must_set_password = false;
} else {
$must_set_password = $provider->shouldRequireRegistrationPassword();
}
$can_edit_anything = $profile->getCanEditAnything() || $must_set_password;
$force_verify = $profile->getShouldVerifyEmail();
// Automatically verify the administrator's email address during first-time
// setup.
if ($is_setup) {
$force_verify = true;
}
$value_username = $default_username;
$value_realname = $default_realname;
$value_email = $default_email;
$value_password = null;
$require_real_name = PhabricatorEnv::getEnvConfig('user.require-real-name');
$e_username = strlen($value_username) ? null : true;
$e_realname = $require_real_name ? true : null;
$e_email = strlen($value_email) ? null : true;
$e_password = true;
$e_captcha = true;
$skip_captcha = false;
if ($invite) {
// If the user is accepting an invite, assume they're trustworthy enough
// that we don't need to CAPTCHA them.
$skip_captcha = true;
}
$min_len = PhabricatorEnv::getEnvConfig('account.minimum-password-length');
$min_len = (int)$min_len;
$from_invite = $request->getStr('invite');
if ($from_invite && $can_edit_username) {
$value_username = $request->getStr('username');
$e_username = null;
}
$try_register =
($request->isFormPost() || !$can_edit_anything) &&
!$from_invite &&
($request->getInt('phase') != 1);
if ($try_register) {
$errors = array();
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
if ($must_set_password && !$skip_captcha) {
$e_captcha = pht('Again');
$captcha_ok = AphrontFormRecaptchaControl::processCaptcha($request);
if (!$captcha_ok) {
$errors[] = pht('Captcha response is incorrect, try again.');
$e_captcha = pht('Invalid');
}
}
if ($can_edit_username) {
$value_username = $request->getStr('username');
if (!strlen($value_username)) {
$e_username = pht('Required');
$errors[] = pht('Username is required.');
} else if (!PhabricatorUser::validateUsername($value_username)) {
$e_username = pht('Invalid');
$errors[] = PhabricatorUser::describeValidUsername();
} else {
$e_username = null;
}
}
if ($must_set_password) {
$value_password = $request->getStr('password');
$value_confirm = $request->getStr('confirm');
$password_envelope = new PhutilOpaqueEnvelope($value_password);
$confirm_envelope = new PhutilOpaqueEnvelope($value_confirm);
$engine = id(new PhabricatorAuthPasswordEngine())
->setViewer($user)
->setContentSource($content_source)
->setPasswordType($account_type)
->setObject($user);
try {
$engine->checkNewPassword($password_envelope, $confirm_envelope);
$e_password = null;
} catch (PhabricatorAuthPasswordException $ex) {
$errors[] = $ex->getMessage();
$e_password = $ex->getPasswordError();
}
}
if ($can_edit_email) {
$value_email = $request->getStr('email');
if (!strlen($value_email)) {
$e_email = pht('Required');
$errors[] = pht('Email is required.');
} else if (!PhabricatorUserEmail::isValidAddress($value_email)) {
$e_email = pht('Invalid');
$errors[] = PhabricatorUserEmail::describeValidAddresses();
} else if (!PhabricatorUserEmail::isAllowedAddress($value_email)) {
$e_email = pht('Disallowed');
$errors[] = PhabricatorUserEmail::describeAllowedAddresses();
} else {
$e_email = null;
}
}
if ($can_edit_realname) {
$value_realname = $request->getStr('realName');
if (!strlen($value_realname) && $require_real_name) {
$e_realname = pht('Required');
$errors[] = pht('Real name is required.');
} else {
$e_realname = null;
}
}
if (!$errors) {
if (!$is_setup) {
$image = $this->loadProfilePicture($account);
if ($image) {
$user->setProfileImagePHID($image->getPHID());
}
}
try {
$verify_email = false;
if ($force_verify) {
$verify_email = true;
}
if (!$is_setup) {
if ($value_email === $default_email) {
if ($account->getEmailVerified()) {
$verify_email = true;
}
if ($provider->shouldTrustEmails()) {
$verify_email = true;
}
if ($invite) {
$verify_email = true;
}
}
}
$email_obj = null;
if ($invite) {
// If we have a valid invite, this email may exist but be
// nonprimary and unverified, so we'll reassign it.
$email_obj = id(new PhabricatorUserEmail())->loadOneWhere(
'address = %s',
$value_email);
}
if (!$email_obj) {
$email_obj = id(new PhabricatorUserEmail())
->setAddress($value_email);
}
$email_obj->setIsVerified((int)$verify_email);
$user->setUsername($value_username);
$user->setRealname($value_realname);
if ($is_setup) {
$must_approve = false;
} else if ($invite) {
$must_approve = false;
} else {
$must_approve = PhabricatorEnv::getEnvConfig(
'auth.require-approval');
}
if ($must_approve) {
$user->setIsApproved(0);
} else {
$user->setIsApproved(1);
}
if ($invite) {
$allow_reassign_email = true;
} else {
$allow_reassign_email = false;
}
$user->openTransaction();
$editor = id(new PhabricatorUserEditor())
->setActor($user);
$editor->createNewUser($user, $email_obj, $allow_reassign_email);
if ($must_set_password) {
$password_object = PhabricatorAuthPassword::initializeNewPassword(
$user,
$account_type);
$password_object
->setPassword($password_envelope, $user)
->save();
}
if ($is_setup) {
$xactions = array();
$xactions[] = id(new PhabricatorUserTransaction())
->setTransactionType(
PhabricatorUserEmpowerTransaction::TRANSACTIONTYPE)
->setNewValue(true);
$actor = PhabricatorUser::getOmnipotentUser();
$content_source = PhabricatorContentSource::newFromRequest(
$request);
$people_application_phid = id(new PhabricatorPeopleApplication())
->getPHID();
$transaction_editor = id(new PhabricatorUserTransactionEditor())
->setActor($actor)
->setActingAsPHID($people_application_phid)
->setContentSource($content_source)
->setContinueOnMissingFields(true);
$transaction_editor->applyTransactions($user, $xactions);
}
if (!$is_setup) {
$account->setUserPHID($user->getPHID());
$account->save();
}
$user->saveTransaction();
if (!$email_obj->getIsVerified()) {
$email_obj->sendVerificationEmail($user);
}
if ($must_approve) {
$this->sendWaitingForApprovalEmail($user);
}
if ($invite) {
$invite->setAcceptedByPHID($user->getPHID())->save();
}
return $this->loginUser($user);
} catch (AphrontDuplicateKeyQueryException $exception) {
$same_username = id(new PhabricatorUser())->loadOneWhere(
'userName = %s',
$user->getUserName());
$same_email = id(new PhabricatorUserEmail())->loadOneWhere(
'address = %s',
$value_email);
if ($same_username) {
$e_username = pht('Duplicate');
$errors[] = pht('Another user already has that username.');
}
if ($same_email) {
// TODO: See T3340.
$e_email = pht('Duplicate');
$errors[] = pht('Another user already has that email.');
}
if (!$same_username && !$same_email) {
throw $exception;
}
}
}
unset($unguarded);
}
$form = id(new AphrontFormView())
->setUser($request->getUser())
->addHiddenInput('phase', 2);
if (!$is_default) {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('External Account'))
->setValue(
id(new PhabricatorAuthAccountView())
->setUser($request->getUser())
->setExternalAccount($account)
->setAuthProvider($provider)));
}
if ($can_edit_username) {
$form->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Username'))
->setName('username')
->setValue($value_username)
->setError($e_username));
} else {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Username'))
->setValue($value_username)
->setError($e_username));
}
if ($can_edit_realname) {
$form->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Real Name'))
->setName('realName')
->setValue($value_realname)
->setError($e_realname));
}
if ($must_set_password) {
$form->appendChild(
id(new AphrontFormPasswordControl())
->setLabel(pht('Password'))
->setName('password')
->setError($e_password));
$form->appendChild(
id(new AphrontFormPasswordControl())
->setLabel(pht('Confirm Password'))
->setName('confirm')
->setError($e_password)
->setCaption(
$min_len
? pht('Minimum length of %d characters.', $min_len)
: null));
}
if ($can_edit_email) {
$form->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Email'))
->setName('email')
->setValue($value_email)
->setCaption(PhabricatorUserEmail::describeAllowedAddresses())
->setError($e_email));
}
if ($must_set_password && !$skip_captcha) {
$form->appendChild(
id(new AphrontFormRecaptchaControl())
->setLabel(pht('Captcha'))
->setError($e_captcha));
}
$submit = id(new AphrontFormSubmitControl());
if ($is_setup) {
$submit
->setValue(pht('Create Admin Account'));
} else {
$submit
->addCancelButton($this->getApplicationURI('start/'))
->setValue(pht('Register Account'));
}
$form->appendChild($submit);
$crumbs = $this->buildApplicationCrumbs();
if ($is_setup) {
$crumbs->addTextCrumb(pht('Setup Admin Account'));
- $title = pht('Welcome to Phabricator');
+ $title = pht(
+ 'Welcome to %s',
+ PlatformSymbols::getPlatformServerName());
} else {
$crumbs->addTextCrumb(pht('Register'));
$crumbs->addTextCrumb($provider->getProviderName());
$title = pht('Create a New Account');
}
$crumbs->setBorder(true);
$welcome_view = null;
if ($is_setup) {
$welcome_view = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
- ->setTitle(pht('Welcome to Phabricator'))
+ ->setTitle(
+ pht(
+ 'Welcome to %s',
+ PlatformSymbols::getPlatformServerName()))
->appendChild(
pht(
'Installation is complete. Register your administrator account '.
'below to log in. You will be able to configure options and add '.
'authentication mechanisms later on.'));
}
$object_box = id(new PHUIObjectBoxView())
->setForm($form)
->setFormErrors($errors);
$invite_header = null;
if ($invite) {
$invite_header = $this->renderInviteHeader($invite);
}
$header = id(new PHUIHeaderView())
->setHeader($title);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(
array(
$welcome_view,
$invite_header,
$object_box,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
private function loadDefaultAccount($invite) {
$providers = PhabricatorAuthProvider::getAllEnabledProviders();
$account = null;
$provider = null;
$response = null;
foreach ($providers as $key => $candidate_provider) {
if (!$invite) {
if (!$candidate_provider->shouldAllowRegistration()) {
unset($providers[$key]);
continue;
}
}
if (!$candidate_provider->isDefaultRegistrationProvider()) {
unset($providers[$key]);
}
}
if (!$providers) {
$response = $this->renderError(
pht(
'There are no configured default registration providers.'));
return array($account, $provider, $response);
} else if (count($providers) > 1) {
$response = $this->renderError(
pht('There are too many configured default registration providers.'));
return array($account, $provider, $response);
}
$provider = head($providers);
$account = $provider->newDefaultExternalAccount();
return array($account, $provider, $response);
}
private function loadProfilePicture(PhabricatorExternalAccount $account) {
$phid = $account->getProfileImagePHID();
if (!$phid) {
return null;
}
// NOTE: Use of omnipotent user is okay here because the registering user
// can not control the field value, and we can't use their user object to
// do meaningful policy checks anyway since they have not registered yet.
// Reaching this means the user holds the account secret key and the
// registration secret key, and thus has permission to view the image.
$file = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($phid))
->executeOne();
if (!$file) {
return null;
}
$xform = PhabricatorFileTransform::getTransformByKey(
PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE);
return $xform->executeTransform($file);
}
protected function renderError($message) {
return $this->renderErrorPage(
pht('Registration Failed'),
array($message));
}
private function sendWaitingForApprovalEmail(PhabricatorUser $user) {
- $title = '[Phabricator] '.pht(
- 'New User "%s" Awaiting Approval',
+ $title = pht(
+ '[%s] New User "%s" Awaiting Approval',
+ PlatformSymbols::getPlatformServerName(),
$user->getUsername());
$body = new PhabricatorMetaMTAMailBody();
$body->addRawSection(
pht(
'Newly registered user "%s" is awaiting account approval by an '.
'administrator.',
$user->getUsername()));
$body->addLinkSection(
pht('APPROVAL QUEUE'),
PhabricatorEnv::getProductionURI(
'/people/query/approval/'));
$body->addLinkSection(
pht('DISABLE APPROVAL QUEUE'),
PhabricatorEnv::getProductionURI(
'/config/edit/auth.require-approval/'));
$admins = id(new PhabricatorPeopleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withIsAdmin(true)
->execute();
if (!$admins) {
return;
}
$mail = id(new PhabricatorMetaMTAMail())
->addTos(mpull($admins, 'getPHID'))
->setSubject($title)
->setBody($body->render())
->saveAndSend();
}
}
diff --git a/src/applications/auth/controller/PhabricatorAuthSSHKeyGenerateController.php b/src/applications/auth/controller/PhabricatorAuthSSHKeyGenerateController.php
index 206a2f1c4a..4a48a666c2 100644
--- a/src/applications/auth/controller/PhabricatorAuthSSHKeyGenerateController.php
+++ b/src/applications/auth/controller/PhabricatorAuthSSHKeyGenerateController.php
@@ -1,119 +1,119 @@
<?php
final class PhabricatorAuthSSHKeyGenerateController
extends PhabricatorAuthSSHKeyController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$key = $this->newKeyForObjectPHID($request->getStr('objectPHID'));
if (!$key) {
return new Aphront404Response();
}
$cancel_uri = $key->getObject()->getSSHPublicKeyManagementURI($viewer);
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
$request,
$cancel_uri);
if ($request->isFormPost()) {
$default_name = $key->getObject()->getSSHKeyDefaultName();
$keys = PhabricatorSSHKeyGenerator::generateKeypair();
list($public_key, $private_key) = $keys;
$key_name = $default_name.'.key';
$file = PhabricatorFile::newFromFileData(
$private_key,
array(
'name' => $key_name,
'ttl.relative' => phutil_units('10 minutes in seconds'),
'viewPolicy' => $viewer->getPHID(),
));
$public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($public_key);
$type = $public_key->getType();
$body = $public_key->getBody();
$comment = pht('Generated');
$entire_key = "{$type} {$body} {$comment}";
$type_create = PhabricatorTransactions::TYPE_CREATE;
$type_name = PhabricatorAuthSSHKeyTransaction::TYPE_NAME;
$type_key = PhabricatorAuthSSHKeyTransaction::TYPE_KEY;
$xactions = array();
$xactions[] = id(new PhabricatorAuthSSHKeyTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_CREATE);
$xactions[] = id(new PhabricatorAuthSSHKeyTransaction())
->setTransactionType($type_name)
->setNewValue($default_name);
$xactions[] = id(new PhabricatorAuthSSHKeyTransaction())
->setTransactionType($type_key)
->setNewValue($entire_key);
$editor = id(new PhabricatorAuthSSHKeyEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->applyTransactions($key, $xactions);
$download_link = phutil_tag(
'a',
array(
'href' => $file->getDownloadURI(),
),
array(
id(new PHUIIconView())->setIcon('fa-download'),
' ',
pht('Download Private Key (%s)', $key_name),
));
$download_link = phutil_tag('strong', array(), $download_link);
// NOTE: We're disabling workflow on cancel so the page reloads, showing
// the new key.
return $this->newDialog()
->setTitle(pht('Download Private Key'))
->appendParagraph(
pht(
'A keypair has been generated, and the public key has been '.
'added as a recognized key.'))
->appendParagraph($download_link)
->appendParagraph(
pht(
'After you download the private key, it will be destroyed. '.
'You will not be able to retrieve it if you lose your copy.'))
->setDisableWorkflowOnCancel(true)
->addCancelButton($cancel_uri, pht('Done'));
}
try {
PhabricatorSSHKeyGenerator::assertCanGenerateKeypair();
return $this->newDialog()
->setTitle(pht('Generate New Keypair'))
->addHiddenInput('objectPHID', $key->getObject()->getPHID())
->appendParagraph(
pht(
'This workflow will generate a new SSH keypair, add the public '.
'key, and let you download the private key.'))
->appendParagraph(
- pht('Phabricator will not retain a copy of the private key.'))
+ pht('The private key will not be retained.'))
->addSubmitButton(pht('Generate New Keypair'))
->addCancelButton($cancel_uri);
} catch (Exception $ex) {
return $this->newDialog()
->setTitle(pht('Unable to Generate Keys'))
->appendParagraph($ex->getMessage())
->addCancelButton($cancel_uri);
}
}
}
diff --git a/src/applications/auth/controller/PhabricatorAuthSetExternalController.php b/src/applications/auth/controller/PhabricatorAuthSetExternalController.php
index 51dfcab53f..8b0a44b9dc 100644
--- a/src/applications/auth/controller/PhabricatorAuthSetExternalController.php
+++ b/src/applications/auth/controller/PhabricatorAuthSetExternalController.php
@@ -1,110 +1,111 @@
<?php
final class PhabricatorAuthSetExternalController
extends PhabricatorAuthController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$configs = id(new PhabricatorAuthProviderConfigQuery())
->setViewer($viewer)
->withIsEnabled(true)
->execute();
$linkable = array();
foreach ($configs as $config) {
if (!$config->getShouldAllowLink()) {
continue;
}
// For now, only buttons get to appear here: for example, we can't
// reasonably embed an entire LDAP form into this UI.
$provider = $config->getProvider();
if (!$provider->isLoginFormAButton()) {
continue;
}
$linkable[] = $config;
}
if (!$linkable) {
return $this->newDialog()
->setTitle(pht('No Linkable External Providers'))
->appendParagraph(
pht(
'Currently, there are no configured external auth providers '.
'which you can link your account to.'))
->addCancelButton('/');
}
$text = PhabricatorAuthMessage::loadMessageText(
$viewer,
PhabricatorAuthLinkMessageType::MESSAGEKEY);
if (!strlen($text)) {
$text = pht(
- 'You can link your Phabricator account to an external account to '.
+ 'You can link your %s account to an external account to '.
'allow you to log in more easily in the future. To continue, choose '.
'an account to link below. If you prefer not to link your account, '.
- 'you can skip this step.');
+ 'you can skip this step.',
+ PlatformSymbols::getPlatformServerName());
}
$remarkup_view = new PHUIRemarkupView($viewer, $text);
$remarkup_view = phutil_tag(
'div',
array(
'class' => 'phui-object-box-instructions',
),
$remarkup_view);
PhabricatorCookies::setClientIDCookie($request);
$view = array();
foreach ($configs as $config) {
$provider = $config->getProvider();
$form = $provider->buildLinkForm($this);
if ($provider->isLoginFormAButton()) {
require_celerity_resource('auth-css');
$form = phutil_tag(
'div',
array(
'class' => 'phabricator-link-button pl',
),
$form);
}
$view[] = $form;
}
$form = id(new AphrontFormView())
->setViewer($viewer)
->appendControl(
id(new AphrontFormSubmitControl())
->addCancelButton('/', pht('Skip This Step')));
$header = id(new PHUIHeaderView())
->setHeader(pht('Link External Account'));
$box = id(new PHUIObjectBoxView())
->setViewer($viewer)
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($remarkup_view)
->appendChild($view)
->appendChild($form);
$main_view = id(new PHUITwoColumnView())
->setFooter($box);
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Link External Account'))
->setBorder(true);
return $this->newPage()
->setTitle(pht('Link External Account'))
->setCrumbs($crumbs)
->appendChild($main_view);
}
}
diff --git a/src/applications/auth/controller/PhabricatorAuthStartController.php b/src/applications/auth/controller/PhabricatorAuthStartController.php
index 0c42f53556..5789740e14 100644
--- a/src/applications/auth/controller/PhabricatorAuthStartController.php
+++ b/src/applications/auth/controller/PhabricatorAuthStartController.php
@@ -1,340 +1,340 @@
<?php
final class PhabricatorAuthStartController
extends PhabricatorAuthController {
public function shouldRequireLogin() {
return false;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getUser();
if ($viewer->isLoggedIn()) {
// Kick the user home if they are already logged in.
return id(new AphrontRedirectResponse())->setURI('/');
}
if ($request->isAjax()) {
return $this->processAjaxRequest();
}
if ($request->isConduit()) {
return $this->processConduitRequest();
}
// If the user gets this far, they aren't logged in, so if they have a
// user session token we can conclude that it's invalid: if it was valid,
// they'd have been logged in above and never made it here. Try to clear
// it and warn the user they may need to nuke their cookies.
$session_token = $request->getCookie(PhabricatorCookies::COOKIE_SESSION);
$did_clear = $request->getStr('cleared');
if (strlen($session_token)) {
$kind = PhabricatorAuthSessionEngine::getSessionKindFromToken(
$session_token);
switch ($kind) {
case PhabricatorAuthSessionEngine::KIND_ANONYMOUS:
// If this is an anonymous session. It's expected that they won't
// be logged in, so we can just continue.
break;
default:
// The session cookie is invalid, so try to clear it.
$request->clearCookie(PhabricatorCookies::COOKIE_USERNAME);
$request->clearCookie(PhabricatorCookies::COOKIE_SESSION);
// We've previously tried to clear the cookie but we ended up back
// here, so it didn't work. Hard fatal instead of trying again.
if ($did_clear) {
return $this->renderError(
pht(
'Your login session is invalid, and clearing the session '.
'cookie was unsuccessful. Try clearing your browser cookies.'));
}
$redirect_uri = $request->getRequestURI();
$redirect_uri->replaceQueryParam('cleared', 1);
return id(new AphrontRedirectResponse())->setURI($redirect_uri);
}
}
// If we just cleared the session cookie and it worked, clean up after
// ourselves by redirecting to get rid of the "cleared" parameter. The
// the workflow will continue normally.
if ($did_clear) {
$redirect_uri = $request->getRequestURI();
$redirect_uri->removeQueryParam('cleared');
return id(new AphrontRedirectResponse())->setURI($redirect_uri);
}
$providers = PhabricatorAuthProvider::getAllEnabledProviders();
foreach ($providers as $key => $provider) {
if (!$provider->shouldAllowLogin()) {
unset($providers[$key]);
}
}
$configs = array();
foreach ($providers as $provider) {
$configs[] = $provider->getProviderConfig();
}
if (!$providers) {
if ($this->isFirstTimeSetup()) {
// If this is a fresh install, let the user register their admin
// account.
return id(new AphrontRedirectResponse())
->setURI($this->getApplicationURI('/register/'));
}
return $this->renderError(
pht(
- 'This Phabricator install is not configured with any enabled '.
- 'authentication providers which can be used to log in. If you '.
- 'have accidentally locked yourself out by disabling all providers, '.
- 'you can use `%s` to recover access to an account.',
- 'phabricator/bin/auth recover <username>'));
+ 'This server is not configured with any enabled authentication '.
+ 'providers which can be used to log in. If you have accidentally '.
+ 'locked yourself out by disabling all providers, you can use `%s` '.
+ 'to recover access to an account.',
+ './bin/auth recover <username>'));
}
$next_uri = $request->getStr('next');
if (!strlen($next_uri)) {
if ($this->getDelegatingController()) {
// Only set a next URI from the request path if this controller was
// delegated to, which happens when a user tries to view a page which
// requires them to login.
// If this controller handled the request directly, we're on the main
// login page, and never want to redirect the user back here after they
// login.
$next_uri = (string)$this->getRequest()->getRequestURI();
}
}
if (!$request->isFormPost()) {
if (strlen($next_uri)) {
PhabricatorCookies::setNextURICookie($request, $next_uri);
}
PhabricatorCookies::setClientIDCookie($request);
}
$auto_response = $this->tryAutoLogin($providers);
if ($auto_response) {
return $auto_response;
}
$invite = $this->loadInvite();
$not_buttons = array();
$are_buttons = array();
$providers = msort($providers, 'getLoginOrder');
foreach ($providers as $provider) {
if ($invite) {
$form = $provider->buildInviteForm($this);
} else {
$form = $provider->buildLoginForm($this);
}
if ($provider->isLoginFormAButton()) {
$are_buttons[] = $form;
} else {
$not_buttons[] = $form;
}
}
$out = array();
$out[] = $not_buttons;
if ($are_buttons) {
require_celerity_resource('auth-css');
foreach ($are_buttons as $key => $button) {
$are_buttons[$key] = phutil_tag(
'div',
array(
'class' => 'phabricator-login-button mmb',
),
$button);
}
// If we only have one button, add a second pretend button so that we
// always have two columns. This makes it easier to get the alignments
// looking reasonable.
if (count($are_buttons) == 1) {
$are_buttons[] = null;
}
$button_columns = id(new AphrontMultiColumnView())
->setFluidLayout(true);
$are_buttons = array_chunk($are_buttons, ceil(count($are_buttons) / 2));
foreach ($are_buttons as $column) {
$button_columns->addColumn($column);
}
$out[] = phutil_tag(
'div',
array(
'class' => 'phabricator-login-buttons',
),
$button_columns);
}
$invite_message = null;
if ($invite) {
$invite_message = $this->renderInviteHeader($invite);
}
$custom_message = $this->newCustomStartMessage();
$email_login = $this->newEmailLoginView($configs);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Login'));
$crumbs->setBorder(true);
$title = pht('Login');
$view = array(
$invite_message,
$custom_message,
$out,
$email_login,
);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
private function processAjaxRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
// We end up here if the user clicks a workflow link that they need to
// login to use. We give them a dialog saying "You need to login...".
if ($request->isDialogFormPost()) {
return id(new AphrontRedirectResponse())->setURI(
$request->getRequestURI());
}
// Often, users end up here by clicking a disabled action link in the UI
// (for example, they might click "Edit Subtasks" on a Maniphest task
// page). After they log in we want to send them back to that main object
// page if we can, since it's confusing to end up on a standalone page with
// only a dialog (particularly if that dialog is another error,
// like a policy exception).
$via_header = AphrontRequest::getViaHeaderName();
$via_uri = AphrontRequest::getHTTPHeader($via_header);
if (strlen($via_uri)) {
PhabricatorCookies::setNextURICookie($request, $via_uri, $force = true);
}
return $this->newDialog()
->setTitle(pht('Login Required'))
->appendParagraph(pht('You must log in to take this action.'))
->addSubmitButton(pht('Log In'))
->addCancelButton('/');
}
private function processConduitRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
// A common source of errors in Conduit client configuration is getting
// the request path wrong. The client will end up here, so make some
// effort to give them a comprehensible error message.
$request_path = $this->getRequest()->getPath();
$conduit_path = '/api/<method>';
$example_path = '/api/conduit.ping';
$message = pht(
'ERROR: You are making a Conduit API request to "%s", but the correct '.
'HTTP request path to use in order to access a Conduit method is "%s" '.
'(for example, "%s"). Check your configuration.',
$request_path,
$conduit_path,
$example_path);
return id(new AphrontPlainTextResponse())->setContent($message);
}
protected function renderError($message) {
return $this->renderErrorPage(
pht('Authentication Failure'),
array($message));
}
private function tryAutoLogin(array $providers) {
$request = $this->getRequest();
// If the user just logged out, don't immediately log them in again.
if ($request->getURIData('loggedout')) {
return null;
}
// If we have more than one provider, we can't autologin because we
// don't know which one the user wants.
if (count($providers) != 1) {
return null;
}
$provider = head($providers);
if (!$provider->supportsAutoLogin()) {
return null;
}
$config = $provider->getProviderConfig();
if (!$config->getShouldAutoLogin()) {
return null;
}
$auto_uri = $provider->getAutoLoginURI($request);
return id(new AphrontRedirectResponse())
->setIsExternal(true)
->setURI($auto_uri);
}
private function newEmailLoginView(array $configs) {
assert_instances_of($configs, 'PhabricatorAuthProviderConfig');
// Check if password auth is enabled. If it is, the password login form
// renders a "Forgot password?" link, so we don't need to provide a
// supplemental link.
$has_password = false;
foreach ($configs as $config) {
$provider = $config->getProvider();
if ($provider instanceof PhabricatorPasswordAuthProvider) {
$has_password = true;
}
}
if ($has_password) {
return null;
}
$view = array(
pht('Trouble logging in?'),
' ',
phutil_tag(
'a',
array(
'href' => '/login/email/',
),
pht('Send a login link to your email address.')),
);
return phutil_tag(
'div',
array(
'class' => 'auth-custom-message',
),
$view);
}
}
diff --git a/src/applications/auth/controller/PhabricatorAuthUnlinkController.php b/src/applications/auth/controller/PhabricatorAuthUnlinkController.php
index ede9d9d94a..a4843a7ccd 100644
--- a/src/applications/auth/controller/PhabricatorAuthUnlinkController.php
+++ b/src/applications/auth/controller/PhabricatorAuthUnlinkController.php
@@ -1,141 +1,141 @@
<?php
final class PhabricatorAuthUnlinkController
extends PhabricatorAuthController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$account = id(new PhabricatorExternalAccountQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$account) {
return new Aphront404Response();
}
$done_uri = '/settings/panel/external/';
$config = $account->getProviderConfig();
$provider = $config->getProvider();
if (!$provider->shouldAllowAccountUnlink()) {
return $this->renderNotUnlinkableErrorDialog($provider, $done_uri);
}
$confirmations = $request->getStrList('confirmations');
$confirmations = array_fuse($confirmations);
if (!$request->isFormOrHisecPost() || !isset($confirmations['unlink'])) {
return $this->renderConfirmDialog($confirmations, $config, $done_uri);
}
// Check that this account isn't the only account which can be used to
// login. We warn you when you remove your only login account.
if ($account->isUsableForLogin()) {
$other_accounts = id(new PhabricatorExternalAccountQuery())
->setViewer($viewer)
->withUserPHIDs(array($viewer->getPHID()))
->execute();
$valid_accounts = 0;
foreach ($other_accounts as $other_account) {
if ($other_account->isUsableForLogin()) {
$valid_accounts++;
}
}
if ($valid_accounts < 2) {
if (!isset($confirmations['only'])) {
return $this->renderOnlyUsableAccountConfirmDialog(
$confirmations,
$done_uri);
}
}
}
$workflow_key = sprintf(
'account.unlink(%s)',
$account->getPHID());
$hisec_token = id(new PhabricatorAuthSessionEngine())
->setWorkflowKey($workflow_key)
->requireHighSecurityToken($viewer, $request, $done_uri);
$account->unlinkAccount();
id(new PhabricatorAuthSessionEngine())->terminateLoginSessions(
$viewer,
new PhutilOpaqueEnvelope(
$request->getCookie(PhabricatorCookies::COOKIE_SESSION)));
return id(new AphrontRedirectResponse())->setURI($done_uri);
}
private function renderNotUnlinkableErrorDialog(
PhabricatorAuthProvider $provider,
$done_uri) {
return $this->newDialog()
->setTitle(pht('Permanent Account Link'))
->appendChild(
pht(
'You can not unlink this account because the administrator has '.
- 'configured Phabricator to make links to "%s" accounts permanent.',
+ 'configured this server to make links to "%s" accounts permanent.',
$provider->getProviderName()))
->addCancelButton($done_uri);
}
private function renderOnlyUsableAccountConfirmDialog(
array $confirmations,
$done_uri) {
$confirmations[] = 'only';
return $this->newDialog()
->setTitle(pht('Unlink Your Only Login Account?'))
->addHiddenInput('confirmations', implode(',', $confirmations))
->appendParagraph(
pht(
'This is the only external login account linked to your Phabicator '.
'account. If you remove it, you may no longer be able to log in.'))
->appendParagraph(
pht(
'If you lose access to your account, you can recover access by '.
'sending yourself an email login link from the login screen.'))
->addCancelButton($done_uri)
->addSubmitButton(pht('Unlink External Account'));
}
private function renderConfirmDialog(
array $confirmations,
PhabricatorAuthProviderConfig $config,
$done_uri) {
$confirmations[] = 'unlink';
$provider = $config->getProvider();
$title = pht('Unlink "%s" Account?', $provider->getProviderName());
$body = pht(
'You will no longer be able to use your %s account to '.
- 'log in to Phabricator.',
+ 'log in.',
$provider->getProviderName());
return $this->newDialog()
->setTitle($title)
->addHiddenInput('confirmations', implode(',', $confirmations))
->appendParagraph($body)
->appendParagraph(
pht(
'Note: Unlinking an authentication provider will terminate any '.
'other active login sessions.'))
->addSubmitButton(pht('Unlink Account'))
->addCancelButton($done_uri);
}
}
diff --git a/src/applications/auth/controller/PhabricatorEmailVerificationController.php b/src/applications/auth/controller/PhabricatorEmailVerificationController.php
index e8138339af..996795eb94 100644
--- a/src/applications/auth/controller/PhabricatorEmailVerificationController.php
+++ b/src/applications/auth/controller/PhabricatorEmailVerificationController.php
@@ -1,89 +1,89 @@
<?php
final class PhabricatorEmailVerificationController
extends PhabricatorAuthController {
public function shouldRequireEmailVerification() {
// Since users need to be able to hit this endpoint in order to verify
// email, we can't ever require email verification here.
return false;
}
public function shouldRequireEnabledUser() {
// Unapproved users are allowed to verify their email addresses. We'll kick
// disabled users out later.
return false;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$code = $request->getURIData('code');
if ($viewer->getIsDisabled()) {
// We allowed unapproved and disabled users to hit this controller, but
// want to kick out disabled users now.
return new Aphront400Response();
}
$email = id(new PhabricatorUserEmail())->loadOneWhere(
'userPHID = %s AND verificationCode = %s',
$viewer->getPHID(),
$code);
$submit = null;
if (!$email) {
$title = pht('Unable to Verify Email');
$content = pht(
'The verification code you provided is incorrect, or the email '.
'address has been removed, or the email address is owned by another '.
'user. Make sure you followed the link in the email correctly and are '.
'logged in with the user account associated with the email address.');
$continue = pht('Rats!');
} else if ($email->getIsVerified() && $viewer->getIsEmailVerified()) {
$title = pht('Address Already Verified');
$content = pht(
'This email address has already been verified.');
- $continue = pht('Continue to Phabricator');
+ $continue = pht('Continue');
} else if ($request->isFormPost()) {
id(new PhabricatorUserEditor())
->setActor($viewer)
->verifyEmail($viewer, $email);
$title = pht('Address Verified');
$content = pht(
'The email address %s is now verified.',
phutil_tag('strong', array(), $email->getAddress()));
- $continue = pht('Continue to Phabricator');
+ $continue = pht('Continue');
} else {
$title = pht('Verify Email Address');
$content = pht(
'Verify this email address (%s) and attach it to your account?',
phutil_tag('strong', array(), $email->getAddress()));
$continue = pht('Cancel');
$submit = pht('Verify %s', $email->getAddress());
}
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle($title)
->addCancelButton('/', $continue)
->appendChild($content);
if ($submit) {
$dialog->addSubmitButton($submit);
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Verify Email'));
$crumbs->setBorder(true);
return $this->newPage()
->setTitle(pht('Verify Email'))
->setCrumbs($crumbs)
->appendChild($dialog);
}
}
diff --git a/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php b/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php
index bf3410139d..2f15460eac 100644
--- a/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php
+++ b/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php
@@ -1,63 +1,62 @@
<?php
final class PhabricatorMustVerifyEmailController
extends PhabricatorAuthController {
public function shouldRequireEmailVerification() {
// NOTE: We don't technically need this since PhabricatorController forces
// us here in either case, but it's more consistent with intent.
return false;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$email = $viewer->loadPrimaryEmail();
if ($viewer->getIsEmailVerified()) {
return id(new AphrontRedirectResponse())->setURI('/');
}
$email_address = $email->getAddress();
$sent = null;
if ($request->isFormPost()) {
$email->sendVerificationEmail($viewer);
$sent = new PHUIInfoView();
$sent->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
$sent->setTitle(pht('Email Sent'));
$sent->appendChild(
pht(
'Another verification email was sent to %s.',
phutil_tag('strong', array(), $email_address)));
}
$must_verify = pht(
'You must verify your email address to log in. You should have a '.
- 'new email message from Phabricator with verification instructions '.
- 'in your inbox (%s).',
+ 'new email message with verification instructions in your inbox (%s).',
phutil_tag('strong', array(), $email_address));
$send_again = pht(
'If you did not receive an email, you can click the button below '.
'to try sending another one.');
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht('Check Your Email'))
->appendParagraph($must_verify)
->appendParagraph($send_again)
->addSubmitButton(pht('Send Another Email'));
$view = array(
$sent,
$dialog,
);
return $this->newPage()
->setTitle(pht('Must Verify Email'))
->appendChild($view);
}
}
diff --git a/src/applications/auth/controller/config/PhabricatorAuthDisableController.php b/src/applications/auth/controller/config/PhabricatorAuthDisableController.php
index 252f159ec4..15b5461186 100644
--- a/src/applications/auth/controller/config/PhabricatorAuthDisableController.php
+++ b/src/applications/auth/controller/config/PhabricatorAuthDisableController.php
@@ -1,89 +1,89 @@
<?php
final class PhabricatorAuthDisableController
extends PhabricatorAuthProviderConfigController {
public function handleRequest(AphrontRequest $request) {
$this->requireApplicationCapability(
AuthManageProvidersCapability::CAPABILITY);
$viewer = $this->getViewer();
$config_id = $request->getURIData('id');
$action = $request->getURIData('action');
$config = id(new PhabricatorAuthProviderConfigQuery())
->setViewer($viewer)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->withIDs(array($config_id))
->executeOne();
if (!$config) {
return new Aphront404Response();
}
$is_enable = ($action === 'enable');
$done_uri = $config->getURI();
if ($request->isDialogFormPost()) {
$xactions = array();
$xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
->setTransactionType(
PhabricatorAuthProviderConfigTransaction::TYPE_ENABLE)
->setNewValue((int)$is_enable);
$editor = id(new PhabricatorAuthProviderConfigEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->applyTransactions($config, $xactions);
return id(new AphrontRedirectResponse())->setURI($done_uri);
}
if ($is_enable) {
$title = pht('Enable Provider?');
if ($config->getShouldAllowRegistration()) {
$body = pht(
'Do you want to enable this provider? Users will be able to use '.
- 'their existing external accounts to register new Phabricator '.
- 'accounts and log in using linked accounts.');
+ 'their existing external accounts to register new accounts and '.
+ 'log in using linked accounts.');
} else {
$body = pht(
'Do you want to enable this provider? Users will be able to log '.
- 'in to Phabricator using linked accounts.');
+ 'in using linked accounts.');
}
$button = pht('Enable Provider');
} else {
// TODO: We could tailor this a bit more. In particular, we could
// check if this is the last provider and either prevent if from
// being disabled or force the user through like 35 prompts. We could
// also check if it's the last provider linked to the acting user's
// account and pop a warning like "YOU WILL NO LONGER BE ABLE TO LOGIN
// YOU GOOF, YOU PROBABLY DO NOT MEAN TO DO THIS". None of this is
// critical and we can wait to see how users manage to shoot themselves
// in the feet.
// `bin/auth` can recover from these types of mistakes.
$title = pht('Disable Provider?');
$body = pht(
'Do you want to disable this provider? Users will not be able to '.
'register or log in using linked accounts. If there are any users '.
'without other linked authentication mechanisms, they will no longer '.
'be able to log in. If you disable all providers, no one will be '.
'able to log in.');
$button = pht('Disable Provider');
}
return $this->newDialog()
->setTitle($title)
->appendChild($body)
->addCancelButton($done_uri)
->addSubmitButton($button);
}
}
diff --git a/src/applications/auth/controller/config/PhabricatorAuthEditController.php b/src/applications/auth/controller/config/PhabricatorAuthEditController.php
index f602c4fb24..693fc5bffd 100644
--- a/src/applications/auth/controller/config/PhabricatorAuthEditController.php
+++ b/src/applications/auth/controller/config/PhabricatorAuthEditController.php
@@ -1,398 +1,397 @@
<?php
final class PhabricatorAuthEditController
extends PhabricatorAuthProviderConfigController {
public function handleRequest(AphrontRequest $request) {
$this->requireApplicationCapability(
AuthManageProvidersCapability::CAPABILITY);
$viewer = $this->getViewer();
$provider_class = $request->getStr('provider');
$config_id = $request->getURIData('id');
if ($config_id) {
$config = id(new PhabricatorAuthProviderConfigQuery())
->setViewer($viewer)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->withIDs(array($config_id))
->executeOne();
if (!$config) {
return new Aphront404Response();
}
$provider = $config->getProvider();
if (!$provider) {
return new Aphront404Response();
}
$is_new = false;
} else {
$provider = null;
$providers = PhabricatorAuthProvider::getAllBaseProviders();
foreach ($providers as $candidate_provider) {
if (get_class($candidate_provider) === $provider_class) {
$provider = $candidate_provider;
break;
}
}
if (!$provider) {
return new Aphront404Response();
}
// TODO: When we have multi-auth providers, support them here.
$configs = id(new PhabricatorAuthProviderConfigQuery())
->setViewer($viewer)
->withProviderClasses(array(get_class($provider)))
->execute();
if ($configs) {
$id = head($configs)->getID();
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setMethod('GET')
->setSubmitURI($this->getApplicationURI('config/edit/'.$id.'/'))
->setTitle(pht('Provider Already Configured'))
->appendChild(
pht(
'This provider ("%s") already exists, and you can not add more '.
'than one instance of it. You can edit the existing provider, '.
'or you can choose a different provider.',
$provider->getProviderName()))
->addCancelButton($this->getApplicationURI('config/new/'))
->addSubmitButton(pht('Edit Existing Provider'));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
$config = $provider->getDefaultProviderConfig();
$provider->attachProviderConfig($config);
$is_new = true;
}
$errors = array();
$validation_exception = null;
$v_login = $config->getShouldAllowLogin();
$v_registration = $config->getShouldAllowRegistration();
$v_link = $config->getShouldAllowLink();
$v_unlink = $config->getShouldAllowUnlink();
$v_trust_email = $config->getShouldTrustEmails();
$v_auto_login = $config->getShouldAutoLogin();
if ($request->isFormPost()) {
$properties = $provider->readFormValuesFromRequest($request);
list($errors, $issues, $properties) = $provider->processEditForm(
$request,
$properties);
$xactions = array();
if (!$errors) {
if ($is_new) {
if (!strlen($config->getProviderType())) {
$config->setProviderType($provider->getProviderType());
}
if (!strlen($config->getProviderDomain())) {
$config->setProviderDomain($provider->getProviderDomain());
}
}
$xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
->setTransactionType(
PhabricatorAuthProviderConfigTransaction::TYPE_LOGIN)
->setNewValue($request->getInt('allowLogin', 0));
$xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
->setTransactionType(
PhabricatorAuthProviderConfigTransaction::TYPE_REGISTRATION)
->setNewValue($request->getInt('allowRegistration', 0));
$xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
->setTransactionType(
PhabricatorAuthProviderConfigTransaction::TYPE_LINK)
->setNewValue($request->getInt('allowLink', 0));
$xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
->setTransactionType(
PhabricatorAuthProviderConfigTransaction::TYPE_UNLINK)
->setNewValue($request->getInt('allowUnlink', 0));
$xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
->setTransactionType(
PhabricatorAuthProviderConfigTransaction::TYPE_TRUST_EMAILS)
->setNewValue($request->getInt('trustEmails', 0));
if ($provider->supportsAutoLogin()) {
$xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
->setTransactionType(
PhabricatorAuthProviderConfigTransaction::TYPE_AUTO_LOGIN)
->setNewValue($request->getInt('autoLogin', 0));
}
foreach ($properties as $key => $value) {
$xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
->setTransactionType(
PhabricatorAuthProviderConfigTransaction::TYPE_PROPERTY)
->setMetadataValue('auth:property', $key)
->setNewValue($value);
}
if ($is_new) {
$config->save();
}
$editor = id(new PhabricatorAuthProviderConfigEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true);
try {
$editor->applyTransactions($config, $xactions);
$next_uri = $config->getURI();
return id(new AphrontRedirectResponse())->setURI($next_uri);
} catch (Exception $ex) {
$validation_exception = $ex;
}
}
} else {
$properties = $provider->readFormValuesFromProvider();
$issues = array();
}
if ($is_new) {
if ($provider->hasSetupStep()) {
$button = pht('Next Step');
} else {
$button = pht('Add Provider');
}
$crumb = pht('Add Provider');
$title = pht('Add Auth Provider');
$header_icon = 'fa-plus-square';
$cancel_uri = $this->getApplicationURI('/config/new/');
} else {
$button = pht('Save');
$crumb = pht('Edit Provider');
$title = pht('Edit Auth Provider');
$header_icon = 'fa-pencil';
$cancel_uri = $config->getURI();
}
$header = id(new PHUIHeaderView())
->setHeader(pht('%s: %s', $title, $provider->getProviderName()))
->setHeaderIcon($header_icon);
if (!$is_new) {
if ($config->getIsEnabled()) {
$status_name = pht('Enabled');
$status_color = 'green';
$status_icon = 'fa-check';
$header->setStatus($status_icon, $status_color, $status_name);
} else {
$status_name = pht('Disabled');
$status_color = 'indigo';
$status_icon = 'fa-ban';
$header->setStatus($status_icon, $status_color, $status_name);
}
}
$config_name = 'auth.email-domains';
$config_href = '/config/edit/'.$config_name.'/';
$email_domains = PhabricatorEnv::getEnvConfig($config_name);
if ($email_domains) {
$registration_warning = pht(
'Users will only be able to register with a verified email address '.
'at one of the configured [[ %s | %s ]] domains: **%s**',
$config_href,
$config_name,
implode(', ', $email_domains));
} else {
$registration_warning = pht(
"NOTE: Any user who can browse to this install's login page will be ".
- "able to register a Phabricator account. To restrict who can register ".
+ "able to register an account. To restrict who can register ".
"an account, configure [[ %s | %s ]].",
$config_href,
$config_name);
}
$str_login = array(
phutil_tag('strong', array(), pht('Allow Login:')),
' ',
pht(
'Allow users to log in using this provider. If you disable login, '.
'users can still use account integrations for this provider.'),
);
$str_registration = array(
phutil_tag('strong', array(), pht('Allow Registration:')),
' ',
pht(
- 'Allow users to register new Phabricator accounts using this '.
- 'provider. If you disable registration, users can still use this '.
- 'provider to log in to existing accounts, but will not be able to '.
- 'create new accounts.'),
+ 'Allow users to register new accounts using this provider. If you '.
+ 'disable registration, users can still use this provider to log in '.
+ 'to existing accounts, but will not be able to create new accounts.'),
);
$str_link = hsprintf(
'<strong>%s:</strong> %s',
pht('Allow Linking Accounts'),
pht(
'Allow users to link account credentials for this provider to '.
- 'existing Phabricator accounts. There is normally no reason to '.
- 'disable this unless you are trying to move away from a provider '.
- 'and want to stop users from creating new account links.'));
+ 'existing accounts. There is normally no reason to disable this '.
+ 'unless you are trying to move away from a provider and want to '.
+ 'stop users from creating new account links.'));
$str_unlink = hsprintf(
'<strong>%s:</strong> %s',
pht('Allow Unlinking Accounts'),
pht(
'Allow users to unlink account credentials for this provider from '.
- 'existing Phabricator accounts. If you disable this, Phabricator '.
- 'accounts will be permanently bound to provider accounts.'));
+ 'existing accounts. If you disable this, accounts will be '.
+ 'permanently bound to provider accounts.'));
$str_trusted_email = hsprintf(
'<strong>%s:</strong> %s',
pht('Trust Email Addresses'),
pht(
- 'Phabricator will skip email verification for accounts registered '.
+ 'Skip email verification for accounts registered '.
'through this provider.'));
$str_auto_login = hsprintf(
'<strong>%s:</strong> %s',
pht('Allow Auto Login'),
pht(
- 'Phabricator will automatically login with this provider if it is '.
+ 'Automatically log in with this provider if it is '.
'the only available provider.'));
$form = id(new AphrontFormView())
->setUser($viewer)
->addHiddenInput('provider', $provider_class)
->appendChild(
id(new AphrontFormCheckboxControl())
->setLabel(pht('Allow'))
->addCheckbox(
'allowLogin',
1,
$str_login,
$v_login))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'allowRegistration',
1,
$str_registration,
$v_registration))
->appendRemarkupInstructions($registration_warning)
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'allowLink',
1,
$str_link,
$v_link))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'allowUnlink',
1,
$str_unlink,
$v_unlink));
if ($provider->shouldAllowEmailTrustConfiguration()) {
$form->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'trustEmails',
1,
$str_trusted_email,
$v_trust_email));
}
if ($provider->supportsAutoLogin()) {
$form->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'autoLogin',
1,
$str_auto_login,
$v_auto_login));
}
$provider->extendEditForm($request, $form, $properties, $issues);
$locked_config_key = 'auth.lock-config';
$is_locked = PhabricatorEnv::getEnvConfig($locked_config_key);
$locked_warning = null;
if ($is_locked && !$validation_exception) {
$message = pht(
'Authentication provider configuration is locked, and can not be '.
'changed without being unlocked. See the configuration setting %s '.
'for details.',
phutil_tag(
'a',
array(
'href' => '/config/edit/'.$locked_config_key,
),
$locked_config_key));
$locked_warning = id(new PHUIInfoView())
->setViewer($viewer)
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors(array($message));
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setDisabled($is_locked)
->setValue($button));
$help = $provider->getConfigurationHelp();
if ($help) {
$form->appendChild(id(new PHUIFormDividerControl()));
$form->appendRemarkupInstructions($help);
}
$footer = $provider->renderConfigurationFooter();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($crumb);
$crumbs->setBorder(true);
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Provider'))
->setFormErrors($errors)
->setValidationException($validation_exception)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($form);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$locked_warning,
$form_box,
$footer,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
}
diff --git a/src/applications/auth/controller/config/PhabricatorAuthListController.php b/src/applications/auth/controller/config/PhabricatorAuthListController.php
index b25c791e27..690aa4e57c 100644
--- a/src/applications/auth/controller/config/PhabricatorAuthListController.php
+++ b/src/applications/auth/controller/config/PhabricatorAuthListController.php
@@ -1,122 +1,121 @@
<?php
final class PhabricatorAuthListController
extends PhabricatorAuthProviderConfigController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$configs = id(new PhabricatorAuthProviderConfigQuery())
->setViewer($viewer)
->execute();
$list = new PHUIObjectItemListView();
$can_manage = $this->hasApplicationCapability(
AuthManageProvidersCapability::CAPABILITY);
$is_locked = PhabricatorEnv::getEnvConfig('auth.lock-config');
foreach ($configs as $config) {
$item = new PHUIObjectItemView();
$id = $config->getID();
$view_uri = $config->getURI();
$provider = $config->getProvider();
$name = $provider->getProviderName();
$item
->setHeader($name)
->setHref($view_uri);
$domain = $provider->getProviderDomain();
if ($domain !== 'self') {
$item->addAttribute($domain);
}
if ($config->getShouldAllowRegistration()) {
$item->addAttribute(pht('Allows Registration'));
} else {
$item->addAttribute(pht('Does Not Allow Registration'));
}
if ($config->getIsEnabled()) {
$item->setStatusIcon('fa-check-circle green');
} else {
$item->setStatusIcon('fa-ban red');
$item->addIcon('fa-ban grey', pht('Disabled'));
}
$list->addItem($item);
}
$list->setNoDataString(
pht(
'%s You have not added authentication providers yet. Use "%s" to add '.
- 'a provider, which will let users register new Phabricator accounts '.
- 'and log in.',
+ 'a provider, which will let users register new accounts and log in.',
phutil_tag(
'strong',
array(),
pht('No Providers Configured:')),
phutil_tag(
'a',
array(
'href' => $this->getApplicationURI('config/new/'),
),
pht('Add Provider'))));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Login and Registration'));
$crumbs->setBorder(true);
$guidance_context = id(new PhabricatorAuthProvidersGuidanceContext())
->setCanManage($can_manage);
$guidance = id(new PhabricatorGuidanceEngine())
->setViewer($viewer)
->setGuidanceContext($guidance_context)
->newInfoView();
$is_disabled = (!$can_manage || $is_locked);
$button = id(new PHUIButtonView())
->setTag('a')
->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE)
->setIcon('fa-plus')
->setDisabled($is_disabled)
->setWorkflow($is_disabled)
->setHref($this->getApplicationURI('config/new/'))
->setText(pht('Add Provider'));
$list->setFlush(true);
$list = id(new PHUIObjectBoxView())
->setHeaderText(pht('Providers'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($list);
$title = pht('Login and Registration Providers');
$header = id(new PHUIHeaderView())
->setHeader($title)
->setHeaderIcon('fa-key')
->addActionLink($button);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(
array(
$guidance,
$list,
));
$nav = $this->newNavigation()
->setCrumbs($crumbs)
->appendChild($view);
$nav->selectFilter('login');
return $this->newPage()
->setTitle($title)
->appendChild($nav);
}
}
diff --git a/src/applications/auth/controller/contact/PhabricatorAuthContactNumberTestController.php b/src/applications/auth/controller/contact/PhabricatorAuthContactNumberTestController.php
index 2c25fa3f4a..f8c8b013bf 100644
--- a/src/applications/auth/controller/contact/PhabricatorAuthContactNumberTestController.php
+++ b/src/applications/auth/controller/contact/PhabricatorAuthContactNumberTestController.php
@@ -1,64 +1,64 @@
<?php
final class PhabricatorAuthContactNumberTestController
extends PhabricatorAuthContactNumberController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$number = id(new PhabricatorAuthContactNumberQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$number) {
return new Aphront404Response();
}
$id = $number->getID();
$cancel_uri = $number->getURI();
// NOTE: This is a global limit shared by all users.
PhabricatorSystemActionEngine::willTakeAction(
array(id(new PhabricatorAuthApplication())->getPHID()),
new PhabricatorAuthTestSMSAction(),
1);
if ($request->isFormPost()) {
$uri = PhabricatorEnv::getURI('/');
$uri = new PhutilURI($uri);
$mail = id(new PhabricatorMetaMTAMail())
->setMessageType(PhabricatorMailSMSMessage::MESSAGETYPE)
->addTos(array($viewer->getPHID()))
->setSensitiveContent(false)
->setBody(
pht(
- 'This is a terse test text message from Phabricator (%s).',
+ 'This is a terse test text message (from "%s").',
$uri->getDomain()))
->save();
return id(new AphrontRedirectResponse())->setURI($mail->getURI());
}
$number_display = phutil_tag(
'strong',
array(),
$number->getDisplayName());
return $this->newDialog()
->setTitle(pht('Set Test Message'))
->appendParagraph(
pht(
'Send a test message to %s?',
$number_display))
->addSubmitButton(pht('Send SMS'))
->addCancelButton($cancel_uri);
}
}
diff --git a/src/applications/auth/factor/PhabricatorDuoAuthFactor.php b/src/applications/auth/factor/PhabricatorDuoAuthFactor.php
index a84337a764..65f0aa5e4b 100644
--- a/src/applications/auth/factor/PhabricatorDuoAuthFactor.php
+++ b/src/applications/auth/factor/PhabricatorDuoAuthFactor.php
@@ -1,867 +1,869 @@
<?php
final class PhabricatorDuoAuthFactor
extends PhabricatorAuthFactor {
const PROP_CREDENTIAL = 'duo.credentialPHID';
const PROP_ENROLL = 'duo.enroll';
const PROP_USERNAMES = 'duo.usernames';
const PROP_HOSTNAME = 'duo.hostname';
public function getFactorKey() {
return 'duo';
}
public function getFactorName() {
return pht('Duo Security');
}
public function getFactorShortName() {
return pht('Duo');
}
public function getFactorCreateHelp() {
return pht('Support for Duo push authentication.');
}
public function getFactorDescription() {
return pht(
'When you need to authenticate, a request will be pushed to the '.
'Duo application on your phone.');
}
public function getEnrollDescription(
PhabricatorAuthFactorProvider $provider,
PhabricatorUser $user) {
return pht(
'To add a Duo factor, first download and install the Duo application '.
'on your phone. Once you have launched the application and are ready '.
'to perform setup, click continue.');
}
public function canCreateNewConfiguration(
PhabricatorAuthFactorProvider $provider,
PhabricatorUser $user) {
if ($this->loadConfigurationsForProvider($provider, $user)) {
return false;
}
return true;
}
public function getConfigurationCreateDescription(
PhabricatorAuthFactorProvider $provider,
PhabricatorUser $user) {
$messages = array();
if ($this->loadConfigurationsForProvider($provider, $user)) {
$messages[] = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors(
array(
pht(
'You already have Duo authentication attached to your account '.
'for this provider.'),
));
}
return $messages;
}
public function getConfigurationListDetails(
PhabricatorAuthFactorConfig $config,
PhabricatorAuthFactorProvider $provider,
PhabricatorUser $viewer) {
$duo_user = $config->getAuthFactorConfigProperty('duo.username');
return pht('Duo Username: %s', $duo_user);
}
public function newEditEngineFields(
PhabricatorEditEngine $engine,
PhabricatorAuthFactorProvider $provider) {
$viewer = $engine->getViewer();
$credential_phid = $provider->getAuthFactorProviderProperty(
self::PROP_CREDENTIAL);
$hostname = $provider->getAuthFactorProviderProperty(self::PROP_HOSTNAME);
$usernames = $provider->getAuthFactorProviderProperty(self::PROP_USERNAMES);
$enroll = $provider->getAuthFactorProviderProperty(self::PROP_ENROLL);
$credential_type = PassphrasePasswordCredentialType::CREDENTIAL_TYPE;
$provides_type = PassphrasePasswordCredentialType::PROVIDES_TYPE;
$credentials = id(new PassphraseCredentialQuery())
->setViewer($viewer)
->withIsDestroyed(false)
->withProvidesTypes(array($provides_type))
->execute();
$xaction_hostname =
PhabricatorAuthFactorProviderDuoHostnameTransaction::TRANSACTIONTYPE;
$xaction_credential =
PhabricatorAuthFactorProviderDuoCredentialTransaction::TRANSACTIONTYPE;
$xaction_usernames =
PhabricatorAuthFactorProviderDuoUsernamesTransaction::TRANSACTIONTYPE;
$xaction_enroll =
PhabricatorAuthFactorProviderDuoEnrollTransaction::TRANSACTIONTYPE;
return array(
id(new PhabricatorTextEditField())
->setLabel(pht('Duo API Hostname'))
->setKey('duo.hostname')
->setValue($hostname)
->setTransactionType($xaction_hostname)
->setIsRequired(true),
id(new PhabricatorCredentialEditField())
->setLabel(pht('Duo API Credential'))
->setKey('duo.credential')
->setValue($credential_phid)
->setTransactionType($xaction_credential)
->setCredentialType($credential_type)
->setCredentials($credentials),
id(new PhabricatorSelectEditField())
->setLabel(pht('Duo Username'))
->setKey('duo.usernames')
->setValue($usernames)
->setTransactionType($xaction_usernames)
->setOptions(
array(
- 'username' => pht('Use Phabricator Username'),
+ 'username' => pht(
+ 'Use %s Username',
+ PlatformSymbols::getPlatformServerName()),
'email' => pht('Use Primary Email Address'),
)),
id(new PhabricatorSelectEditField())
->setLabel(pht('Create Accounts'))
->setKey('duo.enroll')
->setValue($enroll)
->setTransactionType($xaction_enroll)
->setOptions(
array(
'deny' => pht('Require Existing Duo Account'),
'allow' => pht('Create New Duo Account'),
)),
);
}
public function processAddFactorForm(
PhabricatorAuthFactorProvider $provider,
AphrontFormView $form,
AphrontRequest $request,
PhabricatorUser $user) {
$token = $this->loadMFASyncToken($provider, $request, $form, $user);
if ($this->isAuthResult($token)) {
$form->appendChild($this->newAutomaticControl($token));
return;
}
$enroll = $token->getTemporaryTokenProperty('duo.enroll');
$duo_id = $token->getTemporaryTokenProperty('duo.user-id');
$duo_uri = $token->getTemporaryTokenProperty('duo.uri');
$duo_user = $token->getTemporaryTokenProperty('duo.username');
$is_external = ($enroll === 'external');
$is_auto = ($enroll === 'auto');
$is_blocked = ($enroll === 'blocked');
if (!$token->getIsNewTemporaryToken()) {
if ($is_auto) {
return $this->newDuoConfig($user, $duo_user);
} else if ($is_external || $is_blocked) {
$parameters = array(
'username' => $duo_user,
);
$result = $this->newDuoFuture($provider)
->setMethod('preauth', $parameters)
->resolve();
$result_code = $result['response']['result'];
switch ($result_code) {
case 'auth':
case 'allow':
return $this->newDuoConfig($user, $duo_user);
case 'enroll':
if ($is_blocked) {
// We'll render an equivalent static control below, so skip
// rendering here. We explicitly don't want to give the user
// an enroll workflow.
break;
}
$duo_uri = $result['response']['enroll_portal_url'];
$waiting_icon = id(new PHUIIconView())
->setIcon('fa-mobile', 'red');
$waiting_control = id(new PHUIFormTimerControl())
->setIcon($waiting_icon)
->setError(pht('Not Complete'))
->appendChild(
pht(
'You have not completed Duo enrollment yet. '.
'Complete enrollment, then click continue.'));
$form->appendControl($waiting_control);
break;
default:
case 'deny':
break;
}
} else {
$parameters = array(
'user_id' => $duo_id,
'activation_code' => $duo_uri,
);
$future = $this->newDuoFuture($provider)
->setMethod('enroll_status', $parameters);
$result = $future->resolve();
$response = $result['response'];
switch ($response) {
case 'success':
return $this->newDuoConfig($user, $duo_user);
case 'waiting':
$waiting_icon = id(new PHUIIconView())
->setIcon('fa-mobile', 'red');
$waiting_control = id(new PHUIFormTimerControl())
->setIcon($waiting_icon)
->setError(pht('Not Complete'))
->appendChild(
pht(
'You have not activated this enrollment in the Duo '.
'application on your phone yet. Complete activation, then '.
'click continue.'));
$form->appendControl($waiting_control);
break;
case 'invalid':
default:
throw new Exception(
pht(
'This Duo enrollment attempt is invalid or has '.
'expired ("%s"). Cancel the workflow and try again.',
$response));
}
}
}
if ($is_blocked) {
$blocked_icon = id(new PHUIIconView())
->setIcon('fa-times', 'red');
$blocked_control = id(new PHUIFormTimerControl())
->setIcon($blocked_icon)
->appendChild(
pht(
'Your Duo account ("%s") has not completed Duo enrollment. '.
'Check your email and complete enrollment to continue.',
phutil_tag('strong', array(), $duo_user)));
$form->appendControl($blocked_control);
} else if ($is_auto) {
$auto_icon = id(new PHUIIconView())
->setIcon('fa-check', 'green');
$auto_control = id(new PHUIFormTimerControl())
->setIcon($auto_icon)
->appendChild(
pht(
'Duo account ("%s") is fully enrolled.',
phutil_tag('strong', array(), $duo_user)));
$form->appendControl($auto_control);
} else {
$duo_button = phutil_tag(
'a',
array(
'href' => $duo_uri,
'class' => 'button button-grey',
'target' => ($is_external ? '_blank' : null),
),
pht('Enroll Duo Account: %s', $duo_user));
$duo_button = phutil_tag(
'div',
array(
'class' => 'mfa-form-enroll-button',
),
$duo_button);
if ($is_external) {
$form->appendRemarkupInstructions(
pht(
'Complete enrolling your phone with Duo:'));
$form->appendControl(
id(new AphrontFormMarkupControl())
->setValue($duo_button));
} else {
$form->appendRemarkupInstructions(
pht(
'Scan this QR code with the Duo application on your mobile '.
'phone:'));
$qr_code = $this->newQRCode($duo_uri);
$form->appendChild($qr_code);
$form->appendRemarkupInstructions(
pht(
'If you are currently using your phone to view this page, '.
'click this button to open the Duo application:'));
$form->appendControl(
id(new AphrontFormMarkupControl())
->setValue($duo_button));
}
$form->appendRemarkupInstructions(
pht(
'Once you have completed setup on your phone, click continue.'));
}
}
protected function newMFASyncTokenProperties(
PhabricatorAuthFactorProvider $provider,
PhabricatorUser $user) {
$duo_user = $this->getDuoUsername($provider, $user);
// Duo automatically normalizes usernames to lowercase. Just do that here
// so that our value agrees more closely with Duo.
$duo_user = phutil_utf8_strtolower($duo_user);
$parameters = array(
'username' => $duo_user,
);
$result = $this->newDuoFuture($provider)
->setMethod('preauth', $parameters)
->resolve();
$external_uri = null;
$result_code = $result['response']['result'];
$status_message = $result['response']['status_msg'];
switch ($result_code) {
case 'auth':
case 'allow':
// If the user already has a Duo account, they don't need to do
// anything.
return array(
'duo.enroll' => 'auto',
'duo.username' => $duo_user,
);
case 'enroll':
if (!$this->shouldAllowDuoEnrollment($provider)) {
return array(
'duo.enroll' => 'blocked',
'duo.username' => $duo_user,
);
}
$external_uri = $result['response']['enroll_portal_url'];
// Otherwise, enrollment is permitted so we're going to continue.
break;
default:
case 'deny':
return $this->newResult()
->setIsError(true)
->setErrorMessage(
pht(
'Your Duo account ("%s") is not permitted to access this '.
'system. Contact your Duo administrator for help. '.
'The Duo preauth API responded with status message ("%s"): %s',
$duo_user,
$result_code,
$status_message));
}
// Duo's "/enroll" API isn't repeatable for the same username. If we're
// the first call, great: we can do inline enrollment, which is way more
// user friendly. Otherwise, we have to send the user on an adventure.
$parameters = array(
'username' => $duo_user,
'valid_secs' => phutil_units('1 hour in seconds'),
);
try {
$result = $this->newDuoFuture($provider)
->setMethod('enroll', $parameters)
->resolve();
} catch (HTTPFutureHTTPResponseStatus $ex) {
return array(
'duo.enroll' => 'external',
'duo.username' => $duo_user,
'duo.uri' => $external_uri,
);
}
return array(
'duo.enroll' => 'inline',
'duo.uri' => $result['response']['activation_code'],
'duo.username' => $duo_user,
'duo.user-id' => $result['response']['user_id'],
);
}
protected function newIssuedChallenges(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
array $challenges) {
// If we already issued a valid challenge for this workflow and session,
// don't issue a new one.
$challenge = $this->getChallengeForCurrentContext(
$config,
$viewer,
$challenges);
if ($challenge) {
return array();
}
if (!$this->hasCSRF($config)) {
return $this->newResult()
->setIsContinue(true)
->setErrorMessage(
pht(
'An authorization request will be pushed to the Duo '.
'application on your phone.'));
}
$provider = $config->getFactorProvider();
// Otherwise, issue a new challenge.
$duo_user = (string)$config->getAuthFactorConfigProperty('duo.username');
$parameters = array(
'username' => $duo_user,
);
$response = $this->newDuoFuture($provider)
->setMethod('preauth', $parameters)
->resolve();
$response = $response['response'];
$next_step = $response['result'];
$status_message = $response['status_msg'];
switch ($next_step) {
case 'auth':
// We're good to go.
break;
case 'allow':
// Duo is telling us to bypass MFA. For now, refuse.
return $this->newResult()
->setIsError(true)
->setErrorMessage(
pht(
'Duo is not requiring a challenge, which defeats the '.
'purpose of MFA. Duo must be configured to challenge you.'));
case 'enroll':
return $this->newResult()
->setIsError(true)
->setErrorMessage(
pht(
'Your Duo account ("%s") requires enrollment. Contact your '.
'Duo administrator for help. Duo status message: %s',
$duo_user,
$status_message));
case 'deny':
default:
return $this->newResult()
->setIsError(true)
->setErrorMessage(
pht(
'Your Duo account ("%s") is not permitted to access this '.
'system. Contact your Duo administrator for help. The Duo '.
'preauth API responded with status message ("%s"): %s',
$duo_user,
$next_step,
$status_message));
}
$has_push = false;
$devices = $response['devices'];
foreach ($devices as $device) {
$capabilities = array_fuse($device['capabilities']);
if (isset($capabilities['push'])) {
$has_push = true;
break;
}
}
if (!$has_push) {
return $this->newResult()
->setIsError(true)
->setErrorMessage(
pht(
- 'This factor has been removed from your device, so Phabricator '.
+ 'This factor has been removed from your device, so this server '.
'can not send you a challenge. To continue, an administrator '.
'must strip this factor from your account.'));
}
$push_info = array(
pht('Domain') => $this->getInstallDisplayName(),
);
$push_info = phutil_build_http_querystring($push_info);
$parameters = array(
'username' => $duo_user,
'factor' => 'push',
'async' => '1',
// Duo allows us to specify a device, or to pass "auto" to have it pick
// the first one. For now, just let it pick.
'device' => 'auto',
// This is a hard-coded prefix for the word "... request" in the Duo UI,
// which defaults to "Login". We could pass richer information from
// workflows here, but it's not very flexible anyway.
'type' => 'Authentication',
'display_username' => $viewer->getUsername(),
'pushinfo' => $push_info,
);
$result = $this->newDuoFuture($provider)
->setMethod('auth', $parameters)
->resolve();
$duo_xaction = $result['response']['txid'];
// The Duo push timeout is 60 seconds. Set our challenge to expire slightly
// more quickly so that we'll re-issue a new challenge before Duo times out.
// This should keep users away from a dead-end where they can't respond to
- // Duo but Phabricator won't issue a new challenge yet.
+ // Duo but we won't issue a new challenge yet.
$ttl_seconds = 55;
return array(
$this->newChallenge($config, $viewer)
->setChallengeKey($duo_xaction)
->setChallengeTTL(PhabricatorTime::getNow() + $ttl_seconds),
);
}
protected function newResultFromIssuedChallenges(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
array $challenges) {
$challenge = $this->getChallengeForCurrentContext(
$config,
$viewer,
$challenges);
if ($challenge->getIsAnsweredChallenge()) {
return $this->newResult()
->setAnsweredChallenge($challenge);
}
$provider = $config->getFactorProvider();
$duo_xaction = $challenge->getChallengeKey();
$parameters = array(
'txid' => $duo_xaction,
);
// This endpoint always long-polls, so use a timeout to force it to act
// more asynchronously.
try {
$result = $this->newDuoFuture($provider)
->setHTTPMethod('GET')
->setMethod('auth_status', $parameters)
->setTimeout(3)
->resolve();
$state = $result['response']['result'];
$status = $result['response']['status'];
} catch (HTTPFutureCURLResponseStatus $exception) {
if ($exception->isTimeout()) {
$state = 'waiting';
$status = 'poll';
} else {
throw $exception;
}
}
$now = PhabricatorTime::getNow();
switch ($state) {
case 'allow':
$ttl = PhabricatorTime::getNow()
+ phutil_units('15 minutes in seconds');
$challenge
->markChallengeAsAnswered($ttl);
return $this->newResult()
->setAnsweredChallenge($challenge);
case 'waiting':
// If we didn't just issue this challenge, give the user a stronger
// hint that they need to follow the instructions.
if (!$challenge->getIsNewChallenge()) {
return $this->newResult()
->setIsContinue(true)
->setIcon(
id(new PHUIIconView())
->setIcon('fa-exclamation-triangle', 'yellow'))
->setErrorMessage(
pht(
'You must approve the challenge which was sent to your '.
'phone. Open the Duo application and confirm the challenge, '.
'then continue.'));
}
// Otherwise, we'll construct a default message later on.
break;
default:
case 'deny':
if ($status === 'timeout') {
return $this->newResult()
->setIsError(true)
->setErrorMessage(
pht(
'This request has timed out because you took too long to '.
'respond.'));
} else {
$wait_duration = ($challenge->getChallengeTTL() - $now) + 1;
return $this->newResult()
->setIsWait(true)
->setErrorMessage(
pht(
'You denied this request. Wait %s second(s) to try again.',
new PhutilNumber($wait_duration)));
}
break;
}
return null;
}
public function renderValidateFactorForm(
PhabricatorAuthFactorConfig $config,
AphrontFormView $form,
PhabricatorUser $viewer,
PhabricatorAuthFactorResult $result) {
$control = $this->newAutomaticControl($result);
$control
->setLabel(pht('Duo'))
->setCaption(pht('Factor Name: %s', $config->getFactorName()));
$form->appendChild($control);
}
public function getRequestHasChallengeResponse(
PhabricatorAuthFactorConfig $config,
AphrontRequest $request) {
return false;
}
protected function newResultFromChallengeResponse(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
AphrontRequest $request,
array $challenges) {
return $this->getResultForPrompt(
$config,
$viewer,
$request,
$challenges);
}
protected function newResultForPrompt(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
AphrontRequest $request,
array $challenges) {
$result = $this->newResult()
->setIsContinue(true)
->setErrorMessage(
pht(
'A challenge has been sent to your phone. Open the Duo '.
'application and confirm the challenge, then continue.'));
$challenge = $this->getChallengeForCurrentContext(
$config,
$viewer,
$challenges);
if ($challenge) {
$result
->setStatusChallenge($challenge)
->setIcon(
id(new PHUIIconView())
->setIcon('fa-refresh', 'green ph-spin'));
}
return $result;
}
private function newDuoFuture(PhabricatorAuthFactorProvider $provider) {
$credential_phid = $provider->getAuthFactorProviderProperty(
self::PROP_CREDENTIAL);
$omnipotent = PhabricatorUser::getOmnipotentUser();
$credential = id(new PassphraseCredentialQuery())
->setViewer($omnipotent)
->withPHIDs(array($credential_phid))
->needSecrets(true)
->executeOne();
if (!$credential) {
throw new Exception(
pht(
'Unable to load Duo API credential ("%s").',
$credential_phid));
}
$duo_key = $credential->getUsername();
$duo_secret = $credential->getSecret();
if (!$duo_secret) {
throw new Exception(
pht(
'Duo API credential ("%s") has no secret key.',
$credential_phid));
}
$duo_host = $provider->getAuthFactorProviderProperty(
self::PROP_HOSTNAME);
self::requireDuoAPIHostname($duo_host);
return id(new PhabricatorDuoFuture())
->setIntegrationKey($duo_key)
->setSecretKey($duo_secret)
->setAPIHostname($duo_host)
->setTimeout(10)
->setHTTPMethod('POST');
}
private function getDuoUsername(
PhabricatorAuthFactorProvider $provider,
PhabricatorUser $user) {
$mode = $provider->getAuthFactorProviderProperty(self::PROP_USERNAMES);
switch ($mode) {
case 'username':
return $user->getUsername();
case 'email':
return $user->loadPrimaryEmailAddress();
default:
throw new Exception(
pht(
'Duo username pairing mode ("%s") is not supported.',
$mode));
}
}
private function shouldAllowDuoEnrollment(
PhabricatorAuthFactorProvider $provider) {
$mode = $provider->getAuthFactorProviderProperty(self::PROP_ENROLL);
switch ($mode) {
case 'deny':
return false;
case 'allow':
return true;
default:
throw new Exception(
pht(
'Duo enrollment mode ("%s") is not supported.',
$mode));
}
}
private function newDuoConfig(PhabricatorUser $user, $duo_user) {
$config_properties = array(
'duo.username' => $duo_user,
);
$config = $this->newConfigForUser($user)
->setFactorName(pht('Duo (%s)', $duo_user))
->setProperties($config_properties);
return $config;
}
public static function requireDuoAPIHostname($hostname) {
if (preg_match('/\.duosecurity\.com\z/', $hostname)) {
return;
}
throw new Exception(
pht(
'Duo API hostname ("%s") is invalid, hostname must be '.
'"*.duosecurity.com".',
$hostname));
}
public function newChallengeStatusView(
PhabricatorAuthFactorConfig $config,
PhabricatorAuthFactorProvider $provider,
PhabricatorUser $viewer,
PhabricatorAuthChallenge $challenge) {
$duo_xaction = $challenge->getChallengeKey();
$parameters = array(
'txid' => $duo_xaction,
);
$default_result = id(new PhabricatorAuthChallengeUpdate())
->setRetry(true);
try {
$result = $this->newDuoFuture($provider)
->setHTTPMethod('GET')
->setMethod('auth_status', $parameters)
->setTimeout(5)
->resolve();
$state = $result['response']['result'];
} catch (HTTPFutureCURLResponseStatus $exception) {
// If we failed or timed out, retry. Usually, this is a timeout.
return id(new PhabricatorAuthChallengeUpdate())
->setRetry(true);
}
// For now, don't update the view for anything but an "Allow". Updates
// here are just about providing more visual feedback for user convenience.
if ($state !== 'allow') {
return id(new PhabricatorAuthChallengeUpdate())
->setRetry(false);
}
$icon = id(new PHUIIconView())
->setIcon('fa-check-circle-o', 'green');
$view = id(new PHUIFormTimerControl())
->setIcon($icon)
->appendChild(pht('You responded to this challenge correctly.'))
->newTimerView();
return id(new PhabricatorAuthChallengeUpdate())
->setState('allow')
->setRetry(false)
->setMarkup($view);
}
}
diff --git a/src/applications/auth/factor/PhabricatorSMSAuthFactor.php b/src/applications/auth/factor/PhabricatorSMSAuthFactor.php
index ba46de980e..33f640e692 100644
--- a/src/applications/auth/factor/PhabricatorSMSAuthFactor.php
+++ b/src/applications/auth/factor/PhabricatorSMSAuthFactor.php
@@ -1,401 +1,402 @@
<?php
final class PhabricatorSMSAuthFactor
extends PhabricatorAuthFactor {
public function getFactorKey() {
return 'sms';
}
public function getFactorName() {
return pht('Text Message (SMS)');
}
public function getFactorShortName() {
return pht('SMS');
}
public function getFactorCreateHelp() {
return pht(
'Allow users to receive a code via SMS.');
}
public function getFactorDescription() {
return pht(
'When you need to authenticate, a text message with a code will '.
'be sent to your phone.');
}
public function getFactorOrder() {
// Sort this factor toward the end of the list because SMS is relatively
// weak.
return 2000;
}
public function isContactNumberFactor() {
return true;
}
public function canCreateNewProvider() {
return $this->isSMSMailerConfigured();
}
public function getProviderCreateDescription() {
$messages = array();
if (!$this->isSMSMailerConfigured()) {
$messages[] = id(new PHUIInfoView())
->setErrors(
array(
pht(
'You have not configured an outbound SMS mailer. You must '.
'configure one before you can set up SMS. See: %s',
phutil_tag(
'a',
array(
'href' => '/config/edit/cluster.mailers/',
),
'cluster.mailers')),
));
}
$messages[] = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors(
array(
pht(
'SMS is weak, and relatively easy for attackers to compromise. '.
'Strongly consider using a different MFA provider.'),
));
return $messages;
}
public function canCreateNewConfiguration(
PhabricatorAuthFactorProvider $provider,
PhabricatorUser $user) {
if (!$this->loadUserContactNumber($user)) {
return false;
}
if ($this->loadConfigurationsForProvider($provider, $user)) {
return false;
}
return true;
}
public function getConfigurationCreateDescription(
PhabricatorAuthFactorProvider $provider,
PhabricatorUser $user) {
$messages = array();
if (!$this->loadUserContactNumber($user)) {
$messages[] = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors(
array(
pht(
'You have not configured a primary contact number. Configure '.
'a contact number before adding SMS as an authentication '.
'factor.'),
));
}
if ($this->loadConfigurationsForProvider($provider, $user)) {
$messages[] = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors(
array(
pht(
'You already have SMS authentication attached to your account.'),
));
}
return $messages;
}
public function getEnrollDescription(
PhabricatorAuthFactorProvider $provider,
PhabricatorUser $user) {
return pht(
'To verify your phone as an authentication factor, a text message with '.
'a secret code will be sent to the phone number you have listed as '.
'your primary contact number.');
}
public function getEnrollButtonText(
PhabricatorAuthFactorProvider $provider,
PhabricatorUser $user) {
$contact_number = $this->loadUserContactNumber($user);
return pht('Send SMS: %s', $contact_number->getDisplayName());
}
public function processAddFactorForm(
PhabricatorAuthFactorProvider $provider,
AphrontFormView $form,
AphrontRequest $request,
PhabricatorUser $user) {
$token = $this->loadMFASyncToken($provider, $request, $form, $user);
$code = $request->getStr('sms.code');
$e_code = true;
if (!$token->getIsNewTemporaryToken()) {
$expect_code = $token->getTemporaryTokenProperty('code');
$okay = phutil_hashes_are_identical(
$this->normalizeSMSCode($code),
$this->normalizeSMSCode($expect_code));
if ($okay) {
$config = $this->newConfigForUser($user)
->setFactorName(pht('SMS'));
return $config;
} else {
if (!strlen($code)) {
$e_code = pht('Required');
} else {
$e_code = pht('Invalid');
}
}
}
$form->appendRemarkupInstructions(
pht(
'Enter the code from the text message which was sent to your '.
'primary contact number.'));
$form->appendChild(
id(new PHUIFormNumberControl())
->setLabel(pht('SMS Code'))
->setName('sms.code')
->setValue($code)
->setError($e_code));
}
protected function newIssuedChallenges(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
array $challenges) {
// If we already issued a valid challenge for this workflow and session,
// don't issue a new one.
$challenge = $this->getChallengeForCurrentContext(
$config,
$viewer,
$challenges);
if ($challenge) {
return array();
}
if (!$this->loadUserContactNumber($viewer)) {
return $this->newResult()
->setIsError(true)
->setErrorMessage(
pht(
'Your account has no primary contact number.'));
}
if (!$this->isSMSMailerConfigured()) {
return $this->newResult()
->setIsError(true)
->setErrorMessage(
pht(
'No outbound mailer which can deliver SMS messages is '.
'configured.'));
}
if (!$this->hasCSRF($config)) {
return $this->newResult()
->setIsContinue(true)
->setErrorMessage(
pht(
'A text message with an authorization code will be sent to your '.
'primary contact number.'));
}
// Otherwise, issue a new challenge.
$challenge_code = $this->newSMSChallengeCode();
$envelope = new PhutilOpaqueEnvelope($challenge_code);
$this->sendSMSCodeToUser($envelope, $viewer);
$ttl_seconds = phutil_units('15 minutes in seconds');
return array(
$this->newChallenge($config, $viewer)
->setChallengeKey($challenge_code)
->setChallengeTTL(PhabricatorTime::getNow() + $ttl_seconds),
);
}
protected function newResultFromIssuedChallenges(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
array $challenges) {
$challenge = $this->getChallengeForCurrentContext(
$config,
$viewer,
$challenges);
if ($challenge->getIsAnsweredChallenge()) {
return $this->newResult()
->setAnsweredChallenge($challenge);
}
return null;
}
public function renderValidateFactorForm(
PhabricatorAuthFactorConfig $config,
AphrontFormView $form,
PhabricatorUser $viewer,
PhabricatorAuthFactorResult $result) {
$control = $this->newAutomaticControl($result);
if (!$control) {
$value = $result->getValue();
$error = $result->getErrorMessage();
$name = $this->getChallengeResponseParameterName($config);
$control = id(new PHUIFormNumberControl())
->setName($name)
->setDisableAutocomplete(true)
->setValue($value)
->setError($error);
}
$control
->setLabel(pht('SMS Code'))
->setCaption(pht('Factor Name: %s', $config->getFactorName()));
$form->appendChild($control);
}
public function getRequestHasChallengeResponse(
PhabricatorAuthFactorConfig $config,
AphrontRequest $request) {
$value = $this->getChallengeResponseFromRequest($config, $request);
return (bool)strlen($value);
}
protected function newResultFromChallengeResponse(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
AphrontRequest $request,
array $challenges) {
$challenge = $this->getChallengeForCurrentContext(
$config,
$viewer,
$challenges);
$code = $this->getChallengeResponseFromRequest(
$config,
$request);
$result = $this->newResult()
->setValue($code);
if ($challenge->getIsAnsweredChallenge()) {
return $result->setAnsweredChallenge($challenge);
}
if (phutil_hashes_are_identical($code, $challenge->getChallengeKey())) {
$ttl = PhabricatorTime::getNow() + phutil_units('15 minutes in seconds');
$challenge
->markChallengeAsAnswered($ttl);
return $result->setAnsweredChallenge($challenge);
}
if (strlen($code)) {
$error_message = pht('Invalid');
} else {
$error_message = pht('Required');
}
$result->setErrorMessage($error_message);
return $result;
}
private function newSMSChallengeCode() {
$value = Filesystem::readRandomInteger(0, 99999999);
$value = sprintf('%08d', $value);
return $value;
}
private function isSMSMailerConfigured() {
$mailers = PhabricatorMetaMTAMail::newMailers(
array(
'outbound' => true,
'media' => array(
PhabricatorMailSMSMessage::MESSAGETYPE,
),
));
return (bool)$mailers;
}
private function loadUserContactNumber(PhabricatorUser $user) {
$contact_numbers = id(new PhabricatorAuthContactNumberQuery())
->setViewer($user)
->withObjectPHIDs(array($user->getPHID()))
->withStatuses(
array(
PhabricatorAuthContactNumber::STATUS_ACTIVE,
))
->withIsPrimary(true)
->execute();
if (count($contact_numbers) !== 1) {
return null;
}
return head($contact_numbers);
}
protected function newMFASyncTokenProperties(
PhabricatorAuthFactorProvider $providerr,
PhabricatorUser $user) {
$sms_code = $this->newSMSChallengeCode();
$envelope = new PhutilOpaqueEnvelope($sms_code);
$this->sendSMSCodeToUser($envelope, $user);
return array(
'code' => $sms_code,
);
}
private function sendSMSCodeToUser(
PhutilOpaqueEnvelope $envelope,
PhabricatorUser $user) {
return id(new PhabricatorMetaMTAMail())
->setMessageType(PhabricatorMailSMSMessage::MESSAGETYPE)
->addTos(array($user->getPHID()))
->setForceDelivery(true)
->setSensitiveContent(true)
->setBody(
pht(
- 'Phabricator (%s) MFA Code: %s',
+ '%s (%s) MFA Code: %s',
+ PlatformSymbols::getPlatformServerName(),
$this->getInstallDisplayName(),
$envelope->openEnvelope()))
->save();
}
private function normalizeSMSCode($code) {
return trim($code);
}
}
diff --git a/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceEngineExtension.php b/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceEngineExtension.php
index 32482a582a..d4d41f1d83 100644
--- a/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceEngineExtension.php
+++ b/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceEngineExtension.php
@@ -1,127 +1,127 @@
<?php
final class PhabricatorAuthProvidersGuidanceEngineExtension
extends PhabricatorGuidanceEngineExtension {
const GUIDANCEKEY = 'core.auth.providers';
public function canGenerateGuidance(PhabricatorGuidanceContext $context) {
return ($context instanceof PhabricatorAuthProvidersGuidanceContext);
}
public function generateGuidance(PhabricatorGuidanceContext $context) {
$configs = id(new PhabricatorAuthProviderConfigQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withIsEnabled(true)
->execute();
$allows_registration = false;
foreach ($configs as $config) {
$provider = $config->getProvider();
if ($provider->shouldAllowRegistration()) {
$allows_registration = true;
break;
}
}
// If no provider allows registration, we don't need provide any warnings
// about registration being too open.
if (!$allows_registration) {
return array();
}
$domains_key = 'auth.email-domains';
$domains_link = $this->renderConfigLink($domains_key);
$domains_value = PhabricatorEnv::getEnvConfig($domains_key);
$approval_key = 'auth.require-approval';
$approval_link = $this->renderConfigLink($approval_key);
$approval_value = PhabricatorEnv::getEnvConfig($approval_key);
$results = array();
if ($domains_value) {
$message = pht(
- 'Phabricator is configured with an email domain whitelist (in %s), so '.
+ 'This server is configured with an email domain whitelist (in %s), so '.
'only users with a verified email address at one of these %s '.
'allowed domain(s) will be able to register an account: %s',
$domains_link,
phutil_count($domains_value),
phutil_tag('strong', array(), implode(', ', $domains_value)));
$results[] = $this->newGuidance('core.auth.email-domains.on')
->setMessage($message);
} else {
$message = pht(
- 'Anyone who can browse to this Phabricator install will be able to '.
+ 'Anyone who can browse to this this server will be able to '.
'register an account. To add email domain restrictions, configure '.
'%s.',
$domains_link);
$results[] = $this->newGuidance('core.auth.email-domains.off')
->setMessage($message);
}
if ($approval_value) {
$message = pht(
'Administrative approvals are enabled (in %s), so all new users must '.
'have their accounts approved by an administrator.',
$approval_link);
$results[] = $this->newGuidance('core.auth.require-approval.on')
->setMessage($message);
} else {
$message = pht(
'Administrative approvals are disabled, so users who register will '.
'be able to use their accounts immediately. To enable approvals, '.
'configure %s.',
$approval_link);
$results[] = $this->newGuidance('core.auth.require-approval.off')
->setMessage($message);
}
if (!$domains_value && !$approval_value) {
$message = pht(
'You can safely ignore these warnings if the install itself has '.
'access controls (for example, it is deployed on a VPN) or if all of '.
'the configured providers have access controls (for example, they are '.
'all private LDAP or OAuth servers).');
$results[] = $this->newWarning('core.auth.warning')
->setMessage($message);
}
$locked_config_key = 'auth.lock-config';
$is_locked = PhabricatorEnv::getEnvConfig($locked_config_key);
if ($is_locked) {
$message = pht(
'Authentication provider configuration is locked, and can not be '.
'changed without being unlocked. See the configuration setting %s '.
'for details.',
phutil_tag(
'a',
array(
'href' => '/config/edit/'.$locked_config_key,
),
$locked_config_key));
$results[] = $this->newWarning('auth.locked-config')
->setPriority(500)
->setMessage($message);
}
return $results;
}
private function renderConfigLink($key) {
return phutil_tag(
'a',
array(
'href' => '/config/edit/'.$key.'/',
'target' => '_blank',
),
$key);
}
}
diff --git a/src/applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php b/src/applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php
index 3190a842f7..21ef32ae05 100644
--- a/src/applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php
+++ b/src/applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php
@@ -1,91 +1,90 @@
<?php
final class PhabricatorAuthManagementRecoverWorkflow
extends PhabricatorAuthManagementWorkflow {
protected function didConstruct() {
$this
->setName('recover')
->setExamples('**recover** __username__')
->setSynopsis(
pht(
- 'Recover access to an account if you have locked yourself out '.
- 'of Phabricator.'))
+ 'Recover access to an account if you have locked yourself out.'))
->setArguments(
array(
array(
'name' => 'force-full-session',
'help' => pht(
'Recover directly into a full session without requiring MFA '.
'or other login checks.'),
),
array(
'name' => 'username',
'wildcard' => true,
),
));
}
public function execute(PhutilArgumentParser $args) {
$usernames = $args->getArg('username');
if (!$usernames) {
throw new PhutilArgumentUsageException(
pht('You must specify the username of the account to recover.'));
} else if (count($usernames) > 1) {
throw new PhutilArgumentUsageException(
pht('You can only recover the username for one account.'));
}
$username = head($usernames);
$user = id(new PhabricatorPeopleQuery())
->setViewer($this->getViewer())
->withUsernames(array($username))
->executeOne();
if (!$user) {
throw new PhutilArgumentUsageException(
pht(
'No such user "%s" to recover.',
$username));
}
if (!$user->canEstablishWebSessions()) {
throw new PhutilArgumentUsageException(
pht(
'This account ("%s") can not establish web sessions, so it is '.
'not possible to generate a functional recovery link. Special '.
'accounts like daemons and mailing lists can not log in via the '.
'web UI.',
$username));
}
$force_full_session = $args->getArg('force-full-session');
$engine = new PhabricatorAuthSessionEngine();
$onetime_uri = $engine->getOneTimeLoginURI(
$user,
null,
PhabricatorAuthSessionEngine::ONETIME_RECOVER,
$force_full_session);
$console = PhutilConsole::getConsole();
$console->writeOut(
pht(
'Use this link to recover access to the "%s" account from the web '.
'interface:',
$username));
$console->writeOut("\n\n");
$console->writeOut(' %s', $onetime_uri);
$console->writeOut("\n\n");
$console->writeOut(
"%s\n",
pht(
'After logging in, you can use the "Auth" application to add or '.
'restore authentication providers and allow normal logins to '.
'succeed.'));
return 0;
}
}
diff --git a/src/applications/auth/management/PhabricatorAuthManagementRevokeWorkflow.php b/src/applications/auth/management/PhabricatorAuthManagementRevokeWorkflow.php
index 6d907e67d6..67ee746c2c 100644
--- a/src/applications/auth/management/PhabricatorAuthManagementRevokeWorkflow.php
+++ b/src/applications/auth/management/PhabricatorAuthManagementRevokeWorkflow.php
@@ -1,195 +1,195 @@
<?php
final class PhabricatorAuthManagementRevokeWorkflow
extends PhabricatorAuthManagementWorkflow {
protected function didConstruct() {
$this
->setName('revoke')
->setExamples(
"**revoke** --list\n".
"**revoke** --type __type__ --from __@user__\n".
"**revoke** --everything --everywhere")
->setSynopsis(
pht(
'Revoke credentials which may have been leaked or disclosed.'))
->setArguments(
array(
array(
'name' => 'from',
'param' => 'object',
'help' => pht(
'Revoke credentials for the specified object. To revoke '.
'credentials for a user, use "@username".'),
),
array(
'name' => 'type',
'param' => 'type',
'help' => pht('Revoke credentials of the given type.'),
),
array(
'name' => 'list',
'help' => pht(
'List information about available credential revokers.'),
),
array(
'name' => 'everything',
'help' => pht('Revoke all credentials types.'),
),
array(
'name' => 'everywhere',
'help' => pht('Revoke from all credential owners.'),
),
array(
'name' => 'force',
'help' => pht('Revoke credentials without prompting.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$viewer = $this->getViewer();
$all_types = PhabricatorAuthRevoker::getAllRevokers();
$is_force = $args->getArg('force');
// The "--list" flag is compatible with revoker selection flags like
// "--type" to filter the list, but not compatible with target selection
// flags like "--from".
$is_list = $args->getArg('list');
$type = $args->getArg('type');
$is_everything = $args->getArg('everything');
- if (!strlen($type) && !$is_everything) {
+ if ($type === null && !$is_everything) {
if ($is_list) {
// By default, "bin/revoke --list" implies "--everything".
$types = $all_types;
} else {
throw new PhutilArgumentUsageException(
pht(
'Specify the credential type to revoke with "--type" or specify '.
'"--everything". Use "--list" to list available credential '.
'types.'));
}
} else if (strlen($type) && $is_everything) {
throw new PhutilArgumentUsageException(
pht(
'Specify the credential type to revoke with "--type" or '.
'"--everything", but not both.'));
} else if ($is_everything) {
$types = $all_types;
} else {
if (empty($all_types[$type])) {
throw new PhutilArgumentUsageException(
pht(
'Credential type "%s" is not valid. Valid credential types '.
'are: %s.',
$type,
implode(', ', array_keys($all_types))));
}
$types = array($all_types[$type]);
}
$is_everywhere = $args->getArg('everywhere');
$from = $args->getArg('from');
if ($is_list) {
- if (strlen($from) || $is_everywhere) {
+ if ($from !== null || $is_everywhere) {
throw new PhutilArgumentUsageException(
pht(
'You can not "--list" and revoke credentials (with "--from" or '.
'"--everywhere") in the same operation.'));
}
}
if ($is_list) {
$last_key = last_key($types);
foreach ($types as $key => $type) {
echo tsprintf(
"**%s** (%s)\n\n",
$type->getRevokerKey(),
$type->getRevokerName());
id(new PhutilConsoleBlock())
->addParagraph(tsprintf('%B', $type->getRevokerDescription()))
->draw();
}
return 0;
}
$target = null;
if (!strlen($from) && !$is_everywhere) {
throw new PhutilArgumentUsageException(
pht(
'Specify the target to revoke credentials from with "--from" or '.
'specify "--everywhere".'));
} else if (strlen($from) && $is_everywhere) {
throw new PhutilArgumentUsageException(
pht(
'Specify the target to revoke credentials from with "--from" or '.
'specify "--everywhere", but not both.'));
} else if ($is_everywhere) {
// Just carry the flag through.
} else {
$target = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withNames(array($from))
->executeOne();
if (!$target) {
throw new PhutilArgumentUsageException(
pht(
'Target "%s" is not a valid target to revoke credentials from. '.
'Usually, revoke from "@username".',
$from));
}
}
if ($is_everywhere && !$is_force) {
echo id(new PhutilConsoleBlock())
->addParagraph(
pht(
'You are destroying an entire class of credentials. This may be '.
'very disruptive to users. You should normally do this only if '.
'you suspect there has been a widespread compromise which may '.
'have impacted everyone.'))
->drawConsoleString();
$prompt = pht('Really destroy credentials everywhere?');
if (!phutil_console_confirm($prompt)) {
throw new PhutilArgumentUsageException(
pht('Aborted workflow.'));
}
}
foreach ($types as $type) {
$type->setViewer($viewer);
if ($is_everywhere) {
$count = $type->revokeAllCredentials();
} else {
$count = $type->revokeCredentialsFrom($target);
}
echo tsprintf(
"%s\n",
pht(
'Destroyed %s credential(s) of type "%s".',
new PhutilNumber($count),
$type->getRevokerKey()));
$guidance = $type->getRevokerNextSteps();
if ($guidance !== null) {
echo tsprintf(
"%s\n",
$guidance);
}
}
echo tsprintf(
"%s\n",
pht('Done.'));
return 0;
}
}
diff --git a/src/applications/auth/management/PhabricatorAuthManagementTrustOAuthClientWorkflow.php b/src/applications/auth/management/PhabricatorAuthManagementTrustOAuthClientWorkflow.php
index ee5e50b38e..8455b5697f 100644
--- a/src/applications/auth/management/PhabricatorAuthManagementTrustOAuthClientWorkflow.php
+++ b/src/applications/auth/management/PhabricatorAuthManagementTrustOAuthClientWorkflow.php
@@ -1,64 +1,63 @@
<?php
final class PhabricatorAuthManagementTrustOAuthClientWorkflow
extends PhabricatorAuthManagementWorkflow {
protected function didConstruct() {
$this
->setName('trust-oauth-client')
->setExamples('**trust-oauth-client** [--id client_id]')
->setSynopsis(
pht(
- 'Set Phabricator to trust an OAuth client. Phabricator '.
- 'redirects to trusted OAuth clients that users have authorized '.
- 'without user intervention.'))
+ 'Mark an OAuth client as trusted. Trusted OAuth clients may be '.
+ 'reauthorized without requiring users to manually confirm the '.
+ 'action.'))
->setArguments(
array(
array(
'name' => 'id',
'param' => 'id',
'help' => pht('The id of the OAuth client.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$id = $args->getArg('id');
if (!$id) {
throw new PhutilArgumentUsageException(
pht(
- 'Specify an OAuth client id with %s.',
- '--id'));
+ 'Specify an OAuth client id with "--id".'));
}
$client = id(new PhabricatorOAuthServerClientQuery())
->setViewer($this->getViewer())
->withIDs(array($id))
->executeOne();
if (!$client) {
throw new PhutilArgumentUsageException(
pht(
'Failed to find an OAuth client with id %s.', $id));
}
if ($client->getIsTrusted()) {
throw new PhutilArgumentUsageException(
pht(
- 'Phabricator already trusts OAuth client "%s".',
+ 'OAuth client "%s" is already trusted.',
$client->getName()));
}
$client->setIsTrusted(1);
$client->save();
$console = PhutilConsole::getConsole();
$console->writeOut(
"%s\n",
pht(
- 'Updated; Phabricator trusts OAuth client %s.',
+ 'OAuth client "%s" is now trusted.',
$client->getName()));
}
}
diff --git a/src/applications/auth/management/PhabricatorAuthManagementUntrustOAuthClientWorkflow.php b/src/applications/auth/management/PhabricatorAuthManagementUntrustOAuthClientWorkflow.php
index a0b3e02fa4..d3d7ff3aea 100644
--- a/src/applications/auth/management/PhabricatorAuthManagementUntrustOAuthClientWorkflow.php
+++ b/src/applications/auth/management/PhabricatorAuthManagementUntrustOAuthClientWorkflow.php
@@ -1,64 +1,63 @@
<?php
final class PhabricatorAuthManagementUntrustOAuthClientWorkflow
extends PhabricatorAuthManagementWorkflow {
protected function didConstruct() {
$this
->setName('untrust-oauth-client')
->setExamples('**untrust-oauth-client** [--id client_id]')
->setSynopsis(
pht(
- 'Set Phabricator to not trust an OAuth client. Phabricator '.
- 'redirects to trusted OAuth clients that users have authorized '.
- 'without user intervention.'))
+ 'Remove trust from an OAuth client. Users must manually confirm '.
+ 'reauthorization of untrusted OAuth clients.'))
->setArguments(
array(
array(
'name' => 'id',
'param' => 'id',
'help' => pht('The id of the OAuth client.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$id = $args->getArg('id');
if (!$id) {
throw new PhutilArgumentUsageException(
pht(
'Specify an OAuth client ID with %s.',
'--id'));
}
$client = id(new PhabricatorOAuthServerClientQuery())
->setViewer($this->getViewer())
->withIDs(array($id))
->executeOne();
if (!$client) {
throw new PhutilArgumentUsageException(
pht(
'Failed to find an OAuth client with ID %s.', $id));
}
if (!$client->getIsTrusted()) {
throw new PhutilArgumentUsageException(
pht(
- 'Phabricator already does not trust OAuth client "%s".',
+ 'OAuth client "%s" is already untrusted.',
$client->getName()));
}
$client->setIsTrusted(0);
$client->save();
$console = PhutilConsole::getConsole();
$console->writeOut(
"%s\n",
pht(
- 'Updated; Phabricator does not trust OAuth client %s.',
+ 'OAuth client "%s" is now trusted.',
$client->getName()));
}
}
diff --git a/src/applications/auth/provider/PhabricatorAmazonAuthProvider.php b/src/applications/auth/provider/PhabricatorAmazonAuthProvider.php
index c35bd547f3..4f62ee9f45 100644
--- a/src/applications/auth/provider/PhabricatorAmazonAuthProvider.php
+++ b/src/applications/auth/provider/PhabricatorAmazonAuthProvider.php
@@ -1,46 +1,46 @@
<?php
final class PhabricatorAmazonAuthProvider
extends PhabricatorOAuth2AuthProvider {
public function getProviderName() {
return pht('Amazon');
}
protected function getProviderConfigurationHelp() {
$login_uri = PhabricatorEnv::getURI($this->getLoginURI());
$uri = new PhutilURI(PhabricatorEnv::getProductionURI('/'));
$https_note = null;
if ($uri->getProtocol() !== 'https') {
$https_note = pht(
- 'NOTE: Amazon **requires** HTTPS, but your Phabricator install does '.
+ 'NOTE: Amazon **requires** HTTPS, but this service does '.
'not use HTTPS. **You will not be able to add Amazon as an '.
'authentication provider until you configure HTTPS on this install**.');
}
return pht(
"%s\n\n".
"To configure Amazon OAuth, create a new 'API Project' here:".
"\n\n".
"http://login.amazon.com/manageApps".
"\n\n".
"Use these settings:".
"\n\n".
" - **Allowed Return URLs:** Add this: `%s`".
"\n\n".
"After completing configuration, copy the **Client ID** and ".
"**Client Secret** to the fields above.",
$https_note,
$login_uri);
}
protected function newOAuthAdapter() {
return new PhutilAmazonAuthAdapter();
}
protected function getLoginIcon() {
return 'Amazon';
}
}
diff --git a/src/applications/auth/provider/PhabricatorGitHubAuthProvider.php b/src/applications/auth/provider/PhabricatorGitHubAuthProvider.php
index 6a7667e2d6..52bcae4916 100644
--- a/src/applications/auth/provider/PhabricatorGitHubAuthProvider.php
+++ b/src/applications/auth/provider/PhabricatorGitHubAuthProvider.php
@@ -1,44 +1,44 @@
<?php
final class PhabricatorGitHubAuthProvider
extends PhabricatorOAuth2AuthProvider {
public function getProviderName() {
return pht('GitHub');
}
protected function getProviderConfigurationHelp() {
$uri = PhabricatorEnv::getProductionURI('/');
$callback_uri = PhabricatorEnv::getURI($this->getLoginURI());
return pht(
"To configure GitHub OAuth, create a new GitHub Application here:".
"\n\n".
"https://github.com/settings/applications/new".
"\n\n".
"You should use these settings in your application:".
"\n\n".
" - **URL:** Set this to your full domain with protocol. For this ".
- " Phabricator install, the correct value is: `%s`\n".
+ " server, the correct value is: `%s`\n".
" - **Callback URL**: Set this to: `%s`\n".
"\n\n".
"Once you've created an application, copy the **Client ID** and ".
"**Client Secret** into the fields above.",
$uri,
$callback_uri);
}
protected function newOAuthAdapter() {
return new PhutilGitHubAuthAdapter();
}
protected function getLoginIcon() {
return 'Github';
}
public function getLoginURI() {
// TODO: Clean this up. See PhabricatorAuthOldOAuthRedirectController.
return '/oauth/github/login/';
}
}
diff --git a/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php b/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php
index 8a858f2bea..7b2756dad5 100644
--- a/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php
+++ b/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php
@@ -1,370 +1,371 @@
<?php
final class PhabricatorJIRAAuthProvider
extends PhabricatorOAuth1AuthProvider
implements DoorkeeperRemarkupURIInterface {
public function getProviderName() {
return pht('JIRA');
}
public function getDescriptionForCreate() {
return pht('Configure JIRA OAuth. NOTE: Only supports JIRA 6.');
}
public function getConfigurationHelp() {
return $this->getProviderConfigurationHelp();
}
protected function getProviderConfigurationHelp() {
if ($this->isSetup()) {
return pht(
"**Step 1 of 2**: Provide the name and URI for your JIRA install.\n\n".
"In the next step, you will configure JIRA.");
} else {
$login_uri = PhabricatorEnv::getURI($this->getLoginURI());
return pht(
"**Step 2 of 2**: In this step, you will configure JIRA.\n\n".
"**Create a JIRA Application**: Log into JIRA and go to ".
"**Administration**, then **Add-ons**, then **Application Links**. ".
"Click the button labeled **Add Application Link**, and use these ".
"settings to create an application:\n\n".
" - **Server URL**: `%s`\n".
" - Then, click **Next**. On the second page:\n".
- " - **Application Name**: `Phabricator`\n".
+ " - **Application Name**: `%s`\n".
" - **Application Type**: `Generic Application`\n".
" - Then, click **Create**.\n\n".
"**Configure Your Application**: Find the application you just ".
"created in the table, and click the **Configure** link under ".
"**Actions**. Select **Incoming Authentication** and click the ".
"**OAuth** tab (it may be selected by default). Then, use these ".
"settings:\n\n".
" - **Consumer Key**: Set this to the \"Consumer Key\" value in the ".
"form above.\n".
- " - **Consumer Name**: `Phabricator`\n".
+ " - **Consumer Name**: `%s`\n".
" - **Public Key**: Set this to the \"Public Key\" value in the ".
"form above.\n".
" - **Consumer Callback URL**: `%s`\n".
"Click **Save** in JIRA. Authentication should now be configured, ".
"and this provider should work correctly.",
PhabricatorEnv::getProductionURI('/'),
+ PlatformSymbols::getPlatformServerName(),
+ PlatformSymbols::getPlatformServerName(),
$login_uri);
}
}
protected function newOAuthAdapter() {
$config = $this->getProviderConfig();
return id(new PhutilJIRAAuthAdapter())
->setAdapterDomain($config->getProviderDomain())
->setJIRABaseURI($config->getProperty(self::PROPERTY_JIRA_URI))
->setPrivateKey(
new PhutilOpaqueEnvelope(
$config->getProperty(self::PROPERTY_PRIVATE_KEY)));
}
protected function getLoginIcon() {
return 'Jira';
}
private function isSetup() {
return !$this->getProviderConfig()->getID();
}
const PROPERTY_JIRA_NAME = 'oauth1:jira:name';
const PROPERTY_JIRA_URI = 'oauth1:jira:uri';
const PROPERTY_PUBLIC_KEY = 'oauth1:jira:key:public';
const PROPERTY_PRIVATE_KEY = 'oauth1:jira:key:private';
const PROPERTY_REPORT_LINK = 'oauth1:jira:report:link';
const PROPERTY_REPORT_COMMENT = 'oauth1:jira:report:comment';
public function readFormValuesFromProvider() {
$config = $this->getProviderConfig();
$uri = $config->getProperty(self::PROPERTY_JIRA_URI);
return array(
self::PROPERTY_JIRA_NAME => $this->getProviderDomain(),
self::PROPERTY_JIRA_URI => $uri,
);
}
public function readFormValuesFromRequest(AphrontRequest $request) {
$is_setup = $this->isSetup();
if ($is_setup) {
$name = $request->getStr(self::PROPERTY_JIRA_NAME);
} else {
$name = $this->getProviderDomain();
}
return array(
self::PROPERTY_JIRA_NAME => $name,
self::PROPERTY_JIRA_URI => $request->getStr(self::PROPERTY_JIRA_URI),
self::PROPERTY_REPORT_LINK =>
$request->getInt(self::PROPERTY_REPORT_LINK, 0),
self::PROPERTY_REPORT_COMMENT =>
$request->getInt(self::PROPERTY_REPORT_COMMENT, 0),
);
}
public function processEditForm(
AphrontRequest $request,
array $values) {
$errors = array();
$issues = array();
$is_setup = $this->isSetup();
$key_name = self::PROPERTY_JIRA_NAME;
$key_uri = self::PROPERTY_JIRA_URI;
if (!strlen($values[$key_name])) {
$errors[] = pht('JIRA instance name is required.');
$issues[$key_name] = pht('Required');
} else if (!preg_match('/^[a-z0-9.]+\z/', $values[$key_name])) {
$errors[] = pht(
'JIRA instance name must contain only lowercase letters, digits, and '.
'period.');
$issues[$key_name] = pht('Invalid');
}
if (!strlen($values[$key_uri])) {
$errors[] = pht('JIRA base URI is required.');
$issues[$key_uri] = pht('Required');
} else {
$uri = new PhutilURI($values[$key_uri]);
if (!$uri->getProtocol()) {
$errors[] = pht(
'JIRA base URI should include protocol (like "https://").');
$issues[$key_uri] = pht('Invalid');
}
}
if (!$errors && $is_setup) {
$config = $this->getProviderConfig();
$config->setProviderDomain($values[$key_name]);
$consumer_key = 'phjira.'.Filesystem::readRandomCharacters(16);
list($public, $private) = PhutilJIRAAuthAdapter::newJIRAKeypair();
$config->setProperty(self::PROPERTY_PUBLIC_KEY, $public);
$config->setProperty(self::PROPERTY_PRIVATE_KEY, $private);
$config->setProperty(self::PROPERTY_CONSUMER_KEY, $consumer_key);
}
return array($errors, $issues, $values);
}
public function extendEditForm(
AphrontRequest $request,
AphrontFormView $form,
array $values,
array $issues) {
if (!function_exists('openssl_pkey_new')) {
// TODO: This could be a bit prettier.
throw new Exception(
pht(
"The PHP 'openssl' extension is not installed. You must install ".
"this extension in order to add a JIRA authentication provider, ".
"because JIRA OAuth requests use the RSA-SHA1 signing algorithm. ".
- "Install the 'openssl' extension, restart Phabricator, and try ".
+ "Install the 'openssl' extension, restart everything, and try ".
"again."));
}
$form->appendRemarkupInstructions(
pht(
'NOTE: This provider **only supports JIRA 6**. It will not work with '.
'JIRA 5 or earlier.'));
$is_setup = $this->isSetup();
$viewer = $request->getViewer();
$e_required = $request->isFormPost() ? null : true;
$v_name = $values[self::PROPERTY_JIRA_NAME];
if ($is_setup) {
$e_name = idx($issues, self::PROPERTY_JIRA_NAME, $e_required);
} else {
$e_name = null;
}
$v_uri = $values[self::PROPERTY_JIRA_URI];
$e_uri = idx($issues, self::PROPERTY_JIRA_URI, $e_required);
if ($is_setup) {
$form
->appendRemarkupInstructions(
pht(
"**JIRA Instance Name**\n\n".
- "Choose a permanent name for this instance of JIRA. Phabricator ".
- "uses this name internally to keep track of this instance of ".
+ "Choose a permanent name for this instance of JIRA. This name is ".
+ "used internally to keep track of this particular instance of ".
"JIRA, in case the URL changes later.\n\n".
"Use lowercase letters, digits, and period. For example, ".
"`jira`, `jira.mycompany` or `jira.engineering` are reasonable ".
"names."))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('JIRA Instance Name'))
->setValue($v_name)
->setName(self::PROPERTY_JIRA_NAME)
->setError($e_name));
} else {
$form
->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('JIRA Instance Name'))
->setValue($v_name));
}
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('JIRA Base URI'))
->setValue($v_uri)
->setName(self::PROPERTY_JIRA_URI)
->setCaption(
pht(
'The URI where JIRA is installed. For example: %s',
phutil_tag('tt', array(), 'https://jira.mycompany.com/')))
->setError($e_uri));
if (!$is_setup) {
$config = $this->getProviderConfig();
$ckey = $config->getProperty(self::PROPERTY_CONSUMER_KEY);
$ckey = phutil_tag('tt', array(), $ckey);
$pkey = $config->getProperty(self::PROPERTY_PUBLIC_KEY);
$pkey = phutil_escape_html_newlines($pkey);
$pkey = phutil_tag('tt', array(), $pkey);
$form
->appendRemarkupInstructions(
pht(
'NOTE: **To complete setup**, copy and paste these keys into JIRA '.
'according to the instructions below.'))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Consumer Key'))
->setValue($ckey))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Public Key'))
->setValue($pkey));
$form
->appendRemarkupInstructions(
pht(
'= Integration Options = '."\n".
'Configure how to record Revisions on JIRA tasks.'."\n\n".
'Note you\'ll have to restart the daemons for this to take '.
'effect.'))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
self::PROPERTY_REPORT_LINK,
1,
new PHUIRemarkupView(
$viewer,
pht(
'Create **Issue Link** to the Revision, as an "implemented '.
'in" relationship.')),
$this->shouldCreateJIRALink()))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
self::PROPERTY_REPORT_COMMENT,
1,
new PHUIRemarkupView(
$viewer,
pht(
- '**Post a comment** in the JIRA task, similar to the '.
- 'emails Phabricator sends.')),
+ '**Post a comment** in the JIRA task.')),
$this->shouldCreateJIRAComment()));
}
}
/**
* JIRA uses a setup step to generate public/private keys.
*/
public function hasSetupStep() {
return true;
}
public static function getJIRAProvider() {
$providers = self::getAllEnabledProviders();
foreach ($providers as $provider) {
if ($provider instanceof PhabricatorJIRAAuthProvider) {
return $provider;
}
}
return null;
}
public function newJIRAFuture(
PhabricatorExternalAccount $account,
$path,
$method,
$params = array()) {
$adapter = clone $this->getAdapter();
$adapter->setToken($account->getProperty('oauth1.token'));
$adapter->setTokenSecret($account->getProperty('oauth1.token.secret'));
return $adapter->newJIRAFuture($path, $method, $params);
}
public function shouldCreateJIRALink() {
$config = $this->getProviderConfig();
return $config->getProperty(self::PROPERTY_REPORT_LINK, true);
}
public function shouldCreateJIRAComment() {
$config = $this->getProviderConfig();
return $config->getProperty(self::PROPERTY_REPORT_COMMENT, true);
}
/* -( DoorkeeperRemarkupURIInterface )------------------------------------- */
public function getDoorkeeperURIRef(PhutilURI $uri) {
$uri_string = phutil_string_cast($uri);
$pattern = '((https?://\S+?)/browse/([A-Z][A-Z0-9]*-[1-9]\d*))';
$matches = null;
if (!preg_match($pattern, $uri_string, $matches)) {
return null;
}
if (strlen($uri->getFragment())) {
return null;
}
if ($uri->getQueryParamsAsPairList()) {
return null;
}
$domain = $matches[1];
$issue = $matches[2];
$config = $this->getProviderConfig();
$base_uri = $config->getProperty(self::PROPERTY_JIRA_URI);
if ($domain !== rtrim($base_uri, '/')) {
return null;
}
return id(new DoorkeeperURIRef())
->setURI($uri)
->setApplicationType(DoorkeeperBridgeJIRA::APPTYPE_JIRA)
->setApplicationDomain($this->getProviderDomain())
->setObjectType(DoorkeeperBridgeJIRA::OBJTYPE_ISSUE)
->setObjectID($issue);
}
}
diff --git a/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php b/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php
index c899caca71..d1db832aa2 100644
--- a/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php
+++ b/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php
@@ -1,496 +1,496 @@
<?php
final class PhabricatorLDAPAuthProvider extends PhabricatorAuthProvider {
private $adapter;
public function getProviderName() {
return pht('LDAP');
}
public function getDescriptionForCreate() {
return pht(
'Configure a connection to an LDAP server so that users can use their '.
- 'LDAP credentials to log in to Phabricator.');
+ 'LDAP credentials to log in.');
}
public function getDefaultProviderConfig() {
return parent::getDefaultProviderConfig()
->setProperty(self::KEY_PORT, 389)
->setProperty(self::KEY_VERSION, 3);
}
public function getAdapter() {
if (!$this->adapter) {
$conf = $this->getProviderConfig();
$realname_attributes = $conf->getProperty(self::KEY_REALNAME_ATTRIBUTES);
if (!is_array($realname_attributes)) {
$realname_attributes = array();
}
$search_attributes = $conf->getProperty(self::KEY_SEARCH_ATTRIBUTES);
$search_attributes = phutil_split_lines($search_attributes, false);
$search_attributes = array_filter($search_attributes);
$adapter = id(new PhutilLDAPAuthAdapter())
->setHostname(
$conf->getProperty(self::KEY_HOSTNAME))
->setPort(
$conf->getProperty(self::KEY_PORT))
->setBaseDistinguishedName(
$conf->getProperty(self::KEY_DISTINGUISHED_NAME))
->setSearchAttributes($search_attributes)
->setUsernameAttribute(
$conf->getProperty(self::KEY_USERNAME_ATTRIBUTE))
->setRealNameAttributes($realname_attributes)
->setLDAPVersion(
$conf->getProperty(self::KEY_VERSION))
->setLDAPReferrals(
$conf->getProperty(self::KEY_REFERRALS))
->setLDAPStartTLS(
$conf->getProperty(self::KEY_START_TLS))
->setAlwaysSearch($conf->getProperty(self::KEY_ALWAYS_SEARCH))
->setAnonymousUsername(
$conf->getProperty(self::KEY_ANONYMOUS_USERNAME))
->setAnonymousPassword(
new PhutilOpaqueEnvelope(
$conf->getProperty(self::KEY_ANONYMOUS_PASSWORD)))
->setActiveDirectoryDomain(
$conf->getProperty(self::KEY_ACTIVEDIRECTORY_DOMAIN));
$this->adapter = $adapter;
}
return $this->adapter;
}
protected function renderLoginForm(AphrontRequest $request, $mode) {
$viewer = $request->getUser();
$dialog = id(new AphrontDialogView())
->setSubmitURI($this->getLoginURI())
->setUser($viewer);
if ($mode == 'link') {
$dialog->setTitle(pht('Link LDAP Account'));
$dialog->addSubmitButton(pht('Link Accounts'));
$dialog->addCancelButton($this->getSettingsURI());
} else if ($mode == 'refresh') {
$dialog->setTitle(pht('Refresh LDAP Account'));
$dialog->addSubmitButton(pht('Refresh Account'));
$dialog->addCancelButton($this->getSettingsURI());
} else {
if ($this->shouldAllowRegistration()) {
$dialog->setTitle(pht('Log In or Register with LDAP'));
$dialog->addSubmitButton(pht('Log In or Register'));
} else {
$dialog->setTitle(pht('Log In with LDAP'));
$dialog->addSubmitButton(pht('Log In'));
}
if ($mode == 'login') {
$dialog->addCancelButton($this->getStartURI());
}
}
$v_user = $request->getStr('ldap_username');
$e_user = null;
$e_pass = null;
$errors = array();
if ($request->isHTTPPost()) {
// NOTE: This is intentionally vague so as not to disclose whether a
// given username exists.
$e_user = pht('Invalid');
$e_pass = pht('Invalid');
$errors[] = pht('Username or password are incorrect.');
}
$form = id(new PHUIFormLayoutView())
->setUser($viewer)
->setFullWidth(true)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('LDAP Username'))
->setName('ldap_username')
->setAutofocus(true)
->setValue($v_user)
->setError($e_user))
->appendChild(
id(new AphrontFormPasswordControl())
->setLabel(pht('LDAP Password'))
->setName('ldap_password')
->setError($e_pass));
if ($errors) {
$errors = id(new PHUIInfoView())->setErrors($errors);
}
$dialog->appendChild($errors);
$dialog->appendChild($form);
return $dialog;
}
public function processLoginRequest(
PhabricatorAuthLoginController $controller) {
$request = $controller->getRequest();
$viewer = $request->getUser();
$response = null;
$account = null;
$username = $request->getStr('ldap_username');
$password = $request->getStr('ldap_password');
$has_password = strlen($password);
$password = new PhutilOpaqueEnvelope($password);
if (!strlen($username) || !$has_password) {
$response = $controller->buildProviderPageResponse(
$this,
$this->renderLoginForm($request, 'login'));
return array($account, $response);
}
if ($request->isFormPost()) {
try {
if (strlen($username) && $has_password) {
$adapter = $this->getAdapter();
$adapter->setLoginUsername($username);
$adapter->setLoginPassword($password);
// TODO: This calls ldap_bind() eventually, which dumps cleartext
// passwords to the error log. See note in PhutilLDAPAuthAdapter.
// See T3351.
DarkConsoleErrorLogPluginAPI::enableDiscardMode();
$identifiers = $adapter->getAccountIdentifiers();
DarkConsoleErrorLogPluginAPI::disableDiscardMode();
} else {
throw new Exception(pht('Username and password are required!'));
}
} catch (PhutilAuthCredentialException $ex) {
$response = $controller->buildProviderPageResponse(
$this,
$this->renderLoginForm($request, 'login'));
return array($account, $response);
} catch (Exception $ex) {
// TODO: Make this cleaner.
throw $ex;
}
}
$account = $this->newExternalAccountForIdentifiers($identifiers);
return array($account, $response);
}
const KEY_HOSTNAME = 'ldap:host';
const KEY_PORT = 'ldap:port';
const KEY_DISTINGUISHED_NAME = 'ldap:dn';
const KEY_SEARCH_ATTRIBUTES = 'ldap:search-attribute';
const KEY_USERNAME_ATTRIBUTE = 'ldap:username-attribute';
const KEY_REALNAME_ATTRIBUTES = 'ldap:realname-attributes';
const KEY_VERSION = 'ldap:version';
const KEY_REFERRALS = 'ldap:referrals';
const KEY_START_TLS = 'ldap:start-tls';
// TODO: This is misspelled! See T13005.
const KEY_ANONYMOUS_USERNAME = 'ldap:anoynmous-username';
const KEY_ANONYMOUS_PASSWORD = 'ldap:anonymous-password';
const KEY_ALWAYS_SEARCH = 'ldap:always-search';
const KEY_ACTIVEDIRECTORY_DOMAIN = 'ldap:activedirectory-domain';
private function getPropertyKeys() {
return array_keys($this->getPropertyLabels());
}
private function getPropertyLabels() {
return array(
self::KEY_HOSTNAME => pht('LDAP Hostname'),
self::KEY_PORT => pht('LDAP Port'),
self::KEY_DISTINGUISHED_NAME => pht('Base Distinguished Name'),
self::KEY_SEARCH_ATTRIBUTES => pht('Search Attributes'),
self::KEY_ALWAYS_SEARCH => pht('Always Search'),
self::KEY_ANONYMOUS_USERNAME => pht('Anonymous Username'),
self::KEY_ANONYMOUS_PASSWORD => pht('Anonymous Password'),
self::KEY_USERNAME_ATTRIBUTE => pht('Username Attribute'),
self::KEY_REALNAME_ATTRIBUTES => pht('Realname Attributes'),
self::KEY_VERSION => pht('LDAP Version'),
self::KEY_REFERRALS => pht('Enable Referrals'),
self::KEY_START_TLS => pht('Use TLS'),
self::KEY_ACTIVEDIRECTORY_DOMAIN => pht('ActiveDirectory Domain'),
);
}
public function readFormValuesFromProvider() {
$properties = array();
foreach ($this->getPropertyLabels() as $key => $ignored) {
$properties[$key] = $this->getProviderConfig()->getProperty($key);
}
return $properties;
}
public function readFormValuesFromRequest(AphrontRequest $request) {
$values = array();
foreach ($this->getPropertyKeys() as $key) {
switch ($key) {
case self::KEY_REALNAME_ATTRIBUTES:
$values[$key] = $request->getStrList($key, array());
break;
default:
$values[$key] = $request->getStr($key);
break;
}
}
return $values;
}
public function processEditForm(
AphrontRequest $request,
array $values) {
$errors = array();
$issues = array();
return array($errors, $issues, $values);
}
public static function assertLDAPExtensionInstalled() {
if (!function_exists('ldap_bind')) {
throw new Exception(
pht(
'Before you can set up or use LDAP, you need to install the PHP '.
'LDAP extension. It is not currently installed, so PHP can not '.
'talk to LDAP. Usually you can install it with '.
'`%s`, `%s`, or a similar package manager command.',
'yum install php-ldap',
'apt-get install php5-ldap'));
}
}
public function extendEditForm(
AphrontRequest $request,
AphrontFormView $form,
array $values,
array $issues) {
self::assertLDAPExtensionInstalled();
$labels = $this->getPropertyLabels();
$captions = array(
self::KEY_HOSTNAME =>
pht('Example: %s%sFor LDAPS, use: %s',
phutil_tag('tt', array(), pht('ldap.example.com')),
phutil_tag('br'),
phutil_tag('tt', array(), pht('ldaps://ldaps.example.com/'))),
self::KEY_DISTINGUISHED_NAME =>
pht('Example: %s',
phutil_tag('tt', array(), pht('ou=People, dc=example, dc=com'))),
self::KEY_USERNAME_ATTRIBUTE =>
pht('Example: %s',
phutil_tag('tt', array(), pht('sn'))),
self::KEY_REALNAME_ATTRIBUTES =>
pht('Example: %s',
phutil_tag('tt', array(), pht('firstname, lastname'))),
self::KEY_REFERRALS =>
pht('Follow referrals. Disable this for Windows AD 2003.'),
self::KEY_START_TLS =>
pht('Start TLS after binding to the LDAP server.'),
self::KEY_ALWAYS_SEARCH =>
pht('Always bind and search, even without a username and password.'),
);
$types = array(
self::KEY_REFERRALS => 'checkbox',
self::KEY_START_TLS => 'checkbox',
self::KEY_SEARCH_ATTRIBUTES => 'textarea',
self::KEY_REALNAME_ATTRIBUTES => 'list',
self::KEY_ANONYMOUS_PASSWORD => 'password',
self::KEY_ALWAYS_SEARCH => 'checkbox',
);
$instructions = array(
self::KEY_SEARCH_ATTRIBUTES => pht(
- "When a user types their LDAP username and password into Phabricator, ".
- "Phabricator can either bind to LDAP with those credentials directly ".
+ "When a user provides their LDAP username and password, this ".
+ "software can either bind to LDAP with those credentials directly ".
"(which is simpler, but not as powerful) or bind to LDAP with ".
"anonymous credentials, then search for record matching the supplied ".
"credentials (which is more complicated, but more powerful).\n\n".
"For many installs, direct binding is sufficient. However, you may ".
"want to search first if:\n\n".
" - You want users to be able to log in with either their username ".
" or their email address.\n".
" - The login/username is not part of the distinguished name in ".
" your LDAP records.\n".
" - You want to restrict logins to a subset of users (like only ".
" those in certain departments).\n".
" - Your LDAP server is configured in some other way that prevents ".
" direct binding from working correctly.\n\n".
"**To bind directly**, enter the LDAP attribute corresponding to the ".
"login name into the **Search Attributes** box below. Often, this is ".
"something like `sn` or `uid`. This is the simplest configuration, ".
"but will only work if the username is part of the distinguished ".
"name, and won't let you apply complex restrictions to logins.\n\n".
" lang=text,name=Simple Direct Binding\n".
" sn\n\n".
"**To search first**, provide an anonymous username and password ".
"below (or check the **Always Search** checkbox), then enter one ".
"or more search queries into this field, one per line. ".
"After binding, these queries will be used to identify the ".
"record associated with the login name the user typed.\n\n".
"Searches will be tried in order until a matching record is found. ".
"Each query can be a simple attribute name (like `sn` or `mail`), ".
"which will search for a matching record, or it can be a complex ".
"query that uses the string `\${login}` to represent the login ".
"name.\n\n".
"A common simple configuration is just an attribute name, like ".
"`sn`, which will work the same way direct binding works:\n\n".
" lang=text,name=Simple Example\n".
" sn\n\n".
"A slightly more complex configuration might let the user log in with ".
"either their login name or email address:\n\n".
" lang=text,name=Match Several Attributes\n".
" mail\n".
" sn\n\n".
"If your LDAP directory is more complex, or you want to perform ".
"sophisticated filtering, you can use more complex queries. Depending ".
"on your directory structure, this example might allow users to log ".
"in with either their email address or username, but only if they're ".
"in specific departments:\n\n".
" lang=text,name=Complex Example\n".
" (&(mail=\${login})(|(departmentNumber=1)(departmentNumber=2)))\n".
" (&(sn=\${login})(|(departmentNumber=1)(departmentNumber=2)))\n\n".
"All of the attribute names used here are just examples: your LDAP ".
"server may use different attribute names."),
self::KEY_ALWAYS_SEARCH => pht(
'To search for an LDAP record before authenticating, either check '.
'the **Always Search** checkbox or enter an anonymous '.
'username and password to use to perform the search.'),
self::KEY_USERNAME_ATTRIBUTE => pht(
'Optionally, specify a username attribute to use to prefill usernames '.
'when registering a new account. This is purely cosmetic and does not '.
'affect the login process, but you can configure it to make sure '.
'users get the same default username as their LDAP username, so '.
'usernames remain consistent across systems.'),
self::KEY_REALNAME_ATTRIBUTES => pht(
'Optionally, specify one or more comma-separated attributes to use to '.
'prefill the "Real Name" field when registering a new account. This '.
'is purely cosmetic and does not affect the login process, but can '.
'make registration a little easier.'),
);
foreach ($labels as $key => $label) {
$caption = idx($captions, $key);
$type = idx($types, $key);
$value = idx($values, $key);
$control = null;
switch ($type) {
case 'checkbox':
$control = id(new AphrontFormCheckboxControl())
->addCheckbox(
$key,
1,
hsprintf('<strong>%s:</strong> %s', $label, $caption),
$value);
break;
case 'list':
$control = id(new AphrontFormTextControl())
->setName($key)
->setLabel($label)
->setCaption($caption)
->setValue($value ? implode(', ', $value) : null);
break;
case 'password':
$control = id(new AphrontFormPasswordControl())
->setName($key)
->setLabel($label)
->setCaption($caption)
->setDisableAutocomplete(true)
->setValue($value);
break;
case 'textarea':
$control = id(new AphrontFormTextAreaControl())
->setName($key)
->setLabel($label)
->setCaption($caption)
->setValue($value);
break;
default:
$control = id(new AphrontFormTextControl())
->setName($key)
->setLabel($label)
->setCaption($caption)
->setValue($value);
break;
}
$instruction_text = idx($instructions, $key);
if (strlen($instruction_text)) {
$form->appendRemarkupInstructions($instruction_text);
}
$form->appendChild($control);
}
}
public function renderConfigPropertyTransactionTitle(
PhabricatorAuthProviderConfigTransaction $xaction) {
$author_phid = $xaction->getAuthorPHID();
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$key = $xaction->getMetadataValue(
PhabricatorAuthProviderConfigTransaction::PROPERTY_KEY);
$labels = $this->getPropertyLabels();
if (isset($labels[$key])) {
$label = $labels[$key];
$mask = false;
switch ($key) {
case self::KEY_ANONYMOUS_PASSWORD:
$mask = true;
break;
}
if ($mask) {
return pht(
'%s updated the "%s" value.',
$xaction->renderHandleLink($author_phid),
$label);
}
if ($old === null || $old === '') {
return pht(
'%s set the "%s" value to "%s".',
$xaction->renderHandleLink($author_phid),
$label,
$new);
} else {
return pht(
'%s changed the "%s" value from "%s" to "%s".',
$xaction->renderHandleLink($author_phid),
$label,
$old,
$new);
}
}
return parent::renderConfigPropertyTransactionTitle($xaction);
}
public static function getLDAPProvider() {
$providers = self::getAllEnabledProviders();
foreach ($providers as $provider) {
if ($provider instanceof PhabricatorLDAPAuthProvider) {
return $provider;
}
}
return null;
}
}
diff --git a/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php b/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php
index ef1991e8d7..b1590b9c82 100644
--- a/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php
+++ b/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php
@@ -1,285 +1,285 @@
<?php
abstract class PhabricatorOAuth1AuthProvider
extends PhabricatorOAuthAuthProvider {
protected $adapter;
const PROPERTY_CONSUMER_KEY = 'oauth1:consumer:key';
const PROPERTY_CONSUMER_SECRET = 'oauth1:consumer:secret';
const PROPERTY_PRIVATE_KEY = 'oauth1:private:key';
protected function getIDKey() {
return self::PROPERTY_CONSUMER_KEY;
}
protected function getSecretKey() {
return self::PROPERTY_CONSUMER_SECRET;
}
protected function configureAdapter(PhutilOAuth1AuthAdapter $adapter) {
$config = $this->getProviderConfig();
$adapter->setConsumerKey($config->getProperty(self::PROPERTY_CONSUMER_KEY));
$secret = $config->getProperty(self::PROPERTY_CONSUMER_SECRET);
- if (strlen($secret)) {
+ if (phutil_nonempty_string($secret)) {
$adapter->setConsumerSecret(new PhutilOpaqueEnvelope($secret));
}
$adapter->setCallbackURI(PhabricatorEnv::getURI($this->getLoginURI()));
return $adapter;
}
protected function renderLoginForm(AphrontRequest $request, $mode) {
$attributes = array(
'method' => 'POST',
'uri' => $this->getLoginURI(),
);
return $this->renderStandardLoginButton($request, $mode, $attributes);
}
public function processLoginRequest(
PhabricatorAuthLoginController $controller) {
$request = $controller->getRequest();
$adapter = $this->getAdapter();
$account = null;
$response = null;
if ($request->isHTTPPost()) {
// Add a CSRF code to the callback URI, which we'll verify when
// performing the login.
$client_code = $this->getAuthCSRFCode($request);
$callback_uri = $adapter->getCallbackURI();
$callback_uri = $callback_uri.$client_code.'/';
$adapter->setCallbackURI($callback_uri);
$uri = $adapter->getClientRedirectURI();
$this->saveHandshakeTokenSecret(
$client_code,
$adapter->getTokenSecret());
$response = id(new AphrontRedirectResponse())
->setIsExternal(true)
->setURI($uri);
return array($account, $response);
}
$denied = $request->getStr('denied');
if (strlen($denied)) {
// Twitter indicates that the user cancelled the login attempt by
// returning "denied" as a parameter.
throw new PhutilAuthUserAbortedException();
}
// NOTE: You can get here via GET, this should probably be a bit more
// user friendly.
$this->verifyAuthCSRFCode($request, $controller->getExtraURIData());
$token = $request->getStr('oauth_token');
$verifier = $request->getStr('oauth_verifier');
if (!$token) {
throw new Exception(pht("Expected '%s' in request!", 'oauth_token'));
}
if (!$verifier) {
throw new Exception(pht("Expected '%s' in request!", 'oauth_verifier'));
}
$adapter->setToken($token);
$adapter->setVerifier($verifier);
$client_code = $this->getAuthCSRFCode($request);
$token_secret = $this->loadHandshakeTokenSecret($client_code);
$adapter->setTokenSecret($token_secret);
// NOTE: As a side effect, this will cause the OAuth adapter to request
// an access token.
try {
$identifiers = $adapter->getAccountIdentifiers();
} catch (Exception $ex) {
// TODO: Handle this in a more user-friendly way.
throw $ex;
}
if (!$identifiers) {
$response = $controller->buildProviderErrorResponse(
$this,
pht(
'The OAuth provider failed to retrieve an account ID.'));
return array($account, $response);
}
$account = $this->newExternalAccountForIdentifiers($identifiers);
return array($account, $response);
}
public function processEditForm(
AphrontRequest $request,
array $values) {
$key_ckey = self::PROPERTY_CONSUMER_KEY;
$key_csecret = self::PROPERTY_CONSUMER_SECRET;
return $this->processOAuthEditForm(
$request,
$values,
pht('Consumer key is required.'),
pht('Consumer secret is required.'));
}
public function extendEditForm(
AphrontRequest $request,
AphrontFormView $form,
array $values,
array $issues) {
return $this->extendOAuthEditForm(
$request,
$form,
$values,
$issues,
pht('OAuth Consumer Key'),
pht('OAuth Consumer Secret'));
}
public function renderConfigPropertyTransactionTitle(
PhabricatorAuthProviderConfigTransaction $xaction) {
$author_phid = $xaction->getAuthorPHID();
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$key = $xaction->getMetadataValue(
PhabricatorAuthProviderConfigTransaction::PROPERTY_KEY);
switch ($key) {
case self::PROPERTY_CONSUMER_KEY:
if (strlen($old)) {
return pht(
'%s updated the OAuth consumer key for this provider from '.
'"%s" to "%s".',
$xaction->renderHandleLink($author_phid),
$old,
$new);
} else {
return pht(
'%s set the OAuth consumer key for this provider to '.
'"%s".',
$xaction->renderHandleLink($author_phid),
$new);
}
case self::PROPERTY_CONSUMER_SECRET:
if (strlen($old)) {
return pht(
'%s updated the OAuth consumer secret for this provider.',
$xaction->renderHandleLink($author_phid));
} else {
return pht(
'%s set the OAuth consumer secret for this provider.',
$xaction->renderHandleLink($author_phid));
}
}
return parent::renderConfigPropertyTransactionTitle($xaction);
}
protected function synchronizeOAuthAccount(
PhabricatorExternalAccount $account) {
$adapter = $this->getAdapter();
$oauth_token = $adapter->getToken();
$oauth_token_secret = $adapter->getTokenSecret();
$account->setProperty('oauth1.token', $oauth_token);
$account->setProperty('oauth1.token.secret', $oauth_token_secret);
}
public function willRenderLinkedAccount(
PhabricatorUser $viewer,
PHUIObjectItemView $item,
PhabricatorExternalAccount $account) {
$item->addAttribute(pht('OAuth1 Account'));
parent::willRenderLinkedAccount($viewer, $item, $account);
}
protected function getContentSecurityPolicyFormActions() {
return $this->getAdapter()->getContentSecurityPolicyFormActions();
}
/* -( Temporary Secrets )-------------------------------------------------- */
private function saveHandshakeTokenSecret($client_code, $secret) {
$secret_type = PhabricatorOAuth1SecretTemporaryTokenType::TOKENTYPE;
$key = $this->getHandshakeTokenKeyFromClientCode($client_code);
$type = $this->getTemporaryTokenType($secret_type);
// Wipe out an existing token, if one exists.
$token = id(new PhabricatorAuthTemporaryTokenQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withTokenResources(array($key))
->withTokenTypes(array($type))
->executeOne();
if ($token) {
$token->delete();
}
// Save the new secret.
id(new PhabricatorAuthTemporaryToken())
->setTokenResource($key)
->setTokenType($type)
->setTokenExpires(time() + phutil_units('1 hour in seconds'))
->setTokenCode($secret)
->save();
}
private function loadHandshakeTokenSecret($client_code) {
$secret_type = PhabricatorOAuth1SecretTemporaryTokenType::TOKENTYPE;
$key = $this->getHandshakeTokenKeyFromClientCode($client_code);
$type = $this->getTemporaryTokenType($secret_type);
$token = id(new PhabricatorAuthTemporaryTokenQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withTokenResources(array($key))
->withTokenTypes(array($type))
->withExpired(false)
->executeOne();
if (!$token) {
throw new Exception(
pht(
'Unable to load your OAuth1 token secret from storage. It may '.
'have expired. Try authenticating again.'));
}
return $token->getTokenCode();
}
private function getTemporaryTokenType($core_type) {
// Namespace the type so that multiple providers don't step on each
// others' toes if a user starts Mediawiki and Bitbucket auth at the
// same time.
// TODO: This isn't really a proper use of the table and should get
// cleaned up some day: the type should be constant.
return $core_type.':'.$this->getProviderConfig()->getID();
}
private function getHandshakeTokenKeyFromClientCode($client_code) {
// NOTE: This is very slightly coercive since the TemporaryToken table
// expects an "objectPHID" as an identifier, but nothing about the storage
// is bound to PHIDs.
return 'oauth1:secret/'.$client_code;
}
}
diff --git a/src/applications/auth/provider/PhabricatorPhabricatorAuthProvider.php b/src/applications/auth/provider/PhabricatorPhabricatorAuthProvider.php
index 8799c43c6f..4c1c31f146 100644
--- a/src/applications/auth/provider/PhabricatorPhabricatorAuthProvider.php
+++ b/src/applications/auth/provider/PhabricatorPhabricatorAuthProvider.php
@@ -1,209 +1,207 @@
<?php
final class PhabricatorPhabricatorAuthProvider
extends PhabricatorOAuth2AuthProvider {
const PROPERTY_PHABRICATOR_NAME = 'oauth2:phabricator:name';
const PROPERTY_PHABRICATOR_URI = 'oauth2:phabricator:uri';
public function getProviderName() {
- return pht('Phabricator');
+ return PlatformSymbols::getPlatformServerName();
}
public function getConfigurationHelp() {
if ($this->isCreate()) {
return pht(
- "**Step 1 of 2 - Name Phabricator OAuth Instance**\n\n".
- 'Choose a permanent name for the OAuth server instance of '.
- 'Phabricator. //This// instance of Phabricator uses this name '.
- 'internally to keep track of the OAuth server instance of '.
- 'Phabricator, in case the URL changes later.');
+ "**Step 1 of 2 - Name Remote Server**\n\n".
+ 'Choose a permanent name for the remote server you want to connect '.
+ 'to. This name is used internally to keep track of the remote '.
+ 'server, in case the URL changes later.');
}
return parent::getConfigurationHelp();
}
protected function getProviderConfigurationHelp() {
$config = $this->getProviderConfig();
$base_uri = rtrim(
$config->getProperty(self::PROPERTY_PHABRICATOR_URI), '/');
$login_uri = PhabricatorEnv::getURI($this->getLoginURI());
return pht(
- "**Step 2 of 2 - Configure Phabricator OAuth Instance**\n\n".
- "To configure Phabricator OAuth, create a new application here:".
+ "**Step 2 of 2 - Configure OAuth Server**\n\n".
+ "To configure OAuth, create a new application here:".
"\n\n".
"%s/oauthserver/client/create/".
"\n\n".
"When creating your application, use these settings:".
"\n\n".
" - **Redirect URI:** Set this to: `%s`".
"\n\n".
"After completing configuration, copy the **Client ID** and ".
"**Client Secret** to the fields above. (You may need to generate the ".
"client secret by clicking 'New Secret' first.)",
$base_uri,
$login_uri);
}
protected function newOAuthAdapter() {
$config = $this->getProviderConfig();
return id(new PhutilPhabricatorAuthAdapter())
->setAdapterDomain($config->getProviderDomain())
->setPhabricatorBaseURI(
$config->getProperty(self::PROPERTY_PHABRICATOR_URI));
}
protected function getLoginIcon() {
- return 'Phabricator';
+ return PlatformSymbols::getPlatformServerName();
}
private function isCreate() {
return !$this->getProviderConfig()->getID();
}
public function readFormValuesFromProvider() {
$config = $this->getProviderConfig();
$uri = $config->getProperty(self::PROPERTY_PHABRICATOR_URI);
return parent::readFormValuesFromProvider() + array(
self::PROPERTY_PHABRICATOR_NAME => $this->getProviderDomain(),
self::PROPERTY_PHABRICATOR_URI => $uri,
);
}
public function readFormValuesFromRequest(AphrontRequest $request) {
$is_setup = $this->isCreate();
if ($is_setup) {
$parent_values = array();
$name = $request->getStr(self::PROPERTY_PHABRICATOR_NAME);
} else {
$parent_values = parent::readFormValuesFromRequest($request);
$name = $this->getProviderDomain();
}
return $parent_values + array(
self::PROPERTY_PHABRICATOR_NAME => $name,
self::PROPERTY_PHABRICATOR_URI =>
$request->getStr(self::PROPERTY_PHABRICATOR_URI),
);
}
public function processEditForm(
AphrontRequest $request,
array $values) {
$is_setup = $this->isCreate();
if (!$is_setup) {
list($errors, $issues, $values) =
parent::processEditForm($request, $values);
} else {
$errors = array();
$issues = array();
}
$key_name = self::PROPERTY_PHABRICATOR_NAME;
$key_uri = self::PROPERTY_PHABRICATOR_URI;
if (!strlen($values[$key_name])) {
- $errors[] = pht('Phabricator instance name is required.');
+ $errors[] = pht('Server name is required.');
$issues[$key_name] = pht('Required');
} else if (!preg_match('/^[a-z0-9.]+\z/', $values[$key_name])) {
$errors[] = pht(
- 'Phabricator instance name must contain only lowercase letters, '.
+ 'Server name must contain only lowercase letters, '.
'digits, and periods.');
$issues[$key_name] = pht('Invalid');
}
if (!strlen($values[$key_uri])) {
- $errors[] = pht('Phabricator base URI is required.');
+ $errors[] = pht('Base URI is required.');
$issues[$key_uri] = pht('Required');
} else {
$uri = new PhutilURI($values[$key_uri]);
if (!$uri->getProtocol()) {
$errors[] = pht(
- 'Phabricator base URI should include protocol (like "%s").',
+ 'Base URI should include protocol (like "%s").',
'https://');
$issues[$key_uri] = pht('Invalid');
}
}
if (!$errors && $is_setup) {
$config = $this->getProviderConfig();
$config->setProviderDomain($values[$key_name]);
}
return array($errors, $issues, $values);
}
public function extendEditForm(
AphrontRequest $request,
AphrontFormView $form,
array $values,
array $issues) {
$is_setup = $this->isCreate();
$e_required = $request->isFormPost() ? null : true;
$v_name = $values[self::PROPERTY_PHABRICATOR_NAME];
if ($is_setup) {
$e_name = idx($issues, self::PROPERTY_PHABRICATOR_NAME, $e_required);
} else {
$e_name = null;
}
$v_uri = $values[self::PROPERTY_PHABRICATOR_URI];
$e_uri = idx($issues, self::PROPERTY_PHABRICATOR_URI, $e_required);
if ($is_setup) {
$form
->appendChild(
id(new AphrontFormTextControl())
- ->setLabel(pht('Phabricator Instance Name'))
+ ->setLabel(pht('Server Name'))
->setValue($v_name)
->setName(self::PROPERTY_PHABRICATOR_NAME)
->setError($e_name)
->setCaption(pht(
'Use lowercase letters, digits, and periods. For example: %s',
phutil_tag(
'tt',
array(),
- '`phabricator.oauthserver`'))));
+ '`example.oauthserver`'))));
} else {
$form
->appendChild(
id(new AphrontFormStaticControl())
- ->setLabel(pht('Phabricator Instance Name'))
+ ->setLabel(pht('Server Name'))
->setValue($v_name));
}
$form
->appendChild(
id(new AphrontFormTextControl())
- ->setLabel(pht('Phabricator Base URI'))
+ ->setLabel(pht('Base URI'))
->setValue($v_uri)
->setName(self::PROPERTY_PHABRICATOR_URI)
->setCaption(
pht(
- 'The URI where the OAuth server instance of Phabricator is '.
- 'installed. For example: %s',
- phutil_tag('tt', array(), 'https://phabricator.mycompany.com/')))
+ 'The URI where the OAuth server is installed. For example: %s',
+ phutil_tag('tt', array(), 'https://devtools.example.com/')))
->setError($e_uri));
if (!$is_setup) {
parent::extendEditForm($request, $form, $values, $issues);
}
}
public function hasSetupStep() {
return true;
}
public function getPhabricatorURI() {
$config = $this->getProviderConfig();
return $config->getProperty(self::PROPERTY_PHABRICATOR_URI);
}
}
diff --git a/src/applications/auth/provider/PhabricatorWordPressAuthProvider.php b/src/applications/auth/provider/PhabricatorWordPressAuthProvider.php
index 092915f58c..8189685f81 100644
--- a/src/applications/auth/provider/PhabricatorWordPressAuthProvider.php
+++ b/src/applications/auth/provider/PhabricatorWordPressAuthProvider.php
@@ -1,38 +1,38 @@
<?php
final class PhabricatorWordPressAuthProvider
extends PhabricatorOAuth2AuthProvider {
public function getProviderName() {
return pht('WordPress.com');
}
protected function getProviderConfigurationHelp() {
$uri = PhabricatorEnv::getProductionURI('/');
$callback_uri = PhabricatorEnv::getURI($this->getLoginURI());
return pht(
"To configure WordPress.com OAuth, create a new WordPress.com ".
"Application here:\n\n".
"https://developer.wordpress.com/apps/new/.".
"\n\n".
"You should use these settings in your application:".
"\n\n".
" - **URL:** Set this to your full domain with protocol. For this ".
- " Phabricator install, the correct value is: `%s`\n".
+ " server, the correct value is: `%s`\n".
" - **Redirect URL**: Set this to: `%s`\n".
"\n\n".
"Once you've created an application, copy the **Client ID** and ".
"**Client Secret** into the fields above.",
$uri,
$callback_uri);
}
protected function newOAuthAdapter() {
return new PhutilWordPressAuthAdapter();
}
protected function getLoginIcon() {
return 'WordPressCOM';
}
}
diff --git a/src/applications/auth/query/PhabricatorAuthChallengeQuery.php b/src/applications/auth/query/PhabricatorAuthChallengeQuery.php
index 195abe0884..c6abead11f 100644
--- a/src/applications/auth/query/PhabricatorAuthChallengeQuery.php
+++ b/src/applications/auth/query/PhabricatorAuthChallengeQuery.php
@@ -1,99 +1,95 @@
<?php
final class PhabricatorAuthChallengeQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $userPHIDs;
private $factorPHIDs;
private $challengeTTLMin;
private $challengeTTLMax;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withUserPHIDs(array $user_phids) {
$this->userPHIDs = $user_phids;
return $this;
}
public function withFactorPHIDs(array $factor_phids) {
$this->factorPHIDs = $factor_phids;
return $this;
}
public function withChallengeTTLBetween($challenge_min, $challenge_max) {
$this->challengeTTLMin = $challenge_min;
$this->challengeTTLMax = $challenge_max;
return $this;
}
public function newResultObject() {
return new PhabricatorAuthChallenge();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->userPHIDs !== null) {
$where[] = qsprintf(
$conn,
'userPHID IN (%Ls)',
$this->userPHIDs);
}
if ($this->factorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'factorPHID IN (%Ls)',
$this->factorPHIDs);
}
if ($this->challengeTTLMin !== null) {
$where[] = qsprintf(
$conn,
'challengeTTL >= %d',
$this->challengeTTLMin);
}
if ($this->challengeTTLMax !== null) {
$where[] = qsprintf(
$conn,
'challengeTTL <= %d',
$this->challengeTTLMax);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorAuthApplication';
}
}
diff --git a/src/applications/auth/query/PhabricatorAuthContactNumberQuery.php b/src/applications/auth/query/PhabricatorAuthContactNumberQuery.php
index 77b3b559dd..ba507c757b 100644
--- a/src/applications/auth/query/PhabricatorAuthContactNumberQuery.php
+++ b/src/applications/auth/query/PhabricatorAuthContactNumberQuery.php
@@ -1,103 +1,99 @@
<?php
final class PhabricatorAuthContactNumberQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $objectPHIDs;
private $statuses;
private $uniqueKeys;
private $isPrimary;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withObjectPHIDs(array $object_phids) {
$this->objectPHIDs = $object_phids;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function withUniqueKeys(array $unique_keys) {
$this->uniqueKeys = $unique_keys;
return $this;
}
public function withIsPrimary($is_primary) {
$this->isPrimary = $is_primary;
return $this;
}
public function newResultObject() {
return new PhabricatorAuthContactNumber();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->objectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'objectPHID IN (%Ls)',
$this->objectPHIDs);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'status IN (%Ls)',
$this->statuses);
}
if ($this->uniqueKeys !== null) {
$where[] = qsprintf(
$conn,
'uniqueKey IN (%Ls)',
$this->uniqueKeys);
}
if ($this->isPrimary !== null) {
$where[] = qsprintf(
$conn,
'isPrimary = %d',
(int)$this->isPrimary);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorAuthApplication';
}
}
diff --git a/src/applications/auth/query/PhabricatorAuthFactorConfigQuery.php b/src/applications/auth/query/PhabricatorAuthFactorConfigQuery.php
index 5f838f66ba..dd73d0b081 100644
--- a/src/applications/auth/query/PhabricatorAuthFactorConfigQuery.php
+++ b/src/applications/auth/query/PhabricatorAuthFactorConfigQuery.php
@@ -1,131 +1,127 @@
<?php
final class PhabricatorAuthFactorConfigQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $userPHIDs;
private $factorProviderPHIDs;
private $factorProviderStatuses;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withUserPHIDs(array $user_phids) {
$this->userPHIDs = $user_phids;
return $this;
}
public function withFactorProviderPHIDs(array $provider_phids) {
$this->factorProviderPHIDs = $provider_phids;
return $this;
}
public function withFactorProviderStatuses(array $statuses) {
$this->factorProviderStatuses = $statuses;
return $this;
}
public function newResultObject() {
return new PhabricatorAuthFactorConfig();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'config.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'config.phid IN (%Ls)',
$this->phids);
}
if ($this->userPHIDs !== null) {
$where[] = qsprintf(
$conn,
'config.userPHID IN (%Ls)',
$this->userPHIDs);
}
if ($this->factorProviderPHIDs !== null) {
$where[] = qsprintf(
$conn,
'config.factorProviderPHID IN (%Ls)',
$this->factorProviderPHIDs);
}
if ($this->factorProviderStatuses !== null) {
$where[] = qsprintf(
$conn,
'provider.status IN (%Ls)',
$this->factorProviderStatuses);
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->factorProviderStatuses !== null) {
$joins[] = qsprintf(
$conn,
'JOIN %R provider ON config.factorProviderPHID = provider.phid',
new PhabricatorAuthFactorProvider());
}
return $joins;
}
protected function willFilterPage(array $configs) {
$provider_phids = mpull($configs, 'getFactorProviderPHID');
$providers = id(new PhabricatorAuthFactorProviderQuery())
->setViewer($this->getViewer())
->withPHIDs($provider_phids)
->execute();
$providers = mpull($providers, null, 'getPHID');
foreach ($configs as $key => $config) {
$provider = idx($providers, $config->getFactorProviderPHID());
if (!$provider) {
unset($configs[$key]);
$this->didRejectResult($config);
continue;
}
$config->attachFactorProvider($provider);
}
return $configs;
}
protected function getPrimaryTableAlias() {
return 'config';
}
public function getQueryApplicationClass() {
return 'PhabricatorAuthApplication';
}
}
diff --git a/src/applications/auth/query/PhabricatorAuthFactorProviderQuery.php b/src/applications/auth/query/PhabricatorAuthFactorProviderQuery.php
index 57b554885c..7083545e3e 100644
--- a/src/applications/auth/query/PhabricatorAuthFactorProviderQuery.php
+++ b/src/applications/auth/query/PhabricatorAuthFactorProviderQuery.php
@@ -1,94 +1,90 @@
<?php
final class PhabricatorAuthFactorProviderQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $statuses;
private $providerFactorKeys;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withProviderFactorKeys(array $keys) {
$this->providerFactorKeys = $keys;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function newResultObject() {
return new PhabricatorAuthFactorProvider();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'status IN (%Ls)',
$this->statuses);
}
if ($this->providerFactorKeys !== null) {
$where[] = qsprintf(
$conn,
'providerFactorKey IN (%Ls)',
$this->providerFactorKeys);
}
return $where;
}
protected function willFilterPage(array $providers) {
$map = PhabricatorAuthFactor::getAllFactors();
foreach ($providers as $key => $provider) {
$factor_key = $provider->getProviderFactorKey();
$factor = idx($map, $factor_key);
if (!$factor) {
unset($providers[$key]);
continue;
}
$provider->attachFactor($factor);
}
return $providers;
}
public function getQueryApplicationClass() {
return 'PhabricatorAuthApplication';
}
}
diff --git a/src/applications/auth/query/PhabricatorAuthMessageQuery.php b/src/applications/auth/query/PhabricatorAuthMessageQuery.php
index 384c8de23b..7158d03a00 100644
--- a/src/applications/auth/query/PhabricatorAuthMessageQuery.php
+++ b/src/applications/auth/query/PhabricatorAuthMessageQuery.php
@@ -1,83 +1,79 @@
<?php
final class PhabricatorAuthMessageQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $messageKeys;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withMessageKeys(array $keys) {
$this->messageKeys = $keys;
return $this;
}
public function newResultObject() {
return new PhabricatorAuthMessage();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->messageKeys !== null) {
$where[] = qsprintf(
$conn,
'messageKey IN (%Ls)',
$this->messageKeys);
}
return $where;
}
protected function willFilterPage(array $messages) {
$message_types = PhabricatorAuthMessageType::getAllMessageTypes();
foreach ($messages as $key => $message) {
$message_key = $message->getMessageKey();
$message_type = idx($message_types, $message_key);
if (!$message_type) {
unset($messages[$key]);
$this->didRejectResult($message);
continue;
}
$message->attachMessageType($message_type);
}
return $messages;
}
public function getQueryApplicationClass() {
return 'PhabricatorAuthApplication';
}
}
diff --git a/src/applications/auth/query/PhabricatorAuthPasswordQuery.php b/src/applications/auth/query/PhabricatorAuthPasswordQuery.php
index 483936fb30..a77fd54b13 100644
--- a/src/applications/auth/query/PhabricatorAuthPasswordQuery.php
+++ b/src/applications/auth/query/PhabricatorAuthPasswordQuery.php
@@ -1,114 +1,110 @@
<?php
final class PhabricatorAuthPasswordQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $objectPHIDs;
private $passwordTypes;
private $isRevoked;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withObjectPHIDs(array $object_phids) {
$this->objectPHIDs = $object_phids;
return $this;
}
public function withPasswordTypes(array $types) {
$this->passwordTypes = $types;
return $this;
}
public function withIsRevoked($is_revoked) {
$this->isRevoked = $is_revoked;
return $this;
}
public function newResultObject() {
return new PhabricatorAuthPassword();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->objectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'objectPHID IN (%Ls)',
$this->objectPHIDs);
}
if ($this->passwordTypes !== null) {
$where[] = qsprintf(
$conn,
'passwordType IN (%Ls)',
$this->passwordTypes);
}
if ($this->isRevoked !== null) {
$where[] = qsprintf(
$conn,
'isRevoked = %d',
(int)$this->isRevoked);
}
return $where;
}
protected function willFilterPage(array $passwords) {
$object_phids = mpull($passwords, 'getObjectPHID');
$objects = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($object_phids)
->execute();
$objects = mpull($objects, null, 'getPHID');
foreach ($passwords as $key => $password) {
$object = idx($objects, $password->getObjectPHID());
if (!$object) {
unset($passwords[$key]);
$this->didRejectResult($password);
continue;
}
$password->attachObject($object);
}
return $passwords;
}
public function getQueryApplicationClass() {
return 'PhabricatorAuthApplication';
}
}
diff --git a/src/applications/auth/query/PhabricatorAuthProviderConfigQuery.php b/src/applications/auth/query/PhabricatorAuthProviderConfigQuery.php
index ee073e3ac1..30e5dad113 100644
--- a/src/applications/auth/query/PhabricatorAuthProviderConfigQuery.php
+++ b/src/applications/auth/query/PhabricatorAuthProviderConfigQuery.php
@@ -1,90 +1,86 @@
<?php
final class PhabricatorAuthProviderConfigQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $providerClasses;
private $isEnabled;
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withProviderClasses(array $classes) {
$this->providerClasses = $classes;
return $this;
}
public function withIsEnabled($is_enabled) {
$this->isEnabled = $is_enabled;
return $this;
}
public function newResultObject() {
return new PhabricatorAuthProviderConfig();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->providerClasses !== null) {
$where[] = qsprintf(
$conn,
'providerClass IN (%Ls)',
$this->providerClasses);
}
if ($this->isEnabled !== null) {
$where[] = qsprintf(
$conn,
'isEnabled = %d',
(int)$this->isEnabled);
}
return $where;
}
protected function willFilterPage(array $configs) {
foreach ($configs as $key => $config) {
$provider = $config->getProvider();
if (!$provider) {
unset($configs[$key]);
continue;
}
}
return $configs;
}
public function getQueryApplicationClass() {
return 'PhabricatorAuthApplication';
}
}
diff --git a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php
index 3a310ed173..7d4aac4a01 100644
--- a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php
+++ b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php
@@ -1,138 +1,134 @@
<?php
final class PhabricatorAuthSSHKeyQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
const AUTHSTRUCT_CACHEKEY = 'ssh.authstruct';
private $ids;
private $phids;
private $objectPHIDs;
private $keys;
private $isActive;
public static function deleteSSHKeyCache() {
$cache = PhabricatorCaches::getMutableCache();
$authfile_key = self::AUTHSTRUCT_CACHEKEY;
$cache->deleteKey($authfile_key);
}
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withObjectPHIDs(array $object_phids) {
$this->objectPHIDs = $object_phids;
return $this;
}
public function withKeys(array $keys) {
assert_instances_of($keys, 'PhabricatorAuthSSHPublicKey');
$this->keys = $keys;
return $this;
}
public function withIsActive($active) {
$this->isActive = $active;
return $this;
}
public function newResultObject() {
return new PhabricatorAuthSSHKey();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $keys) {
$object_phids = mpull($keys, 'getObjectPHID');
$objects = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($object_phids)
->execute();
$objects = mpull($objects, null, 'getPHID');
foreach ($keys as $key => $ssh_key) {
$object = idx($objects, $ssh_key->getObjectPHID());
// We must have an object, and that object must be a valid object for
// SSH keys.
if (!$object || !($object instanceof PhabricatorSSHPublicKeyInterface)) {
$this->didRejectResult($ssh_key);
unset($keys[$key]);
continue;
}
$ssh_key->attachObject($object);
}
return $keys;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->objectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'objectPHID IN (%Ls)',
$this->objectPHIDs);
}
if ($this->keys !== null) {
$sql = array();
foreach ($this->keys as $key) {
$sql[] = qsprintf(
$conn,
'(keyType = %s AND keyIndex = %s)',
$key->getType(),
$key->getHash());
}
$where[] = qsprintf($conn, '%LO', $sql);
}
if ($this->isActive !== null) {
if ($this->isActive) {
$where[] = qsprintf(
$conn,
'isActive = %d',
1);
} else {
$where[] = qsprintf(
$conn,
'isActive IS NULL');
}
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorAuthApplication';
}
}
diff --git a/src/applications/auth/query/PhabricatorAuthSessionQuery.php b/src/applications/auth/query/PhabricatorAuthSessionQuery.php
index 00a663e964..4bc7eba73f 100644
--- a/src/applications/auth/query/PhabricatorAuthSessionQuery.php
+++ b/src/applications/auth/query/PhabricatorAuthSessionQuery.php
@@ -1,117 +1,113 @@
<?php
final class PhabricatorAuthSessionQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $identityPHIDs;
private $sessionKeys;
private $sessionTypes;
public function withIdentityPHIDs(array $identity_phids) {
$this->identityPHIDs = $identity_phids;
return $this;
}
public function withSessionKeys(array $keys) {
$this->sessionKeys = $keys;
return $this;
}
public function withSessionTypes(array $types) {
$this->sessionTypes = $types;
return $this;
}
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function newResultObject() {
return new PhabricatorAuthSession();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $sessions) {
$identity_phids = mpull($sessions, 'getUserPHID');
$identity_objects = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($identity_phids)
->execute();
$identity_objects = mpull($identity_objects, null, 'getPHID');
foreach ($sessions as $key => $session) {
$identity_object = idx($identity_objects, $session->getUserPHID());
if (!$identity_object) {
unset($sessions[$key]);
} else {
$session->attachIdentityObject($identity_object);
}
}
return $sessions;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->identityPHIDs !== null) {
$where[] = qsprintf(
$conn,
'userPHID IN (%Ls)',
$this->identityPHIDs);
}
if ($this->sessionKeys !== null) {
$hashes = array();
foreach ($this->sessionKeys as $session_key) {
$hashes[] = PhabricatorAuthSession::newSessionDigest(
new PhutilOpaqueEnvelope($session_key));
}
$where[] = qsprintf(
$conn,
'sessionKey IN (%Ls)',
$hashes);
}
if ($this->sessionTypes !== null) {
$where[] = qsprintf(
$conn,
'type IN (%Ls)',
$this->sessionTypes);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorAuthApplication';
}
}
diff --git a/src/applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php b/src/applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php
index 72141f75f0..c5cb39096c 100644
--- a/src/applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php
+++ b/src/applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php
@@ -1,110 +1,106 @@
<?php
final class PhabricatorAuthTemporaryTokenQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $tokenResources;
private $tokenTypes;
private $userPHIDs;
private $expired;
private $tokenCodes;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withTokenResources(array $resources) {
$this->tokenResources = $resources;
return $this;
}
public function withTokenTypes(array $types) {
$this->tokenTypes = $types;
return $this;
}
public function withExpired($expired) {
$this->expired = $expired;
return $this;
}
public function withTokenCodes(array $codes) {
$this->tokenCodes = $codes;
return $this;
}
public function withUserPHIDs(array $phids) {
$this->userPHIDs = $phids;
return $this;
}
public function newResultObject() {
return new PhabricatorAuthTemporaryToken();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->tokenResources !== null) {
$where[] = qsprintf(
$conn,
'tokenResource IN (%Ls)',
$this->tokenResources);
}
if ($this->tokenTypes !== null) {
$where[] = qsprintf(
$conn,
'tokenType IN (%Ls)',
$this->tokenTypes);
}
if ($this->expired !== null) {
if ($this->expired) {
$where[] = qsprintf(
$conn,
'tokenExpires <= %d',
time());
} else {
$where[] = qsprintf(
$conn,
'tokenExpires > %d',
time());
}
}
if ($this->tokenCodes !== null) {
$where[] = qsprintf(
$conn,
'tokenCode IN (%Ls)',
$this->tokenCodes);
}
if ($this->userPHIDs !== null) {
$where[] = qsprintf(
$conn,
'userPHID IN (%Ls)',
$this->userPHIDs);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorAuthApplication';
}
}
diff --git a/src/applications/auth/query/PhabricatorExternalAccountIdentifierQuery.php b/src/applications/auth/query/PhabricatorExternalAccountIdentifierQuery.php
index c44e0aed4f..b5c5b6eaa2 100644
--- a/src/applications/auth/query/PhabricatorExternalAccountIdentifierQuery.php
+++ b/src/applications/auth/query/PhabricatorExternalAccountIdentifierQuery.php
@@ -1,94 +1,90 @@
<?php
final class PhabricatorExternalAccountIdentifierQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $providerConfigPHIDs;
private $externalAccountPHIDs;
private $rawIdentifiers;
public function withIDs($ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withProviderConfigPHIDs(array $phids) {
$this->providerConfigPHIDs = $phids;
return $this;
}
public function withExternalAccountPHIDs(array $phids) {
$this->externalAccountPHIDs = $phids;
return $this;
}
public function withRawIdentifiers(array $identifiers) {
$this->rawIdentifiers = $identifiers;
return $this;
}
public function newResultObject() {
return new PhabricatorExternalAccountIdentifier();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->providerConfigPHIDs !== null) {
$where[] = qsprintf(
$conn,
'providerConfigPHID IN (%Ls)',
$this->providerConfigPHIDs);
}
if ($this->externalAccountPHIDs !== null) {
$where[] = qsprintf(
$conn,
'externalAccountPHID IN (%Ls)',
$this->externalAccountPHIDs);
}
if ($this->rawIdentifiers !== null) {
$hashes = array();
foreach ($this->rawIdentifiers as $raw_identifier) {
$hashes[] = PhabricatorHash::digestForIndex($raw_identifier);
}
$where[] = qsprintf(
$conn,
'identifierHash IN (%Ls)',
$hashes);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorPeopleApplication';
}
}
diff --git a/src/applications/auth/query/PhabricatorExternalAccountQuery.php b/src/applications/auth/query/PhabricatorExternalAccountQuery.php
index bdc030f20c..f44821d7a9 100644
--- a/src/applications/auth/query/PhabricatorExternalAccountQuery.php
+++ b/src/applications/auth/query/PhabricatorExternalAccountQuery.php
@@ -1,251 +1,247 @@
<?php
/**
* NOTE: When loading ExternalAccounts for use in an authentication context
* (that is, you're going to act as the account or link identities or anything
* like that) you should require CAN_EDIT capability even if you aren't actually
* editing the ExternalAccount.
*
* ExternalAccounts have a permissive CAN_VIEW policy (like users) because they
* interact directly with objects and can leave comments, sign documents, etc.
* However, CAN_EDIT is restricted to users who own the accounts.
*/
final class PhabricatorExternalAccountQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $userPHIDs;
private $needImages;
private $accountSecrets;
private $providerConfigPHIDs;
private $needAccountIdentifiers;
private $rawAccountIdentifiers;
public function withUserPHIDs(array $user_phids) {
$this->userPHIDs = $user_phids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withIDs($ids) {
$this->ids = $ids;
return $this;
}
public function withAccountSecrets(array $secrets) {
$this->accountSecrets = $secrets;
return $this;
}
public function needImages($need) {
$this->needImages = $need;
return $this;
}
public function needAccountIdentifiers($need) {
$this->needAccountIdentifiers = $need;
return $this;
}
public function withProviderConfigPHIDs(array $phids) {
$this->providerConfigPHIDs = $phids;
return $this;
}
public function withRawAccountIdentifiers(array $identifiers) {
$this->rawAccountIdentifiers = $identifiers;
return $this;
}
public function newResultObject() {
return new PhabricatorExternalAccount();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $accounts) {
$viewer = $this->getViewer();
$configs = id(new PhabricatorAuthProviderConfigQuery())
->setViewer($viewer)
->withPHIDs(mpull($accounts, 'getProviderConfigPHID'))
->execute();
$configs = mpull($configs, null, 'getPHID');
foreach ($accounts as $key => $account) {
$config_phid = $account->getProviderConfigPHID();
$config = idx($configs, $config_phid);
if (!$config) {
unset($accounts[$key]);
continue;
}
$account->attachProviderConfig($config);
}
if ($this->needImages) {
$file_phids = mpull($accounts, 'getProfileImagePHID');
$file_phids = array_filter($file_phids);
if ($file_phids) {
// NOTE: We use the omnipotent viewer here because these files are
// usually created during registration and can't be associated with
// the correct policies, since the relevant user account does not exist
// yet. In effect, if you can see an ExternalAccount, you can see its
// profile image.
$files = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs($file_phids)
->execute();
$files = mpull($files, null, 'getPHID');
} else {
$files = array();
}
$default_file = null;
foreach ($accounts as $account) {
$image_phid = $account->getProfileImagePHID();
if ($image_phid && isset($files[$image_phid])) {
$account->attachProfileImageFile($files[$image_phid]);
} else {
if ($default_file === null) {
$default_file = PhabricatorFile::loadBuiltin(
$this->getViewer(),
'profile.png');
}
$account->attachProfileImageFile($default_file);
}
}
}
if ($this->needAccountIdentifiers) {
$account_phids = mpull($accounts, 'getPHID');
$identifiers = id(new PhabricatorExternalAccountIdentifierQuery())
->setViewer($viewer)
->setParentQuery($this)
->withExternalAccountPHIDs($account_phids)
->execute();
$identifiers = mgroup($identifiers, 'getExternalAccountPHID');
foreach ($accounts as $account) {
$account_phid = $account->getPHID();
$account_identifiers = idx($identifiers, $account_phid, array());
$account->attachAccountIdentifiers($account_identifiers);
}
}
return $accounts;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'account.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'account.phid IN (%Ls)',
$this->phids);
}
if ($this->userPHIDs !== null) {
$where[] = qsprintf(
$conn,
'account.userPHID IN (%Ls)',
$this->userPHIDs);
}
if ($this->accountSecrets !== null) {
$where[] = qsprintf(
$conn,
'account.accountSecret IN (%Ls)',
$this->accountSecrets);
}
if ($this->providerConfigPHIDs !== null) {
$where[] = qsprintf(
$conn,
'account.providerConfigPHID IN (%Ls)',
$this->providerConfigPHIDs);
// If we have a list of ProviderConfig PHIDs and are joining the
// identifiers table, also include the list as an additional constraint
// on the identifiers table.
// This does not change the query results (an Account and its
// Identifiers always have the same ProviderConfig PHID) but it allows
// us to use keys on the Identifier table more efficiently.
if ($this->shouldJoinIdentifiersTable()) {
$where[] = qsprintf(
$conn,
'identifier.providerConfigPHID IN (%Ls)',
$this->providerConfigPHIDs);
}
}
if ($this->rawAccountIdentifiers !== null) {
$hashes = array();
foreach ($this->rawAccountIdentifiers as $raw_identifier) {
$hashes[] = PhabricatorHash::digestForIndex($raw_identifier);
}
$where[] = qsprintf(
$conn,
'identifier.identifierHash IN (%Ls)',
$hashes);
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->shouldJoinIdentifiersTable()) {
$joins[] = qsprintf(
$conn,
'JOIN %R identifier ON account.phid = identifier.externalAccountPHID',
new PhabricatorExternalAccountIdentifier());
}
return $joins;
}
protected function shouldJoinIdentifiersTable() {
return ($this->rawAccountIdentifiers !== null);
}
protected function shouldGroupQueryResultRows() {
if ($this->shouldJoinIdentifiersTable()) {
return true;
}
return parent::shouldGroupQueryResultRows();
}
protected function getPrimaryTableAlias() {
return 'account';
}
public function getQueryApplicationClass() {
return 'PhabricatorPeopleApplication';
}
}
diff --git a/src/applications/auth/worker/PhabricatorAuthInviteWorker.php b/src/applications/auth/worker/PhabricatorAuthInviteWorker.php
index 7e9e29be50..25ad6961f2 100644
--- a/src/applications/auth/worker/PhabricatorAuthInviteWorker.php
+++ b/src/applications/auth/worker/PhabricatorAuthInviteWorker.php
@@ -1,60 +1,62 @@
<?php
final class PhabricatorAuthInviteWorker
extends PhabricatorWorker {
protected function doWork() {
$data = $this->getTaskData();
$viewer = PhabricatorUser::getOmnipotentUser();
$address = idx($data, 'address');
$author_phid = idx($data, 'authorPHID');
$author = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withPHIDs(array($author_phid))
->executeOne();
if (!$author) {
throw new PhabricatorWorkerPermanentFailureException(
pht('Invite has invalid author PHID ("%s").', $author_phid));
}
$invite = id(new PhabricatorAuthInviteQuery())
->setViewer($viewer)
->withEmailAddresses(array($address))
->executeOne();
if ($invite) {
// If we're inviting a user who has already been invited, we just
// regenerate their invite code.
$invite->regenerateVerificationCode();
} else {
// Otherwise, we're creating a new invite.
$invite = id(new PhabricatorAuthInvite())
->setEmailAddress($address);
}
// Whether this is a new invite or not, tag this most recent author as
// the invite author.
$invite->setAuthorPHID($author_phid);
$code = $invite->getVerificationCode();
$invite_uri = '/auth/invite/'.$code.'/';
$invite_uri = PhabricatorEnv::getProductionURI($invite_uri);
$template = idx($data, 'template');
$template = str_replace('{$INVITE_URI}', $invite_uri, $template);
$invite->save();
$mail = id(new PhabricatorMetaMTAMail())
->addRawTos(array($invite->getEmailAddress()))
->setForceDelivery(true)
->setSubject(
pht(
- '[Phabricator] %s has invited you to join Phabricator',
- $author->getFullName()))
+ '[%s] %s has invited you to join %s',
+ PlatformSymbols::getPlatformServerName(),
+ $author->getFullName(),
+ PlatformSymbols::getPlatformServerName()))
->setBody($template)
->saveAndSend();
}
}
diff --git a/src/applications/badges/query/PhabricatorBadgesAwardQuery.php b/src/applications/badges/query/PhabricatorBadgesAwardQuery.php
index 347462de4d..57e53a5a30 100644
--- a/src/applications/badges/query/PhabricatorBadgesAwardQuery.php
+++ b/src/applications/badges/query/PhabricatorBadgesAwardQuery.php
@@ -1,125 +1,121 @@
<?php
final class PhabricatorBadgesAwardQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $badgePHIDs;
private $recipientPHIDs;
private $awarderPHIDs;
private $badgeStatuses = null;
protected function willFilterPage(array $awards) {
$badge_phids = array();
foreach ($awards as $key => $award) {
$badge_phids[] = $award->getBadgePHID();
}
$badges = id(new PhabricatorBadgesQuery())
->setViewer($this->getViewer())
->withPHIDs($badge_phids)
->execute();
$badges = mpull($badges, null, 'getPHID');
foreach ($awards as $key => $award) {
$award_badge = idx($badges, $award->getBadgePHID());
if (!$award_badge) {
unset($awards[$key]);
$this->didRejectResult($award);
continue;
}
$award->attachBadge($award_badge);
}
return $awards;
}
public function withBadgePHIDs(array $phids) {
$this->badgePHIDs = $phids;
return $this;
}
public function withRecipientPHIDs(array $phids) {
$this->recipientPHIDs = $phids;
return $this;
}
public function withAwarderPHIDs(array $phids) {
$this->awarderPHIDs = $phids;
return $this;
}
public function withBadgeStatuses(array $statuses) {
$this->badgeStatuses = $statuses;
return $this;
}
private function shouldJoinBadge() {
return (bool)$this->badgeStatuses;
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
public function newResultObject() {
return new PhabricatorBadgesAward();
}
protected function getPrimaryTableAlias() {
return 'badges_award';
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->badgePHIDs !== null) {
$where[] = qsprintf(
$conn,
'badges_award.badgePHID IN (%Ls)',
$this->badgePHIDs);
}
if ($this->recipientPHIDs !== null) {
$where[] = qsprintf(
$conn,
'badges_award.recipientPHID IN (%Ls)',
$this->recipientPHIDs);
}
if ($this->awarderPHIDs !== null) {
$where[] = qsprintf(
$conn,
'badges_award.awarderPHID IN (%Ls)',
$this->awarderPHIDs);
}
if ($this->badgeStatuses !== null) {
$where[] = qsprintf(
$conn,
'badges_badge.status IN (%Ls)',
$this->badgeStatuses);
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$join = parent::buildJoinClauseParts($conn);
$badges = new PhabricatorBadgesBadge();
if ($this->shouldJoinBadge()) {
$join[] = qsprintf(
$conn,
'JOIN %T badges_badge ON badges_award.badgePHID = badges_badge.phid',
$badges->getTableName());
}
return $join;
}
public function getQueryApplicationClass() {
return 'PhabricatorBadgesApplication';
}
}
diff --git a/src/applications/badges/query/PhabricatorBadgesQuery.php b/src/applications/badges/query/PhabricatorBadgesQuery.php
index dcadf881fe..cc59465f67 100644
--- a/src/applications/badges/query/PhabricatorBadgesQuery.php
+++ b/src/applications/badges/query/PhabricatorBadgesQuery.php
@@ -1,119 +1,115 @@
<?php
final class PhabricatorBadgesQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $qualities;
private $statuses;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withQualities(array $qualities) {
$this->qualities = $qualities;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint(
id(new PhabricatorBadgesBadgeNameNgrams()),
$ngrams);
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function getPrimaryTableAlias() {
return 'badges';
}
public function newResultObject() {
return new PhabricatorBadgesBadge();
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'badges.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'badges.phid IN (%Ls)',
$this->phids);
}
if ($this->qualities !== null) {
$where[] = qsprintf(
$conn,
'badges.quality IN (%Ls)',
$this->qualities);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'badges.status IN (%Ls)',
$this->statuses);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorBadgesApplication';
}
public function getBuiltinOrders() {
return array(
'quality' => array(
'vector' => array('quality', 'id'),
'name' => pht('Rarity (Rarest First)'),
),
'shoddiness' => array(
'vector' => array('-quality', '-id'),
'name' => pht('Rarity (Most Common First)'),
),
) + parent::getBuiltinOrders();
}
public function getOrderableColumns() {
return array(
'quality' => array(
'table' => $this->getPrimaryTableAlias(),
'column' => 'quality',
'reverse' => true,
'type' => 'int',
),
) + parent::getOrderableColumns();
}
protected function newPagingMapFromPartialObject($object) {
return array(
'id' => (int)$object->getID(),
'quality' => $object->getQuality(),
);
}
}
diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php
index 6c5e424bdd..ba1edc26d3 100644
--- a/src/applications/base/PhabricatorApplication.php
+++ b/src/applications/base/PhabricatorApplication.php
@@ -1,655 +1,655 @@
<?php
/**
* @task info Application Information
* @task ui UI Integration
* @task uri URI Routing
* @task mail Email integration
* @task fact Fact Integration
* @task meta Application Management
*/
abstract class PhabricatorApplication
extends PhabricatorLiskDAO
implements
PhabricatorPolicyInterface,
PhabricatorApplicationTransactionInterface {
const GROUP_CORE = 'core';
const GROUP_UTILITIES = 'util';
const GROUP_ADMIN = 'admin';
const GROUP_DEVELOPER = 'developer';
final public static function getApplicationGroups() {
return array(
self::GROUP_CORE => pht('Core Applications'),
self::GROUP_UTILITIES => pht('Utilities'),
self::GROUP_ADMIN => pht('Administration'),
self::GROUP_DEVELOPER => pht('Developer Tools'),
);
}
final public function getApplicationName() {
return 'application';
}
final public function getTableName() {
return 'application_application';
}
final protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
) + parent::getConfiguration();
}
final public function generatePHID() {
return $this->getPHID();
}
final public function save() {
// When "save()" is called on applications, we just return without
// actually writing anything to the database.
return $this;
}
/* -( Application Information )-------------------------------------------- */
abstract public function getName();
public function getShortDescription() {
return pht('%s Application', $this->getName());
}
final public function isInstalled() {
if (!$this->canUninstall()) {
return true;
}
$prototypes = PhabricatorEnv::getEnvConfig('phabricator.show-prototypes');
if (!$prototypes && $this->isPrototype()) {
return false;
}
$uninstalled = PhabricatorEnv::getEnvConfig(
'phabricator.uninstalled-applications');
return empty($uninstalled[get_class($this)]);
}
public function isPrototype() {
return false;
}
/**
* Return `true` if this application should never appear in application lists
* in the UI. Primarily intended for unit test applications or other
* pseudo-applications.
*
* Few applications should be unlisted. For most applications, use
* @{method:isLaunchable} to hide them from main launch views instead.
*
* @return bool True to remove application from UI lists.
*/
public function isUnlisted() {
return false;
}
/**
* Return `true` if this application is a normal application with a base
* URI and a web interface.
*
* Launchable applications can be pinned to the home page, and show up in the
* "Launcher" view of the Applications application. Making an application
* unlaunchable prevents pinning and hides it from this view.
*
* Usually, an application should be marked unlaunchable if:
*
* - it is available on every page anyway (like search); or
* - it does not have a web interface (like subscriptions); or
* - it is still pre-release and being intentionally buried.
*
* To hide applications more completely, use @{method:isUnlisted}.
*
* @return bool True if the application is launchable.
*/
public function isLaunchable() {
return true;
}
/**
* Return `true` if this application should be pinned by default.
*
* Users who have not yet set preferences see a default list of applications.
*
* @param PhabricatorUser User viewing the pinned application list.
* @return bool True if this application should be pinned by default.
*/
public function isPinnedByDefault(PhabricatorUser $viewer) {
return false;
}
/**
* Returns true if an application is first-party and false otherwise.
*
* @return bool True if this application is first-party.
*/
final public function isFirstParty() {
$where = id(new ReflectionClass($this))->getFileName();
$root = phutil_get_library_root('phabricator');
if (!Filesystem::isDescendant($where, $root)) {
return false;
}
if (Filesystem::isDescendant($where, $root.'/extensions')) {
return false;
}
return true;
}
public function canUninstall() {
return true;
}
final public function getPHID() {
return 'PHID-APPS-'.get_class($this);
}
public function getTypeaheadURI() {
return $this->isLaunchable() ? $this->getBaseURI() : null;
}
public function getBaseURI() {
return null;
}
final public function getApplicationURI($path = '') {
return $this->getBaseURI().ltrim($path, '/');
}
public function getIcon() {
return 'fa-puzzle-piece';
}
public function getApplicationOrder() {
return PHP_INT_MAX;
}
public function getApplicationGroup() {
return self::GROUP_CORE;
}
public function getTitleGlyph() {
return null;
}
final public function getHelpMenuItems(PhabricatorUser $viewer) {
$items = array();
$articles = $this->getHelpDocumentationArticles($viewer);
if ($articles) {
foreach ($articles as $article) {
$item = id(new PhabricatorActionView())
->setName($article['name'])
->setHref($article['href'])
->addSigil('help-item')
->setOpenInNewWindow(true);
$items[] = $item;
}
}
$command_specs = $this->getMailCommandObjects();
if ($command_specs) {
foreach ($command_specs as $key => $spec) {
$object = $spec['object'];
$class = get_class($this);
$href = '/applications/mailcommands/'.$class.'/'.$key.'/';
$item = id(new PhabricatorActionView())
->setName($spec['name'])
->setHref($href)
->addSigil('help-item')
->setOpenInNewWindow(true);
$items[] = $item;
}
}
if ($items) {
$divider = id(new PhabricatorActionView())
->addSigil('help-item')
->setType(PhabricatorActionView::TYPE_DIVIDER);
array_unshift($items, $divider);
}
return array_values($items);
}
public function getHelpDocumentationArticles(PhabricatorUser $viewer) {
return array();
}
public function getOverview() {
return null;
}
public function getEventListeners() {
return array();
}
public function getRemarkupRules() {
return array();
}
public function getQuicksandURIPatternBlacklist() {
return array();
}
public function getMailCommandObjects() {
return array();
}
/* -( URI Routing )-------------------------------------------------------- */
public function getRoutes() {
return array();
}
public function getResourceRoutes() {
return array();
}
/* -( Email Integration )-------------------------------------------------- */
public function supportsEmailIntegration() {
return false;
}
final protected function getInboundEmailSupportLink() {
return PhabricatorEnv::getDoclink('Configuring Inbound Email');
}
public function getAppEmailBlurb() {
throw new PhutilMethodNotImplementedException();
}
/* -( Fact Integration )--------------------------------------------------- */
public function getFactObjectsForAnalysis() {
return array();
}
/* -( UI Integration )----------------------------------------------------- */
/**
* You can provide an optional piece of flavor text for the application. This
* is currently rendered in application launch views if the application has no
* status elements.
*
* @return string|null Flavor text.
* @task ui
*/
public function getFlavorText() {
return null;
}
/**
* Build items for the main menu.
*
* @param PhabricatorUser The viewing user.
* @param AphrontController The current controller. May be null for special
* pages like 404, exception handlers, etc.
* @return list<PHUIListItemView> List of menu items.
* @task ui
*/
public function buildMainMenuItems(
PhabricatorUser $user,
PhabricatorController $controller = null) {
return array();
}
/* -( Application Management )--------------------------------------------- */
final public static function getByClass($class_name) {
$selected = null;
$applications = self::getAllApplications();
foreach ($applications as $application) {
if (get_class($application) == $class_name) {
$selected = $application;
break;
}
}
if (!$selected) {
throw new Exception(pht("No application '%s'!", $class_name));
}
return $selected;
}
final public static function getAllApplications() {
static $applications;
if ($applications === null) {
$apps = id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setSortMethod('getApplicationOrder')
->execute();
// Reorder the applications into "application order". Notably, this
// ensures their event handlers register in application order.
$apps = mgroup($apps, 'getApplicationGroup');
$group_order = array_keys(self::getApplicationGroups());
$apps = array_select_keys($apps, $group_order) + $apps;
$apps = array_mergev($apps);
$applications = $apps;
}
return $applications;
}
final public static function getAllInstalledApplications() {
$all_applications = self::getAllApplications();
$apps = array();
foreach ($all_applications as $app) {
if (!$app->isInstalled()) {
continue;
}
$apps[] = $app;
}
return $apps;
}
/**
* Determine if an application is installed, by application class name.
*
* To check if an application is installed //and// available to a particular
* viewer, user @{method:isClassInstalledForViewer}.
*
* @param string Application class name.
* @return bool True if the class is installed.
* @task meta
*/
final public static function isClassInstalled($class) {
return self::getByClass($class)->isInstalled();
}
/**
* Determine if an application is installed and available to a viewer, by
* application class name.
*
* To check if an application is installed at all, use
* @{method:isClassInstalled}.
*
* @param string Application class name.
* @param PhabricatorUser Viewing user.
* @return bool True if the class is installed for the viewer.
* @task meta
*/
final public static function isClassInstalledForViewer(
$class,
PhabricatorUser $viewer) {
if ($viewer->isOmnipotent()) {
return true;
}
$cache = PhabricatorCaches::getRequestCache();
$viewer_fragment = $viewer->getCacheFragment();
$key = 'app.'.$class.'.installed.'.$viewer_fragment;
$result = $cache->getKey($key);
if ($result === null) {
if (!self::isClassInstalled($class)) {
$result = false;
} else {
$application = self::getByClass($class);
if (!$application->canUninstall()) {
// If the application can not be uninstalled, always allow viewers
// to see it. In particular, this allows logged-out viewers to see
// Settings and load global default settings even if the install
// does not allow public viewers.
$result = true;
} else {
$result = PhabricatorPolicyFilter::hasCapability(
$viewer,
self::getByClass($class),
PhabricatorPolicyCapability::CAN_VIEW);
}
}
$cache->setKey($key, $result);
}
return $result;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array_merge(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
),
array_keys($this->getCustomCapabilities()));
}
public function getPolicy($capability) {
$default = $this->getCustomPolicySetting($capability);
if ($default) {
return $default;
}
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return PhabricatorPolicies::getMostOpenPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return PhabricatorPolicies::POLICY_ADMIN;
default:
$spec = $this->getCustomCapabilitySpecification($capability);
return idx($spec, 'default', PhabricatorPolicies::POLICY_USER);
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
/* -( Policies )----------------------------------------------------------- */
protected function getCustomCapabilities() {
return array();
}
private function getCustomPolicySetting($capability) {
if (!$this->isCapabilityEditable($capability)) {
return null;
}
$policy_locked = PhabricatorEnv::getEnvConfig('policy.locked');
if (isset($policy_locked[$capability])) {
return $policy_locked[$capability];
}
$config = PhabricatorEnv::getEnvConfig('phabricator.application-settings');
$app = idx($config, $this->getPHID());
if (!$app) {
return null;
}
$policy = idx($app, 'policy');
if (!$policy) {
return null;
}
return idx($policy, $capability);
}
private function getCustomCapabilitySpecification($capability) {
$custom = $this->getCustomCapabilities();
if (!isset($custom[$capability])) {
throw new Exception(pht("Unknown capability '%s'!", $capability));
}
return $custom[$capability];
}
final public function getCapabilityLabel($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return pht('Can Use Application');
case PhabricatorPolicyCapability::CAN_EDIT:
return pht('Can Configure Application');
}
$capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
if ($capobj) {
return $capobj->getCapabilityName();
}
return null;
}
final public function isCapabilityEditable($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->canUninstall();
case PhabricatorPolicyCapability::CAN_EDIT:
return true;
default:
$spec = $this->getCustomCapabilitySpecification($capability);
return idx($spec, 'edit', true);
}
}
final public function getCapabilityCaption($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
if (!$this->canUninstall()) {
return pht(
- 'This application is required for Phabricator to operate, so all '.
+ 'This application is required, so all '.
'users must have access to it.');
} else {
return null;
}
case PhabricatorPolicyCapability::CAN_EDIT:
return null;
default:
$spec = $this->getCustomCapabilitySpecification($capability);
return idx($spec, 'caption');
}
}
final public function getCapabilityTemplatePHIDType($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
case PhabricatorPolicyCapability::CAN_EDIT:
return null;
}
$spec = $this->getCustomCapabilitySpecification($capability);
return idx($spec, 'template');
}
final public function getDefaultObjectTypePolicyMap() {
$map = array();
foreach ($this->getCustomCapabilities() as $capability => $spec) {
if (empty($spec['template'])) {
continue;
}
if (empty($spec['capability'])) {
continue;
}
$default = $this->getPolicy($capability);
$map[$spec['template']][$spec['capability']] = $default;
}
return $map;
}
public function getApplicationSearchDocumentTypes() {
return array();
}
protected function getEditRoutePattern($base = null) {
return $base.'(?:'.
'(?P<id>[0-9]\d*)/)?'.
'(?:'.
'(?:'.
'(?P<editAction>parameters|nodefault|nocreate|nomanage|comment)/'.
'|'.
'(?:form/(?P<formKey>[^/]+)/)?(?:page/(?P<pageKey>[^/]+)/)?'.
')'.
')?';
}
protected function getBulkRoutePattern($base = null) {
return $base.'(?:query/(?P<queryKey>[^/]+)/)?';
}
protected function getQueryRoutePattern($base = null) {
return $base.'(?:query/(?P<queryKey>[^/]+)/(?:(?P<queryAction>[^/]+)/)?)?';
}
protected function getProfileMenuRouting($controller) {
$edit_route = $this->getEditRoutePattern();
$mode_route = '(?P<itemEditMode>global|custom)/';
return array(
'(?P<itemAction>view)/(?P<itemID>[^/]+)/' => $controller,
'(?P<itemAction>hide)/(?P<itemID>[^/]+)/' => $controller,
'(?P<itemAction>default)/(?P<itemID>[^/]+)/' => $controller,
'(?P<itemAction>configure)/' => $controller,
'(?P<itemAction>configure)/'.$mode_route => $controller,
'(?P<itemAction>reorder)/'.$mode_route => $controller,
'(?P<itemAction>edit)/'.$edit_route => $controller,
'(?P<itemAction>new)/'.$mode_route.'(?<itemKey>[^/]+)/'.$edit_route
=> $controller,
'(?P<itemAction>builtin)/(?<itemID>[^/]+)/'.$edit_route
=> $controller,
);
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorApplicationEditor();
}
public function getApplicationTransactionTemplate() {
return new PhabricatorApplicationApplicationTransaction();
}
}
diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php
index a463c741d1..db9d456094 100644
--- a/src/applications/base/controller/PhabricatorController.php
+++ b/src/applications/base/controller/PhabricatorController.php
@@ -1,647 +1,651 @@
<?php
abstract class PhabricatorController extends AphrontController {
private $handles;
public function shouldRequireLogin() {
return true;
}
public function shouldRequireAdmin() {
return false;
}
public function shouldRequireEnabledUser() {
return true;
}
public function shouldAllowPublic() {
return false;
}
public function shouldAllowPartialSessions() {
return false;
}
public function shouldRequireEmailVerification() {
return PhabricatorUserEmail::isEmailVerificationRequired();
}
public function shouldAllowRestrictedParameter($parameter_name) {
return false;
}
public function shouldRequireMultiFactorEnrollment() {
if (!$this->shouldRequireLogin()) {
return false;
}
if (!$this->shouldRequireEnabledUser()) {
return false;
}
if ($this->shouldAllowPartialSessions()) {
return false;
}
$user = $this->getRequest()->getUser();
if (!$user->getIsStandardUser()) {
return false;
}
return PhabricatorEnv::getEnvConfig('security.require-multi-factor-auth');
}
public function shouldAllowLegallyNonCompliantUsers() {
return false;
}
public function isGlobalDragAndDropUploadEnabled() {
return false;
}
public function willBeginExecution() {
$request = $this->getRequest();
if ($request->getUser()) {
// NOTE: Unit tests can set a user explicitly. Normal requests are not
// permitted to do this.
PhabricatorTestCase::assertExecutingUnitTests();
$user = $request->getUser();
} else {
$user = new PhabricatorUser();
$session_engine = new PhabricatorAuthSessionEngine();
$phsid = $request->getCookie(PhabricatorCookies::COOKIE_SESSION);
if (strlen($phsid)) {
$session_user = $session_engine->loadUserForSession(
PhabricatorAuthSession::TYPE_WEB,
$phsid);
if ($session_user) {
$user = $session_user;
}
} else {
// If the client doesn't have a session token, generate an anonymous
// session. This is used to provide CSRF protection to logged-out users.
$phsid = $session_engine->establishSession(
PhabricatorAuthSession::TYPE_WEB,
null,
$partial = false);
// This may be a resource request, in which case we just don't set
// the cookie.
if ($request->canSetCookies()) {
$request->setCookie(PhabricatorCookies::COOKIE_SESSION, $phsid);
}
}
if (!$user->isLoggedIn()) {
$csrf = PhabricatorHash::digestWithNamedKey($phsid, 'csrf.alternate');
$user->attachAlternateCSRFString($csrf);
}
$request->setUser($user);
}
id(new PhabricatorAuthSessionEngine())
->willServeRequestForUser($user);
if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) {
$dark_console = PhabricatorDarkConsoleSetting::SETTINGKEY;
if ($user->getUserSetting($dark_console) ||
PhabricatorEnv::getEnvConfig('darkconsole.always-on')) {
$console = new DarkConsoleCore();
$request->getApplicationConfiguration()->setConsole($console);
}
}
// NOTE: We want to set up the user first so we can render a real page
// here, but fire this before any real logic.
$restricted = array(
'code',
);
foreach ($restricted as $parameter) {
if ($request->getExists($parameter)) {
if (!$this->shouldAllowRestrictedParameter($parameter)) {
throw new Exception(
pht(
'Request includes restricted parameter "%s", but this '.
'controller ("%s") does not whitelist it. Refusing to '.
'serve this request because it might be part of a redirection '.
'attack.',
$parameter,
get_class($this)));
}
}
}
if ($this->shouldRequireEnabledUser()) {
if ($user->getIsDisabled()) {
$controller = new PhabricatorDisabledUserController();
return $this->delegateToController($controller);
}
}
$auth_class = 'PhabricatorAuthApplication';
$auth_application = PhabricatorApplication::getByClass($auth_class);
// Require partial sessions to finish login before doing anything.
if (!$this->shouldAllowPartialSessions()) {
if ($user->hasSession() &&
$user->getSession()->getIsPartial()) {
$login_controller = new PhabricatorAuthFinishController();
$this->setCurrentApplication($auth_application);
return $this->delegateToController($login_controller);
}
}
// Require users sign Legalpad documents before we check if they have
// MFA. If we don't do this, they can get stuck in a state where they
// can't add MFA until they sign, and can't sign until they add MFA.
// See T13024 and PHI223.
$result = $this->requireLegalpadSignatures();
if ($result !== null) {
return $result;
}
// Check if the user needs to configure MFA.
$need_mfa = $this->shouldRequireMultiFactorEnrollment();
$have_mfa = $user->getIsEnrolledInMultiFactor();
if ($need_mfa && !$have_mfa) {
// Check if the cache is just out of date. Otherwise, roadblock the user
// and require MFA enrollment.
$user->updateMultiFactorEnrollment();
if (!$user->getIsEnrolledInMultiFactor()) {
$mfa_controller = new PhabricatorAuthNeedsMultiFactorController();
$this->setCurrentApplication($auth_application);
return $this->delegateToController($mfa_controller);
}
}
if ($this->shouldRequireLogin()) {
// This actually means we need either:
// - a valid user, or a public controller; and
// - permission to see the application; and
// - permission to see at least one Space if spaces are configured.
$allow_public = $this->shouldAllowPublic() &&
PhabricatorEnv::getEnvConfig('policy.allow-public');
// If this controller isn't public, and the user isn't logged in, require
// login.
if (!$allow_public && !$user->isLoggedIn()) {
$login_controller = new PhabricatorAuthStartController();
$this->setCurrentApplication($auth_application);
return $this->delegateToController($login_controller);
}
if ($user->isLoggedIn()) {
if ($this->shouldRequireEmailVerification()) {
if (!$user->getIsEmailVerified()) {
$controller = new PhabricatorMustVerifyEmailController();
$this->setCurrentApplication($auth_application);
return $this->delegateToController($controller);
}
}
}
// If Spaces are configured, require that the user have access to at
// least one. If we don't do this, they'll get confusing error messages
// later on.
$spaces = PhabricatorSpacesNamespaceQuery::getSpacesExist();
if ($spaces) {
$viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces(
$user);
if (!$viewer_spaces) {
$controller = new PhabricatorSpacesNoAccessController();
return $this->delegateToController($controller);
}
}
// If the user doesn't have access to the application, don't let them use
// any of its controllers. We query the application in order to generate
// a policy exception if the viewer doesn't have permission.
$application = $this->getCurrentApplication();
if ($application) {
id(new PhabricatorApplicationQuery())
->setViewer($user)
->withPHIDs(array($application->getPHID()))
->executeOne();
}
// If users need approval, require they wait here. We do this near the
// end so they can take other actions (like verifying email, signing
// documents, and enrolling in MFA) while waiting for an admin to take a
// look at things. See T13024 for more discussion.
if ($this->shouldRequireEnabledUser()) {
if ($user->isLoggedIn() && !$user->getIsApproved()) {
$controller = new PhabricatorAuthNeedsApprovalController();
return $this->delegateToController($controller);
}
}
}
// NOTE: We do this last so that users get a login page instead of a 403
// if they need to login.
if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) {
return new Aphront403Response();
}
}
public function getApplicationURI($path = '') {
if (!$this->getCurrentApplication()) {
throw new Exception(pht('No application!'));
}
return $this->getCurrentApplication()->getApplicationURI($path);
}
public function willSendResponse(AphrontResponse $response) {
$request = $this->getRequest();
if ($response instanceof AphrontDialogResponse) {
if (!$request->isAjax() && !$request->isQuicksand()) {
$dialog = $response->getDialog();
$title = $dialog->getTitle();
$short = $dialog->getShortTitle();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(coalesce($short, $title));
$page_content = array(
$crumbs,
$response->buildResponseString(),
);
$view = id(new PhabricatorStandardPageView())
->setRequest($request)
->setController($this)
->setDeviceReady(true)
->setTitle($title)
->appendChild($page_content);
$response = id(new AphrontWebpageResponse())
->setContent($view->render())
->setHTTPResponseCode($response->getHTTPResponseCode());
} else {
$response->getDialog()->setIsStandalone(true);
return id(new AphrontAjaxResponse())
->setContent(array(
'dialog' => $response->buildResponseString(),
));
}
} else if ($response instanceof AphrontRedirectResponse) {
if ($request->isAjax() || $request->isQuicksand()) {
return id(new AphrontAjaxResponse())
->setContent(
array(
'redirect' => $response->getURI(),
'close' => $response->getCloseDialogBeforeRedirect(),
));
}
}
return $response;
}
/**
* WARNING: Do not call this in new code.
*
* @deprecated See "Handles Technical Documentation".
*/
protected function loadViewerHandles(array $phids) {
return id(new PhabricatorHandleQuery())
->setViewer($this->getRequest()->getUser())
->withPHIDs($phids)
->execute();
}
public function buildApplicationMenu() {
return null;
}
protected function buildApplicationCrumbs() {
$crumbs = array();
$application = $this->getCurrentApplication();
if ($application) {
$icon = $application->getIcon();
if (!$icon) {
$icon = 'fa-puzzle';
}
$crumbs[] = id(new PHUICrumbView())
->setHref($this->getApplicationURI())
->setName($application->getName())
->setIcon($icon);
}
$view = new PHUICrumbsView();
foreach ($crumbs as $crumb) {
$view->addCrumb($crumb);
}
return $view;
}
protected function hasApplicationCapability($capability) {
return PhabricatorPolicyFilter::hasCapability(
$this->getRequest()->getUser(),
$this->getCurrentApplication(),
$capability);
}
protected function requireApplicationCapability($capability) {
PhabricatorPolicyFilter::requireCapability(
$this->getRequest()->getUser(),
$this->getCurrentApplication(),
$capability);
}
protected function explainApplicationCapability(
$capability,
$positive_message,
$negative_message) {
$can_act = $this->hasApplicationCapability($capability);
if ($can_act) {
$message = $positive_message;
$icon_name = 'fa-play-circle-o lightgreytext';
} else {
$message = $negative_message;
$icon_name = 'fa-lock';
}
$icon = id(new PHUIIconView())
->setIcon($icon_name);
require_celerity_resource('policy-css');
$phid = $this->getCurrentApplication()->getPHID();
$explain_uri = "/policy/explain/{$phid}/{$capability}/";
$message = phutil_tag(
'div',
array(
'class' => 'policy-capability-explanation',
),
array(
$icon,
javelin_tag(
'a',
array(
'href' => $explain_uri,
'sigil' => 'workflow',
),
$message),
));
return array($can_act, $message);
}
public function getDefaultResourceSource() {
return 'phabricator';
}
/**
* Create a new @{class:AphrontDialogView} with defaults filled in.
*
* @return AphrontDialogView New dialog.
*/
public function newDialog() {
$submit_uri = new PhutilURI($this->getRequest()->getRequestURI());
$submit_uri = $submit_uri->getPath();
return id(new AphrontDialogView())
->setUser($this->getRequest()->getUser())
->setSubmitURI($submit_uri);
}
+ public function newRedirect() {
+ return id(new AphrontRedirectResponse());
+ }
+
public function newPage() {
$page = id(new PhabricatorStandardPageView())
->setRequest($this->getRequest())
->setController($this)
->setDeviceReady(true);
$application = $this->getCurrentApplication();
if ($application) {
$page->setApplicationName($application->getName());
if ($application->getTitleGlyph()) {
$page->setGlyph($application->getTitleGlyph());
}
}
$viewer = $this->getRequest()->getUser();
if ($viewer) {
$page->setUser($viewer);
}
return $page;
}
public function newApplicationMenu() {
return id(new PHUIApplicationMenuView())
->setViewer($this->getViewer());
}
public function newCurtainView($object = null) {
$viewer = $this->getViewer();
$action_id = celerity_generate_unique_node_id();
$action_list = id(new PhabricatorActionListView())
->setViewer($viewer)
->setID($action_id);
// NOTE: Applications (objects of class PhabricatorApplication) can't
// currently be set here, although they don't need any of the extensions
// anyway. This should probably work differently than it does, though.
if ($object) {
if ($object instanceof PhabricatorLiskDAO) {
$action_list->setObject($object);
}
}
$curtain = id(new PHUICurtainView())
->setViewer($viewer)
->setActionList($action_list);
if ($object) {
$panels = PHUICurtainExtension::buildExtensionPanels($viewer, $object);
foreach ($panels as $panel) {
$curtain->addPanel($panel);
}
}
return $curtain;
}
protected function buildTransactionTimeline(
PhabricatorApplicationTransactionInterface $object,
PhabricatorApplicationTransactionQuery $query = null,
PhabricatorMarkupEngine $engine = null,
$view_data = array()) {
$request = $this->getRequest();
$viewer = $this->getViewer();
$xaction = $object->getApplicationTransactionTemplate();
if (!$query) {
$query = PhabricatorApplicationTransactionQuery::newQueryForObject(
$object);
if (!$query) {
throw new Exception(
pht(
'Unable to find transaction query for object of class "%s".',
get_class($object)));
}
}
$pager = id(new AphrontCursorPagerView())
->readFromRequest($request)
->setURI(new PhutilURI(
'/transactions/showolder/'.$object->getPHID().'/'));
$xactions = $query
->setViewer($viewer)
->withObjectPHIDs(array($object->getPHID()))
->needComments(true)
->executeWithCursorPager($pager);
$xactions = array_reverse($xactions);
$timeline_engine = PhabricatorTimelineEngine::newForObject($object)
->setViewer($viewer)
->setTransactions($xactions)
->setViewData($view_data);
$view = $timeline_engine->buildTimelineView();
if ($engine) {
foreach ($xactions as $xaction) {
if ($xaction->getComment()) {
$engine->addObject(
$xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
$engine->process();
$view->setMarkupEngine($engine);
}
$timeline = $view
->setPager($pager)
->setQuoteTargetID($this->getRequest()->getStr('quoteTargetID'))
->setQuoteRef($this->getRequest()->getStr('quoteRef'));
return $timeline;
}
public function buildApplicationCrumbsForEditEngine() {
// TODO: This is kind of gross, I'm basically just making this public so
// I can use it in EditEngine. We could do this without making it public
// by using controller delegation, or make it properly public.
return $this->buildApplicationCrumbs();
}
private function requireLegalpadSignatures() {
if (!$this->shouldRequireLogin()) {
return null;
}
if ($this->shouldAllowLegallyNonCompliantUsers()) {
return null;
}
$viewer = $this->getViewer();
if (!$viewer->hasSession()) {
return null;
}
$session = $viewer->getSession();
if ($session->getIsPartial()) {
// If the user hasn't made it through MFA yet, require they survive
// MFA first.
return null;
}
if ($session->getSignedLegalpadDocuments()) {
return null;
}
if (!$viewer->isLoggedIn()) {
return null;
}
$must_sign_docs = array();
$sign_docs = array();
$legalpad_class = 'PhabricatorLegalpadApplication';
$legalpad_installed = PhabricatorApplication::isClassInstalledForViewer(
$legalpad_class,
$viewer);
if ($legalpad_installed) {
$sign_docs = id(new LegalpadDocumentQuery())
->setViewer($viewer)
->withSignatureRequired(1)
->needViewerSignatures(true)
->setOrder('oldest')
->execute();
foreach ($sign_docs as $sign_doc) {
if (!$sign_doc->getUserSignature($viewer->getPHID())) {
$must_sign_docs[] = $sign_doc;
}
}
}
if (!$must_sign_docs) {
// If nothing needs to be signed (either because there are no documents
// which require a signature, or because the user has already signed
// all of them) mark the session as good and continue.
$engine = id(new PhabricatorAuthSessionEngine())
->signLegalpadDocuments($viewer, $sign_docs);
return null;
}
$request = $this->getRequest();
$request->setURIMap(
array(
'id' => head($must_sign_docs)->getID(),
));
$application = PhabricatorApplication::getByClass($legalpad_class);
$this->setCurrentApplication($application);
$controller = new LegalpadDocumentSignController();
$controller->setIsSessionGate(true);
return $this->delegateToController($controller);
}
/* -( Deprecated )--------------------------------------------------------- */
/**
* DEPRECATED. Use @{method:newPage}.
*/
public function buildStandardPageView() {
return $this->newPage();
}
/**
* DEPRECATED. Use @{method:newPage}.
*/
public function buildStandardPageResponse($view, array $data) {
$page = $this->buildStandardPageView();
$page->appendChild($view);
return $page->produceAphrontResponse();
}
}
diff --git a/src/applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php b/src/applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php
index cfe42c918b..37de525a02 100644
--- a/src/applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php
+++ b/src/applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php
@@ -1,84 +1,84 @@
<?php
final class PhabricatorCacheManagementPurgeWorkflow
extends PhabricatorCacheManagementWorkflow {
protected function didConstruct() {
$this
->setName('purge')
->setSynopsis(pht('Drop data from readthrough caches.'))
->setArguments(
array(
array(
'name' => 'all',
'help' => pht('Purge all caches.'),
),
array(
'name' => 'caches',
'param' => 'keys',
'help' => pht('Purge a specific set of caches.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$all_purgers = PhabricatorCachePurger::getAllPurgers();
$is_all = $args->getArg('all');
$key_list = $args->getArg('caches');
- if ($is_all && strlen($key_list)) {
+ if ($is_all && phutil_nonempty_string($key_list)) {
throw new PhutilArgumentUsageException(
pht(
'Specify either "--all" or "--caches", not both.'));
- } else if (!$is_all && !strlen($key_list)) {
+ } else if (!$is_all && !phutil_nonempty_string($key_list)) {
throw new PhutilArgumentUsageException(
pht(
'Select caches to purge with "--all" or "--caches". Available '.
'caches are: %s.',
implode(', ', array_keys($all_purgers))));
}
if ($is_all) {
$purgers = $all_purgers;
} else {
$key_list = preg_split('/[\s,]+/', $key_list);
$purgers = array();
foreach ($key_list as $key) {
if (isset($all_purgers[$key])) {
$purgers[$key] = $all_purgers[$key];
} else {
throw new PhutilArgumentUsageException(
pht(
'Cache purger "%s" is not recognized. Available caches '.
'are: %s.',
$key,
implode(', ', array_keys($all_purgers))));
}
}
if (!$purgers) {
throw new PhutilArgumentUsageException(
pht(
'When using "--caches", you must select at least one valid '.
'cache to purge.'));
}
}
$viewer = $this->getViewer();
foreach ($purgers as $key => $purger) {
$purger->setViewer($viewer);
echo tsprintf(
"%s\n",
pht(
'Purging "%s" cache...',
$key));
$purger->purgeCache();
}
return 0;
}
}
diff --git a/src/applications/cache/spec/PhabricatorCacheSpec.php b/src/applications/cache/spec/PhabricatorCacheSpec.php
index 1ed4e713f1..3f3273fd2a 100644
--- a/src/applications/cache/spec/PhabricatorCacheSpec.php
+++ b/src/applications/cache/spec/PhabricatorCacheSpec.php
@@ -1,120 +1,120 @@
<?php
abstract class PhabricatorCacheSpec extends Phobject {
private $name;
private $isEnabled = false;
private $version;
private $clearCacheCallback = null;
private $issues = array();
private $usedMemory = 0;
private $totalMemory = 0;
private $entryCount = null;
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setIsEnabled($is_enabled) {
$this->isEnabled = $is_enabled;
return $this;
}
public function getIsEnabled() {
return $this->isEnabled;
}
public function setVersion($version) {
$this->version = $version;
return $this;
}
public function getVersion() {
return $this->version;
}
protected function newIssue($key) {
$issue = id(new PhabricatorSetupIssue())
->setIssueKey($key);
$this->issues[$key] = $issue;
return $issue;
}
public function getIssues() {
return $this->issues;
}
public function setUsedMemory($used_memory) {
$this->usedMemory = $used_memory;
return $this;
}
public function getUsedMemory() {
return $this->usedMemory;
}
public function setTotalMemory($total_memory) {
$this->totalMemory = $total_memory;
return $this;
}
public function getTotalMemory() {
return $this->totalMemory;
}
public function setEntryCount($entry_count) {
$this->entryCount = $entry_count;
return $this;
}
public function getEntryCount() {
return $this->entryCount;
}
protected function raiseInstallAPCIssue() {
$message = pht(
"Installing the PHP extension 'APC' (Alternative PHP Cache) will ".
"dramatically improve performance. Note that APC versions 3.1.14 and ".
"3.1.15 are broken; 3.1.13 is recommended instead.");
return $this
->newIssue('extension.apc')
->setShortName(pht('APC'))
->setName(pht("PHP Extension 'APC' Not Installed"))
->setMessage($message)
->addPHPExtension('apc');
}
protected function raiseEnableAPCIssue() {
$summary = pht('Enabling APC/APCu will improve performance.');
$message = pht(
'The APC or APCu PHP extensions are installed, but not enabled in your '.
- 'PHP configuration. Enabling these extensions will improve Phabricator '.
- 'performance. Edit the "%s" setting to enable these extensions.',
+ 'PHP configuration. Enabling these extensions will improve performance. '.
+ 'Edit the "%s" setting to enable these extensions.',
'apc.enabled');
return $this
->newIssue('extension.apc.enabled')
->setShortName(pht('APC/APCu Disabled'))
->setName(pht('APC/APCu Extensions Not Enabled'))
->setSummary($summary)
->setMessage($message)
->addPHPConfig('apc.enabled');
}
public function setClearCacheCallback($callback) {
$this->clearCacheCallback = $callback;
return $this;
}
public function getClearCacheCallback() {
return $this->clearCacheCallback;
}
}
diff --git a/src/applications/cache/spec/PhabricatorDataCacheSpec.php b/src/applications/cache/spec/PhabricatorDataCacheSpec.php
index 103b0d8394..0c0c449c53 100644
--- a/src/applications/cache/spec/PhabricatorDataCacheSpec.php
+++ b/src/applications/cache/spec/PhabricatorDataCacheSpec.php
@@ -1,158 +1,158 @@
<?php
final class PhabricatorDataCacheSpec extends PhabricatorCacheSpec {
private $cacheSummary;
public function setCacheSummary(array $cache_summary) {
$this->cacheSummary = $cache_summary;
return $this;
}
public function getCacheSummary() {
return $this->cacheSummary;
}
public static function getActiveCacheSpec() {
$spec = new PhabricatorDataCacheSpec();
// NOTE: If APCu is installed, it reports that APC is installed.
if (extension_loaded('apc') && !extension_loaded('apcu')) {
$spec->initAPCSpec();
} else if (extension_loaded('apcu')) {
$spec->initAPCuSpec();
} else {
$spec->initNoneSpec();
}
return $spec;
}
private function initAPCSpec() {
$this
->setName(pht('APC User Cache'))
->setVersion(phpversion('apc'));
if (ini_get('apc.enabled')) {
$this
->setIsEnabled(true)
->setClearCacheCallback('apc_clear_cache');
$this->initAPCCommonSpec();
} else {
$this->setIsEnabled(false);
$this->raiseEnableAPCIssue();
}
}
private function initAPCuSpec() {
$this
->setName(pht('APCu'))
->setVersion(phpversion('apcu'));
if (ini_get('apc.enabled')) {
if (function_exists('apcu_clear_cache')) {
$clear_callback = 'apcu_clear_cache';
} else {
$clear_callback = 'apc_clear_cache';
}
$this
->setIsEnabled(true)
->setClearCacheCallback($clear_callback);
$this->initAPCCommonSpec();
} else {
$this->setIsEnabled(false);
$this->raiseEnableAPCIssue();
}
}
private function initNoneSpec() {
if (version_compare(phpversion(), '5.5', '>=')) {
$message = pht(
'Installing the "APCu" PHP extension will improve performance. '.
- 'This extension is strongly recommended. Without it, Phabricator '.
+ 'This extension is strongly recommended. Without it, this software '.
'must rely on a very inefficient disk-based cache.');
$this
->newIssue('extension.apcu')
->setShortName(pht('APCu'))
->setName(pht('PHP Extension "APCu" Not Installed'))
->setMessage($message)
->addPHPExtension('apcu');
} else {
$this->raiseInstallAPCIssue();
}
}
private function initAPCCommonSpec() {
$state = array();
if (function_exists('apcu_sma_info')) {
$mem = apcu_sma_info();
$info = apcu_cache_info();
} else if (function_exists('apc_sma_info')) {
$mem = apc_sma_info();
$info = apc_cache_info('user');
} else {
$mem = null;
}
if ($mem) {
$this->setTotalMemory($mem['num_seg'] * $mem['seg_size']);
$this->setUsedMemory($info['mem_size']);
$this->setEntryCount(count($info['cache_list']));
$cache = $info['cache_list'];
$state = array();
foreach ($cache as $item) {
// Some older versions of APCu report the cachekey as "key", while
// newer APCu and APC report it as "info". Just check both indexes
// for commpatibility. See T13164 for details.
$info = idx($item, 'info');
if ($info === null) {
$info = idx($item, 'key');
}
if ($info === null) {
$key = '<unknown-key>';
} else {
$key = self::getKeyPattern($info);
}
if (empty($state[$key])) {
$state[$key] = array(
'max' => 0,
'total' => 0,
'count' => 0,
);
}
$state[$key]['max'] = max($state[$key]['max'], $item['mem_size']);
$state[$key]['total'] += $item['mem_size'];
$state[$key]['count']++;
}
}
$this->setCacheSummary($state);
}
private static function getKeyPattern($key) {
// If this key isn't in the current cache namespace, don't reveal any
// information about it.
$namespace = PhabricatorEnv::getEnvConfig('phabricator.cache-namespace');
if (strncmp($key, $namespace.':', strlen($namespace) + 1)) {
return '<other-namespace>';
}
$key = preg_replace('/(?<![a-zA-Z])\d+(?![a-zA-Z])/', 'N', $key);
$key = preg_replace('/PHID-[A-Z]{4}-[a-z0-9]{20}/', 'PHID', $key);
// TODO: We should probably standardize how digests get embedded into cache
// keys to make this rule more generic.
$key = preg_replace('/:celerity:.*$/', ':celerity:X', $key);
$key = preg_replace('/:pkcs8:.*$/', ':pkcs8:X', $key);
return $key;
}
}
diff --git a/src/applications/cache/spec/PhabricatorOpcodeCacheSpec.php b/src/applications/cache/spec/PhabricatorOpcodeCacheSpec.php
index 2a59d7e8aa..a4c930fadd 100644
--- a/src/applications/cache/spec/PhabricatorOpcodeCacheSpec.php
+++ b/src/applications/cache/spec/PhabricatorOpcodeCacheSpec.php
@@ -1,206 +1,205 @@
<?php
final class PhabricatorOpcodeCacheSpec extends PhabricatorCacheSpec {
public static function getActiveCacheSpec() {
$spec = new PhabricatorOpcodeCacheSpec();
// NOTE: If APCu is installed, it reports that APC is installed.
if (extension_loaded('apc') && !extension_loaded('apcu')) {
$spec->initAPCSpec();
} else if (extension_loaded('Zend OPcache')) {
$spec->initOpcacheSpec();
} else {
$spec->initNoneSpec();
}
return $spec;
}
private function initAPCSpec() {
$this
->setName(pht('APC'))
->setVersion(phpversion('apc'));
if (ini_get('apc.enabled')) {
$this
->setIsEnabled(true)
->setClearCacheCallback('apc_clear_cache');
$mem = apc_sma_info();
$this->setTotalMemory($mem['num_seg'] * $mem['seg_size']);
$info = apc_cache_info();
$this->setUsedMemory($info['mem_size']);
$write_lock = ini_get('apc.write_lock');
$slam_defense = ini_get('apc.slam_defense');
if (!$write_lock || $slam_defense) {
$summary = pht('Adjust APC settings to quiet unnecessary errors.');
$message = pht(
'Some versions of APC may emit unnecessary errors into the '.
'error log under the current APC settings. To resolve this, '.
'enable "%s" and disable "%s" in your PHP configuration.',
'apc.write_lock',
'apc.slam_defense');
$this
->newIssue('extension.apc.write-lock')
->setShortName(pht('Noisy APC'))
->setName(pht('APC Has Noisy Configuration'))
->setSummary($summary)
->setMessage($message)
->addPHPConfig('apc.write_lock')
->addPHPConfig('apc.slam_defense');
}
$is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode');
$is_stat_enabled = ini_get('apc.stat');
if ($is_stat_enabled && !$is_dev) {
$summary = pht(
'"%s" is currently enabled, but should probably be disabled.',
'apc.stat');
$message = pht(
'The "%s" setting is currently enabled in your PHP configuration. '.
'In production mode, "%s" should be disabled. '.
'This will improve performance slightly.',
'apc.stat',
'apc.stat');
$this
->newIssue('extension.apc.stat-enabled')
->setShortName(pht('"%s" Enabled', 'apc.stat'))
->setName(pht('"%s" Enabled in Production', 'apc.stat'))
->setSummary($summary)
->setMessage($message)
->addPHPConfig('apc.stat')
->addPhabricatorConfig('phabricator.developer-mode');
} else if (!$is_stat_enabled && $is_dev) {
$summary = pht(
'"%s" is currently disabled, but should probably be enabled.',
'apc.stat');
$message = pht(
'The "%s" setting is currently disabled in your PHP configuration, '.
- 'but Phabricator is running in development mode. This option should '.
- 'normally be enabled in development so you do not need to restart '.
- 'anything after making changes to the code.',
+ 'but this software is running in development mode. This option '.
+ 'should normally be enabled in development so you do not need to '.
+ 'restart anything after making changes to the code.',
'apc.stat');
$this
->newIssue('extension.apc.stat-disabled')
->setShortName(pht('"%s" Disabled', 'apc.stat'))
->setName(pht('"%s" Disabled in Development', 'apc.stat'))
->setSummary($summary)
->setMessage($message)
->addPHPConfig('apc.stat')
->addPhabricatorConfig('phabricator.developer-mode');
}
} else {
$this->setIsEnabled(false);
$this->raiseEnableAPCIssue();
}
}
private function initOpcacheSpec() {
$this
->setName(pht('Zend OPcache'))
->setVersion(phpversion('Zend OPcache'));
if (ini_get('opcache.enable')) {
$this
->setIsEnabled(true)
->setClearCacheCallback('opcache_reset');
$status = opcache_get_status();
$memory = $status['memory_usage'];
$mem_used = $memory['used_memory'];
$mem_free = $memory['free_memory'];
$mem_junk = $memory['wasted_memory'];
$this->setUsedMemory($mem_used + $mem_junk);
$this->setTotalMemory($mem_used + $mem_junk + $mem_free);
$this->setEntryCount($status['opcache_statistics']['num_cached_keys']);
$is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode');
$validate = ini_get('opcache.validate_timestamps');
$freq = ini_get('opcache.revalidate_freq');
if ($is_dev && (!$validate || $freq)) {
$summary = pht(
'OPcache is not configured properly for development.');
$message = pht(
'In development, OPcache should be configured to always reload '.
'code so nothing needs to be restarted after making changes. To do '.
'this, enable "%s" and set "%s" to 0.',
'opcache.validate_timestamps',
'opcache.revalidate_freq');
$this
->newIssue('extension.opcache.devmode')
->setShortName(pht('OPcache Config'))
->setName(pht('OPcache Not Configured for Development'))
->setSummary($summary)
->setMessage($message)
->addPHPConfig('opcache.validate_timestamps')
->addPHPConfig('opcache.revalidate_freq')
->addPhabricatorConfig('phabricator.developer-mode');
} else if (!$is_dev && $validate) {
$summary = pht('OPcache is not configured ideally for production.');
$message = pht(
'In production, OPcache should be configured to never '.
'revalidate code. This will slightly improve performance. '.
'To do this, disable "%s" in your PHP configuration.',
'opcache.validate_timestamps');
$this
->newIssue('extension.opcache.production')
->setShortName(pht('OPcache Config'))
->setName(pht('OPcache Not Configured for Production'))
->setSummary($summary)
->setMessage($message)
->addPHPConfig('opcache.validate_timestamps')
->addPhabricatorConfig('phabricator.developer-mode');
}
} else {
$this->setIsEnabled(false);
$summary = pht('Enabling OPcache will dramatically improve performance.');
$message = pht(
'The PHP "Zend OPcache" extension is installed, but not enabled in '.
'your PHP configuration. Enabling it will dramatically improve '.
- 'Phabricator performance. Edit the "%s" setting to '.
- 'enable the extension.',
+ 'performance. Edit the "%s" setting to enable the extension.',
'opcache.enable');
$this->newIssue('extension.opcache.enable')
->setShortName(pht('OPcache Disabled'))
->setName(pht('Zend OPcache Not Enabled'))
->setSummary($summary)
->setMessage($message)
->addPHPConfig('opcache.enable');
}
}
private function initNoneSpec() {
if (version_compare(phpversion(), '5.5', '>=')) {
$message = pht(
'Installing the "Zend OPcache" extension will dramatically improve '.
'performance.');
$this
->newIssue('extension.opcache')
->setShortName(pht('OPcache'))
->setName(pht('Zend OPcache Not Installed'))
->setMessage($message)
->addPHPExtension('Zend OPcache');
} else {
$this->raiseInstallAPCIssue();
}
}
}
diff --git a/src/applications/calendar/codex/PhabricatorCalendarEventPolicyCodex.php b/src/applications/calendar/codex/PhabricatorCalendarEventPolicyCodex.php
index 97e21d901e..01f6335139 100644
--- a/src/applications/calendar/codex/PhabricatorCalendarEventPolicyCodex.php
+++ b/src/applications/calendar/codex/PhabricatorCalendarEventPolicyCodex.php
@@ -1,80 +1,80 @@
<?php
final class PhabricatorCalendarEventPolicyCodex
extends PhabricatorPolicyCodex {
public function getPolicyShortName() {
$object = $this->getObject();
if (!$object->isImportedEvent()) {
return null;
}
return pht('Uses Import Policy');
}
public function getPolicyIcon() {
$object = $this->getObject();
if (!$object->isImportedEvent()) {
return null;
}
return 'fa-download';
}
public function getPolicyTagClasses() {
$object = $this->getObject();
if (!$object->isImportedEvent()) {
return array();
}
return array(
'policy-adjusted-special',
);
}
public function getPolicySpecialRuleDescriptions() {
$object = $this->getObject();
$rules = array();
$rules[] = $this->newRule()
->setDescription(
pht('The host of an event can always view and edit it.'));
$rules[] = $this->newRule()
->setCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
))
->setDescription(
pht('Users who are invited to an event can always view it.'));
$rules[] = $this->newRule()
->setCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
))
->setIsActive($object->isImportedEvent())
->setDescription(
pht(
'Imported events can only be viewed by users who can view '.
'the import source.'));
$rules[] = $this->newRule()
->setCapabilities(
array(
PhabricatorPolicyCapability::CAN_EDIT,
))
->setIsActive($object->isImportedEvent())
->setDescription(
pht(
- 'Imported events can not be edited in Phabricator.'));
+ 'Imported events can not be edited.'));
return $rules;
}
}
diff --git a/src/applications/calendar/parser/ics/PhutilICSWriter.php b/src/applications/calendar/parser/ics/PhutilICSWriter.php
index 8e7d06804c..c35008e079 100644
--- a/src/applications/calendar/parser/ics/PhutilICSWriter.php
+++ b/src/applications/calendar/parser/ics/PhutilICSWriter.php
@@ -1,391 +1,391 @@
<?php
final class PhutilICSWriter extends Phobject {
public function writeICSDocument(PhutilCalendarRootNode $node) {
$out = array();
foreach ($node->getChildren() as $child) {
$out[] = $this->writeNode($child);
}
return implode('', $out);
}
private function writeNode(PhutilCalendarNode $node) {
if (!$this->getICSNodeType($node)) {
return null;
}
$out = array();
$out[] = $this->writeBeginNode($node);
$out[] = $this->writeNodeProperties($node);
if ($node instanceof PhutilCalendarContainerNode) {
foreach ($node->getChildren() as $child) {
$out[] = $this->writeNode($child);
}
}
$out[] = $this->writeEndNode($node);
return implode('', $out);
}
private function writeBeginNode(PhutilCalendarNode $node) {
$type = $this->getICSNodeType($node);
return $this->wrapICSLine("BEGIN:{$type}");
}
private function writeEndNode(PhutilCalendarNode $node) {
$type = $this->getICSNodeType($node);
return $this->wrapICSLine("END:{$type}");
}
private function writeNodeProperties(PhutilCalendarNode $node) {
$properties = $this->getNodeProperties($node);
$out = array();
foreach ($properties as $property) {
$propname = $property['name'];
$propvalue = $property['value'];
$propline = array();
$propline[] = $propname;
foreach ($property['parameters'] as $parameter) {
$paramname = $parameter['name'];
$paramvalue = $parameter['value'];
$propline[] = ";{$paramname}={$paramvalue}";
}
$propline[] = ":{$propvalue}";
$propline = implode('', $propline);
$out[] = $this->wrapICSLine($propline);
}
return implode('', $out);
}
private function getICSNodeType(PhutilCalendarNode $node) {
switch ($node->getNodeType()) {
case PhutilCalendarDocumentNode::NODETYPE:
return 'VCALENDAR';
case PhutilCalendarEventNode::NODETYPE:
return 'VEVENT';
default:
return null;
}
}
private function wrapICSLine($line) {
$out = array();
$buf = '';
// NOTE: The line may contain sequences of combining characters which are
// more than 80 bytes in length. If it does, we'll split them in the
// middle of the sequence. This is okay and generally anticipated by
// RFC5545, which even allows implementations to split multibyte
// characters. The sequence will be stitched back together properly by
// whatever is parsing things.
foreach (phutil_utf8v($line) as $character) {
// If adding this character would bring the line over 75 bytes, start
// a new line.
if (strlen($buf) + strlen($character) > 75) {
$out[] = $buf."\r\n";
$buf = ' ';
}
$buf .= $character;
}
$out[] = $buf."\r\n";
return implode('', $out);
}
private function getNodeProperties(PhutilCalendarNode $node) {
switch ($node->getNodeType()) {
case PhutilCalendarDocumentNode::NODETYPE:
return $this->getDocumentNodeProperties($node);
case PhutilCalendarEventNode::NODETYPE:
return $this->getEventNodeProperties($node);
default:
return array();
}
}
private function getDocumentNodeProperties(
PhutilCalendarDocumentNode $event) {
$properties = array();
$properties[] = $this->newTextProperty(
'VERSION',
'2.0');
$properties[] = $this->newTextProperty(
'PRODID',
self::getICSPRODID());
return $properties;
}
public static function getICSPRODID() {
return '-//Phacility//Phabricator//EN';
}
private function getEventNodeProperties(PhutilCalendarEventNode $event) {
$properties = array();
$uid = $event->getUID();
if (!strlen($uid)) {
throw new Exception(
pht(
'Unable to write ICS document: event has no UID, but each event '.
'MUST have a UID.'));
}
$properties[] = $this->newTextProperty(
'UID',
$uid);
$created = $event->getCreatedDateTime();
if ($created) {
$properties[] = $this->newDateTimeProperty(
'CREATED',
$event->getCreatedDateTime());
}
$dtstamp = $event->getModifiedDateTime();
if (!$dtstamp) {
throw new Exception(
pht(
'Unable to write ICS document: event has no modified time, but '.
'each event MUST have a modified time.'));
}
$properties[] = $this->newDateTimeProperty(
'DTSTAMP',
$dtstamp);
$dtstart = $event->getStartDateTime();
if ($dtstart) {
$properties[] = $this->newDateTimeProperty(
'DTSTART',
$dtstart);
}
$dtend = $event->getEndDateTime();
if ($dtend) {
$properties[] = $this->newDateTimeProperty(
'DTEND',
$event->getEndDateTime());
}
$name = $event->getName();
- if (strlen($name)) {
+ if (phutil_nonempty_string($name)) {
$properties[] = $this->newTextProperty(
'SUMMARY',
$name);
}
$description = $event->getDescription();
- if (strlen($description)) {
+ if (phutil_nonempty_string($description)) {
$properties[] = $this->newTextProperty(
'DESCRIPTION',
$description);
}
$organizer = $event->getOrganizer();
if ($organizer) {
$properties[] = $this->newUserProperty(
'ORGANIZER',
$organizer);
}
$attendees = $event->getAttendees();
if ($attendees) {
foreach ($attendees as $attendee) {
$properties[] = $this->newUserProperty(
'ATTENDEE',
$attendee);
}
}
$rrule = $event->getRecurrenceRule();
if ($rrule) {
$properties[] = $this->newRRULEProperty(
'RRULE',
$rrule);
}
$recurrence_id = $event->getRecurrenceID();
if ($recurrence_id) {
$properties[] = $this->newTextProperty(
'RECURRENCE-ID',
$recurrence_id);
}
$exdates = $event->getRecurrenceExceptions();
if ($exdates) {
$properties[] = $this->newDateTimesProperty(
'EXDATE',
$exdates);
}
$rdates = $event->getRecurrenceDates();
if ($rdates) {
$properties[] = $this->newDateTimesProperty(
'RDATE',
$rdates);
}
return $properties;
}
private function newTextProperty(
$name,
$value,
array $parameters = array()) {
$map = array(
'\\' => '\\\\',
',' => '\\,',
"\n" => '\\n',
);
$value = (array)$value;
foreach ($value as $k => $v) {
$v = str_replace(array_keys($map), array_values($map), $v);
$value[$k] = $v;
}
$value = implode(',', $value);
return $this->newProperty($name, $value, $parameters);
}
private function newDateTimeProperty(
$name,
PhutilCalendarDateTime $value,
array $parameters = array()) {
return $this->newDateTimesProperty($name, array($value), $parameters);
}
private function newDateTimesProperty(
$name,
array $values,
array $parameters = array()) {
assert_instances_of($values, 'PhutilCalendarDateTime');
if (head($values)->getIsAllDay()) {
$parameters[] = array(
'name' => 'VALUE',
'values' => array(
'DATE',
),
);
}
$datetimes = array();
foreach ($values as $value) {
$datetimes[] = $value->getISO8601();
}
$datetimes = implode(';', $datetimes);
return $this->newProperty($name, $datetimes, $parameters);
}
private function newUserProperty(
$name,
PhutilCalendarUserNode $value,
array $parameters = array()) {
$parameters[] = array(
'name' => 'CN',
'values' => array(
$value->getName(),
),
);
$partstat = null;
switch ($value->getStatus()) {
case PhutilCalendarUserNode::STATUS_INVITED:
$partstat = 'NEEDS-ACTION';
break;
case PhutilCalendarUserNode::STATUS_ACCEPTED:
$partstat = 'ACCEPTED';
break;
case PhutilCalendarUserNode::STATUS_DECLINED:
$partstat = 'DECLINED';
break;
}
if ($partstat !== null) {
$parameters[] = array(
'name' => 'PARTSTAT',
'values' => array(
$partstat,
),
);
}
// TODO: We could reasonably fill in "ROLE" and "RSVP" here too, but it
// isn't clear if these are important to external programs or not.
return $this->newProperty($name, $value->getURI(), $parameters);
}
private function newRRULEProperty(
$name,
PhutilCalendarRecurrenceRule $rule,
array $parameters = array()) {
$value = $rule->toRRULE();
return $this->newProperty($name, $value, $parameters);
}
private function newProperty(
$name,
$value,
array $parameters = array()) {
$map = array(
'^' => '^^',
"\n" => '^n',
'"' => "^'",
);
$writable_params = array();
foreach ($parameters as $k => $parameter) {
$value_list = array();
foreach ($parameter['values'] as $v) {
$v = str_replace(array_keys($map), array_values($map), $v);
// If the parameter value isn't a very simple one, quote it.
// RFC5545 says that we MUST quote it if it has a colon, a semicolon,
// or a comma, and that we MUST quote it if it's a URI.
if (!preg_match('/^[A-Za-z0-9-]*\z/', $v)) {
$v = '"'.$v.'"';
}
$value_list[] = $v;
}
$writable_params[] = array(
'name' => $parameter['name'],
'value' => implode(',', $value_list),
);
}
return array(
'name' => $name,
'value' => $value,
'parameters' => $writable_params,
);
}
}
diff --git a/src/applications/calendar/query/PhabricatorCalendarExportQuery.php b/src/applications/calendar/query/PhabricatorCalendarExportQuery.php
index c51671c806..7ef970216f 100644
--- a/src/applications/calendar/query/PhabricatorCalendarExportQuery.php
+++ b/src/applications/calendar/query/PhabricatorCalendarExportQuery.php
@@ -1,94 +1,90 @@
<?php
final class PhabricatorCalendarExportQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $authorPHIDs;
private $secretKeys;
private $isDisabled;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withAuthorPHIDs(array $phids) {
$this->authorPHIDs = $phids;
return $this;
}
public function withIsDisabled($is_disabled) {
$this->isDisabled = $is_disabled;
return $this;
}
public function withSecretKeys(array $keys) {
$this->secretKeys = $keys;
return $this;
}
public function newResultObject() {
return new PhabricatorCalendarExport();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'export.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'export.phid IN (%Ls)',
$this->phids);
}
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'export.authorPHID IN (%Ls)',
$this->authorPHIDs);
}
if ($this->isDisabled !== null) {
$where[] = qsprintf(
$conn,
'export.isDisabled = %d',
(int)$this->isDisabled);
}
if ($this->secretKeys !== null) {
$where[] = qsprintf(
$conn,
'export.secretKey IN (%Ls)',
$this->secretKeys);
}
return $where;
}
protected function getPrimaryTableAlias() {
return 'export';
}
public function getQueryApplicationClass() {
return 'PhabricatorCalendarApplication';
}
}
diff --git a/src/applications/calendar/query/PhabricatorCalendarExternalInviteeQuery.php b/src/applications/calendar/query/PhabricatorCalendarExternalInviteeQuery.php
index ea7200e614..35891cfd28 100644
--- a/src/applications/calendar/query/PhabricatorCalendarExternalInviteeQuery.php
+++ b/src/applications/calendar/query/PhabricatorCalendarExternalInviteeQuery.php
@@ -1,68 +1,64 @@
<?php
final class PhabricatorCalendarExternalInviteeQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $names;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withNames(array $names) {
$this->names = $names;
return $this;
}
public function newResultObject() {
return new PhabricatorCalendarExternalInvitee();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->names !== null) {
$name_indexes = array();
foreach ($this->names as $name) {
$name_indexes[] = PhabricatorHash::digestForIndex($name);
}
$where[] = qsprintf(
$conn,
'nameIndex IN (%Ls)',
$name_indexes);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorCalendarApplication';
}
}
diff --git a/src/applications/calendar/query/PhabricatorCalendarImportLogQuery.php b/src/applications/calendar/query/PhabricatorCalendarImportLogQuery.php
index 81f309bdca..731b1209cc 100644
--- a/src/applications/calendar/query/PhabricatorCalendarImportLogQuery.php
+++ b/src/applications/calendar/query/PhabricatorCalendarImportLogQuery.php
@@ -1,111 +1,107 @@
<?php
final class PhabricatorCalendarImportLogQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $importPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withImportPHIDs(array $phids) {
$this->importPHIDs = $phids;
return $this;
}
public function newResultObject() {
return new PhabricatorCalendarImportLog();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'log.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'log.phid IN (%Ls)',
$this->phids);
}
if ($this->importPHIDs !== null) {
$where[] = qsprintf(
$conn,
'log.importPHID IN (%Ls)',
$this->importPHIDs);
}
return $where;
}
protected function willFilterPage(array $page) {
$viewer = $this->getViewer();
$type_map = PhabricatorCalendarImportLogType::getAllLogTypes();
foreach ($page as $log) {
$type_constant = $log->getParameter('type');
$type_object = idx($type_map, $type_constant);
if (!$type_object) {
$type_object = new PhabricatorCalendarImportDefaultLogType();
}
$type_object = clone $type_object;
$log->attachLogType($type_object);
}
$import_phids = mpull($page, 'getImportPHID');
if ($import_phids) {
$imports = id(new PhabricatorCalendarImportQuery())
->setViewer($viewer)
->withPHIDs($import_phids)
->execute();
$imports = mpull($imports, null, 'getPHID');
} else {
$imports = array();
}
foreach ($page as $key => $log) {
$import = idx($imports, $log->getImportPHID());
if (!$import) {
$this->didRejectResult($import);
unset($page[$key]);
continue;
}
$log->attachImport($import);
}
return $page;
}
protected function getPrimaryTableAlias() {
return 'log';
}
public function getQueryApplicationClass() {
return 'PhabricatorCalendarApplication';
}
}
diff --git a/src/applications/calendar/query/PhabricatorCalendarImportQuery.php b/src/applications/calendar/query/PhabricatorCalendarImportQuery.php
index 5d44657994..0e3dbbf387 100644
--- a/src/applications/calendar/query/PhabricatorCalendarImportQuery.php
+++ b/src/applications/calendar/query/PhabricatorCalendarImportQuery.php
@@ -1,99 +1,95 @@
<?php
final class PhabricatorCalendarImportQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $authorPHIDs;
private $isDisabled;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withAuthorPHIDs(array $phids) {
$this->authorPHIDs = $phids;
return $this;
}
public function withIsDisabled($is_disabled) {
$this->isDisabled = $is_disabled;
return $this;
}
public function newResultObject() {
return new PhabricatorCalendarImport();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'import.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'import.phid IN (%Ls)',
$this->phids);
}
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'import.authorPHID IN (%Ls)',
$this->authorPHIDs);
}
if ($this->isDisabled !== null) {
$where[] = qsprintf(
$conn,
'import.isDisabled = %d',
(int)$this->isDisabled);
}
return $where;
}
protected function willFilterPage(array $page) {
$engines = PhabricatorCalendarImportEngine::getAllImportEngines();
foreach ($page as $key => $import) {
$engine_type = $import->getEngineType();
$engine = idx($engines, $engine_type);
if (!$engine) {
unset($page[$key]);
$this->didRejectResult($import);
continue;
}
$import->attachEngine(clone $engine);
}
return $page;
}
protected function getPrimaryTableAlias() {
return 'import';
}
public function getQueryApplicationClass() {
return 'PhabricatorCalendarApplication';
}
}
diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php
index bc200e7078..a4d5fdd568 100644
--- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php
@@ -1,755 +1,755 @@
<?php
final class PhabricatorConduitAPIController
extends PhabricatorConduitController {
public function shouldRequireLogin() {
return false;
}
public function handleRequest(AphrontRequest $request) {
$method = $request->getURIData('method');
$time_start = microtime(true);
$api_request = null;
$method_implementation = null;
$log = new PhabricatorConduitMethodCallLog();
$log->setMethod($method);
$metadata = array();
$multimeter = MultimeterControl::getInstance();
if ($multimeter) {
$multimeter->setEventContext('api.'.$method);
}
try {
list($metadata, $params, $strictly_typed) = $this->decodeConduitParams(
$request,
$method);
$call = new ConduitCall($method, $params, $strictly_typed);
$method_implementation = $call->getMethodImplementation();
$result = null;
// TODO: The relationship between ConduitAPIRequest and ConduitCall is a
// little odd here and could probably be improved. Specifically, the
// APIRequest is a sub-object of the Call, which does not parallel the
// role of AphrontRequest (which is an indepenent object).
// In particular, the setUser() and getUser() existing independently on
// the Call and APIRequest is very awkward.
$api_request = $call->getAPIRequest();
$allow_unguarded_writes = false;
$auth_error = null;
$conduit_username = '-';
if ($call->shouldRequireAuthentication()) {
$auth_error = $this->authenticateUser($api_request, $metadata, $method);
// If we've explicitly authenticated the user here and either done
// CSRF validation or are using a non-web authentication mechanism.
$allow_unguarded_writes = true;
if ($auth_error === null) {
$conduit_user = $api_request->getUser();
if ($conduit_user && $conduit_user->getPHID()) {
$conduit_username = $conduit_user->getUsername();
}
$call->setUser($api_request->getUser());
}
}
$access_log = PhabricatorAccessLog::getLog();
if ($access_log) {
$access_log->setData(
array(
'u' => $conduit_username,
'm' => $method,
));
}
if ($call->shouldAllowUnguardedWrites()) {
$allow_unguarded_writes = true;
}
if ($auth_error === null) {
if ($allow_unguarded_writes) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
}
try {
$result = $call->execute();
$error_code = null;
$error_info = null;
} catch (ConduitException $ex) {
$result = null;
$error_code = $ex->getMessage();
if ($ex->getErrorDescription()) {
$error_info = $ex->getErrorDescription();
} else {
$error_info = $call->getErrorDescription($error_code);
}
}
if ($allow_unguarded_writes) {
unset($unguarded);
}
} else {
list($error_code, $error_info) = $auth_error;
}
} catch (Exception $ex) {
$result = null;
if ($ex instanceof ConduitException) {
$error_code = 'ERR-CONDUIT-CALL';
} else {
$error_code = 'ERR-CONDUIT-CORE';
// See T13581. When a Conduit method raises an uncaught exception
// other than a "ConduitException", log it.
phlog($ex);
}
$error_info = $ex->getMessage();
}
$log
->setCallerPHID(
isset($conduit_user)
? $conduit_user->getPHID()
: null)
->setError((string)$error_code)
->setDuration(phutil_microseconds_since($time_start));
if (!PhabricatorEnv::isReadOnly()) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$log->save();
unset($unguarded);
}
$response = id(new ConduitAPIResponse())
->setResult($result)
->setErrorCode($error_code)
->setErrorInfo($error_info);
switch ($request->getStr('output')) {
case 'human':
return $this->buildHumanReadableResponse(
$method,
$api_request,
$response->toDictionary(),
$method_implementation);
case 'json':
default:
$response = id(new AphrontJSONResponse())
->setAddJSONShield(false)
->setContent($response->toDictionary());
$capabilities = $this->getConduitCapabilities();
if ($capabilities) {
$capabilities = implode(' ', $capabilities);
$response->addHeader('X-Conduit-Capabilities', $capabilities);
}
return $response;
}
}
/**
* Authenticate the client making the request to a Phabricator user account.
*
* @param ConduitAPIRequest Request being executed.
* @param dict Request metadata.
* @return null|pair Null to indicate successful authentication, or
* an error code and error message pair.
*/
private function authenticateUser(
ConduitAPIRequest $api_request,
array $metadata,
$method) {
$request = $this->getRequest();
if ($request->getUser()->getPHID()) {
$request->validateCSRF();
return $this->validateAuthenticatedUser(
$api_request,
$request->getUser());
}
$auth_type = idx($metadata, 'auth.type');
if ($auth_type === ConduitClient::AUTH_ASYMMETRIC) {
$host = idx($metadata, 'auth.host');
if (!$host) {
return array(
'ERR-INVALID-AUTH',
pht(
'Request is missing required "%s" parameter.',
'auth.host'),
);
}
// TODO: Validate that we are the host!
$raw_key = idx($metadata, 'auth.key');
$public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($raw_key);
$ssl_public_key = $public_key->toPKCS8();
// First, verify the signature.
try {
$protocol_data = $metadata;
ConduitClient::verifySignature(
$method,
$api_request->getAllParameters(),
$protocol_data,
$ssl_public_key);
} catch (Exception $ex) {
return array(
'ERR-INVALID-AUTH',
pht(
'Signature verification failure. %s',
$ex->getMessage()),
);
}
// If the signature is valid, find the user or device which is
// associated with this public key.
$stored_key = id(new PhabricatorAuthSSHKeyQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withKeys(array($public_key))
->withIsActive(true)
->executeOne();
if (!$stored_key) {
$key_summary = id(new PhutilUTF8StringTruncator())
->setMaximumBytes(64)
->truncateString($raw_key);
return array(
'ERR-INVALID-AUTH',
pht(
'No user or device is associated with the public key "%s".',
$key_summary),
);
}
$object = $stored_key->getObject();
if ($object instanceof PhabricatorUser) {
$user = $object;
} else {
if ($object->isDisabled()) {
return array(
'ERR-INVALID-AUTH',
pht(
'The key which signed this request is associated with a '.
'disabled device ("%s").',
$object->getName()),
);
}
if (!$stored_key->getIsTrusted()) {
return array(
'ERR-INVALID-AUTH',
pht(
'The key which signed this request is not trusted. Only '.
'trusted keys can be used to sign API calls.'),
);
}
if (!PhabricatorEnv::isClusterRemoteAddress()) {
return array(
'ERR-INVALID-AUTH',
pht(
- 'This request originates from outside of the Phabricator '.
- 'cluster address range. Requests signed with trusted '.
- 'device keys must originate from within the cluster.'),
+ 'This request originates from outside of the cluster address '.
+ 'range. Requests signed with trusted device keys must '.
+ 'originate from within the cluster.'),
);
}
$user = PhabricatorUser::getOmnipotentUser();
// Flag this as an intracluster request.
$api_request->setIsClusterRequest(true);
}
return $this->validateAuthenticatedUser(
$api_request,
$user);
} else if ($auth_type === null) {
// No specified authentication type, continue with other authentication
// methods below.
} else {
return array(
'ERR-INVALID-AUTH',
pht(
'Provided "%s" ("%s") is not recognized.',
'auth.type',
$auth_type),
);
}
$token_string = idx($metadata, 'token');
if (strlen($token_string)) {
if (strlen($token_string) != 32) {
return array(
'ERR-INVALID-AUTH',
pht(
'API token "%s" has the wrong length. API tokens should be '.
'32 characters long.',
$token_string),
);
}
$type = head(explode('-', $token_string));
$valid_types = PhabricatorConduitToken::getAllTokenTypes();
$valid_types = array_fuse($valid_types);
if (empty($valid_types[$type])) {
return array(
'ERR-INVALID-AUTH',
pht(
'API token "%s" has the wrong format. API tokens should be '.
'32 characters long and begin with one of these prefixes: %s.',
$token_string,
implode(', ', $valid_types)),
);
}
$token = id(new PhabricatorConduitTokenQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withTokens(array($token_string))
->withExpired(false)
->executeOne();
if (!$token) {
$token = id(new PhabricatorConduitTokenQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withTokens(array($token_string))
->withExpired(true)
->executeOne();
if ($token) {
return array(
'ERR-INVALID-AUTH',
pht(
'API token "%s" was previously valid, but has expired.',
$token_string),
);
} else {
return array(
'ERR-INVALID-AUTH',
pht(
'API token "%s" is not valid.',
$token_string),
);
}
}
// If this is a "cli-" token, it expires shortly after it is generated
// by default. Once it is actually used, we extend its lifetime and make
// it permanent. This allows stray tokens to get cleaned up automatically
// if they aren't being used.
if ($token->getTokenType() == PhabricatorConduitToken::TYPE_COMMANDLINE) {
if ($token->getExpires()) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$token->setExpires(null);
$token->save();
unset($unguarded);
}
}
// If this is a "clr-" token, Phabricator must be configured in cluster
// mode and the remote address must be a cluster node.
if ($token->getTokenType() == PhabricatorConduitToken::TYPE_CLUSTER) {
if (!PhabricatorEnv::isClusterRemoteAddress()) {
return array(
'ERR-INVALID-AUTH',
pht(
- 'This request originates from outside of the Phabricator '.
- 'cluster address range. Requests signed with cluster API '.
- 'tokens must originate from within the cluster.'),
+ 'This request originates from outside of the cluster address '.
+ 'range. Requests signed with cluster API tokens must '.
+ 'originate from within the cluster.'),
);
}
// Flag this as an intracluster request.
$api_request->setIsClusterRequest(true);
}
$user = $token->getObject();
if (!($user instanceof PhabricatorUser)) {
return array(
'ERR-INVALID-AUTH',
pht('API token is not associated with a valid user.'),
);
}
return $this->validateAuthenticatedUser(
$api_request,
$user);
}
$access_token = idx($metadata, 'access_token');
if ($access_token) {
$token = id(new PhabricatorOAuthServerAccessToken())
->loadOneWhere('token = %s', $access_token);
if (!$token) {
return array(
'ERR-INVALID-AUTH',
pht('Access token does not exist.'),
);
}
$oauth_server = new PhabricatorOAuthServer();
$authorization = $oauth_server->authorizeToken($token);
if (!$authorization) {
return array(
'ERR-INVALID-AUTH',
pht('Access token is invalid or expired.'),
);
}
$user = id(new PhabricatorPeopleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($token->getUserPHID()))
->executeOne();
if (!$user) {
return array(
'ERR-INVALID-AUTH',
pht('Access token is for invalid user.'),
);
}
$ok = $this->authorizeOAuthMethodAccess($authorization, $method);
if (!$ok) {
return array(
'ERR-OAUTH-ACCESS',
pht('You do not have authorization to call this method.'),
);
}
$api_request->setOAuthToken($token);
return $this->validateAuthenticatedUser(
$api_request,
$user);
}
// For intracluster requests, use a public user if no authentication
// information is provided. We could do this safely for any request,
// but making the API fully public means there's no way to disable badly
// behaved clients.
if (PhabricatorEnv::isClusterRemoteAddress()) {
if (PhabricatorEnv::getEnvConfig('policy.allow-public')) {
$api_request->setIsClusterRequest(true);
$user = new PhabricatorUser();
return $this->validateAuthenticatedUser(
$api_request,
$user);
}
}
// Handle sessionless auth.
// TODO: This is super messy.
// TODO: Remove this in favor of token-based auth.
if (isset($metadata['authUser'])) {
$user = id(new PhabricatorUser())->loadOneWhere(
'userName = %s',
$metadata['authUser']);
if (!$user) {
return array(
'ERR-INVALID-AUTH',
pht('Authentication is invalid.'),
);
}
$token = idx($metadata, 'authToken');
$signature = idx($metadata, 'authSignature');
$certificate = $user->getConduitCertificate();
$hash = sha1($token.$certificate);
if (!phutil_hashes_are_identical($hash, $signature)) {
return array(
'ERR-INVALID-AUTH',
pht('Authentication is invalid.'),
);
}
return $this->validateAuthenticatedUser(
$api_request,
$user);
}
// Handle session-based auth.
// TODO: Remove this in favor of token-based auth.
$session_key = idx($metadata, 'sessionKey');
if (!$session_key) {
return array(
'ERR-INVALID-SESSION',
pht('Session key is not present.'),
);
}
$user = id(new PhabricatorAuthSessionEngine())
->loadUserForSession(PhabricatorAuthSession::TYPE_CONDUIT, $session_key);
if (!$user) {
return array(
'ERR-INVALID-SESSION',
pht('Session key is invalid.'),
);
}
return $this->validateAuthenticatedUser(
$api_request,
$user);
}
private function validateAuthenticatedUser(
ConduitAPIRequest $request,
PhabricatorUser $user) {
if (!$user->canEstablishAPISessions()) {
return array(
'ERR-INVALID-AUTH',
pht('User account is not permitted to use the API.'),
);
}
$request->setUser($user);
id(new PhabricatorAuthSessionEngine())
->willServeRequestForUser($user);
return null;
}
private function buildHumanReadableResponse(
$method,
ConduitAPIRequest $request = null,
$result = null,
ConduitAPIMethod $method_implementation = null) {
$param_rows = array();
$param_rows[] = array('Method', $this->renderAPIValue($method));
if ($request) {
foreach ($request->getAllParameters() as $key => $value) {
$param_rows[] = array(
$key,
$this->renderAPIValue($value),
);
}
}
$param_table = new AphrontTableView($param_rows);
$param_table->setColumnClasses(
array(
'header',
'wide',
));
$result_rows = array();
foreach ($result as $key => $value) {
$result_rows[] = array(
$key,
$this->renderAPIValue($value),
);
}
$result_table = new AphrontTableView($result_rows);
$result_table->setColumnClasses(
array(
'header',
'wide',
));
$param_panel = id(new PHUIObjectBoxView())
->setHeaderText(pht('Method Parameters'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($param_table);
$result_panel = id(new PHUIObjectBoxView())
->setHeaderText(pht('Method Result'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($result_table);
$method_uri = $this->getApplicationURI('method/'.$method.'/');
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb($method, $method_uri)
->addTextCrumb(pht('Call'))
->setBorder(true);
$example_panel = null;
if ($request && $method_implementation) {
$params = $request->getAllParameters();
$example_panel = $this->renderExampleBox(
$method_implementation,
$params);
}
$title = pht('Method Call Result');
$header = id(new PHUIHeaderView())
->setHeader($title)
->setHeaderIcon('fa-exchange');
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$param_panel,
$result_panel,
$example_panel,
));
$title = pht('Method Call Result');
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
private function renderAPIValue($value) {
$json = new PhutilJSON();
if (is_array($value)) {
$value = $json->encodeFormatted($value);
}
$value = phutil_tag(
'pre',
array('style' => 'white-space: pre-wrap;'),
$value);
return $value;
}
private function decodeConduitParams(
AphrontRequest $request,
$method) {
$content_type = $request->getHTTPHeader('Content-Type');
if ($content_type == 'application/json') {
throw new Exception(
pht('Use form-encoded data to submit parameters to Conduit endpoints. '.
'Sending a JSON-encoded body and setting \'Content-Type\': '.
'\'application/json\' is not currently supported.'));
}
// Look for parameters from the Conduit API Console, which are encoded
// as HTTP POST parameters in an array, e.g.:
//
// params[name]=value&params[name2]=value2
//
// The fields are individually JSON encoded, since we require users to
// enter JSON so that we avoid type ambiguity.
$params = $request->getArr('params', null);
if ($params !== null) {
foreach ($params as $key => $value) {
if ($value == '') {
// Interpret empty string null (e.g., the user didn't type anything
// into the box).
$value = 'null';
}
$decoded_value = json_decode($value, true);
if ($decoded_value === null && strtolower($value) != 'null') {
// When json_decode() fails, it returns null. This almost certainly
// indicates that a user was using the web UI and didn't put quotes
// around a string value. We can either do what we think they meant
// (treat it as a string) or fail. For now, err on the side of
// caution and fail. In the future, if we make the Conduit API
// actually do type checking, it might be reasonable to treat it as
// a string if the parameter type is string.
throw new Exception(
pht(
"The value for parameter '%s' is not valid JSON. All ".
"parameters must be encoded as JSON values, including strings ".
"(which means you need to surround them in double quotes). ".
"Check your syntax. Value was: %s.",
$key,
$value));
}
$params[$key] = $decoded_value;
}
$metadata = idx($params, '__conduit__', array());
unset($params['__conduit__']);
return array($metadata, $params, true);
}
// Otherwise, look for a single parameter called 'params' which has the
// entire param dictionary JSON encoded.
$params_json = $request->getStr('params');
if (strlen($params_json)) {
$params = null;
try {
$params = phutil_json_decode($params_json);
} catch (PhutilJSONParserException $ex) {
throw new PhutilProxyException(
pht(
"Invalid parameter information was passed to method '%s'.",
$method),
$ex);
}
$metadata = idx($params, '__conduit__', array());
unset($params['__conduit__']);
return array($metadata, $params, true);
}
// If we do not have `params`, assume this is a simple HTTP request with
// HTTP key-value pairs.
$params = array();
$metadata = array();
foreach ($request->getPassthroughRequestData() as $key => $value) {
$meta_key = ConduitAPIMethod::getParameterMetadataKey($key);
if ($meta_key !== null) {
$metadata[$meta_key] = $value;
} else {
$params[$key] = $value;
}
}
return array($metadata, $params, false);
}
private function authorizeOAuthMethodAccess(
PhabricatorOAuthClientAuthorization $authorization,
$method_name) {
$method = ConduitAPIMethod::getConduitMethod($method_name);
if (!$method) {
return false;
}
$required_scope = $method->getRequiredScope();
switch ($required_scope) {
case ConduitAPIMethod::SCOPE_ALWAYS:
return true;
case ConduitAPIMethod::SCOPE_NEVER:
return false;
}
$authorization_scope = $authorization->getScope();
if (!empty($authorization_scope[$required_scope])) {
return true;
}
return false;
}
private function getConduitCapabilities() {
$capabilities = array();
if (AphrontRequestStream::supportsGzip()) {
$capabilities[] = 'gzip';
}
return $capabilities;
}
}
diff --git a/src/applications/conduit/controller/PhabricatorConduitTokenEditController.php b/src/applications/conduit/controller/PhabricatorConduitTokenEditController.php
index 7550f92210..d002ea5157 100644
--- a/src/applications/conduit/controller/PhabricatorConduitTokenEditController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitTokenEditController.php
@@ -1,108 +1,108 @@
<?php
final class PhabricatorConduitTokenEditController
extends PhabricatorConduitController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
if ($id) {
$token = id(new PhabricatorConduitTokenQuery())
->setViewer($viewer)
->withIDs(array($id))
->withExpired(false)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$token) {
return new Aphront404Response();
}
$object = $token->getObject();
$is_new = false;
$title = pht('View API Token');
} else {
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(array($request->getStr('objectPHID')))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$object) {
return new Aphront404Response();
}
$token = PhabricatorConduitToken::initializeNewToken(
$object->getPHID(),
PhabricatorConduitToken::TYPE_STANDARD);
$is_new = true;
$title = pht('Generate API Token');
$submit_button = pht('Generate Token');
}
$panel_uri = id(new PhabricatorConduitTokensSettingsPanel())
->setViewer($viewer)
->setUser($object)
->getPanelURI();
id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
$request,
$panel_uri);
if ($request->isFormPost()) {
$token->save();
if ($is_new) {
$token_uri = '/conduit/token/edit/'.$token->getID().'/';
} else {
$token_uri = $panel_uri;
}
return id(new AphrontRedirectResponse())->setURI($token_uri);
}
$dialog = $this->newDialog()
->setTitle($title)
->addHiddenInput('objectPHID', $object->getPHID());
if ($is_new) {
$dialog
->appendParagraph(pht('Generate a new API token?'))
->addSubmitButton($submit_button)
->addCancelButton($panel_uri);
} else {
$form = id(new AphrontFormView())
->setUser($viewer);
if ($token->getTokenType() === PhabricatorConduitToken::TYPE_CLUSTER) {
$dialog->appendChild(
pht(
- 'This token is automatically generated by Phabricator, and used '.
- 'to make requests between nodes in a Phabricator cluster. You '.
- 'can not use this token in external applications.'));
+ 'This token is automatically generated, and used to make '.
+ 'requests between nodes in a cluster. You can not use this '.
+ 'token in external applications.'));
} else {
$form->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Token'))
->setValue($token->getToken()));
}
$dialog
->appendForm($form)
->addCancelButton($panel_uri, pht('Done'));
}
return $dialog;
}
}
diff --git a/src/applications/conduit/method/ConduitConnectConduitAPIMethod.php b/src/applications/conduit/method/ConduitConnectConduitAPIMethod.php
index 8119deb860..9b02dca353 100644
--- a/src/applications/conduit/method/ConduitConnectConduitAPIMethod.php
+++ b/src/applications/conduit/method/ConduitConnectConduitAPIMethod.php
@@ -1,155 +1,155 @@
<?php
final class ConduitConnectConduitAPIMethod extends ConduitAPIMethod {
public function getAPIMethodName() {
return 'conduit.connect';
}
public function shouldRequireAuthentication() {
return false;
}
public function shouldAllowUnguardedWrites() {
return true;
}
public function getMethodDescription() {
return pht('Connect a session-based client.');
}
protected function defineParamTypes() {
return array(
'client' => 'required string',
'clientVersion' => 'required int',
'clientDescription' => 'optional string',
'user' => 'optional string',
'authToken' => 'optional int',
'authSignature' => 'optional string',
'host' => 'deprecated',
);
}
protected function defineReturnType() {
return 'dict<string, any>';
}
protected function defineErrorTypes() {
return array(
'ERR-BAD-VERSION' => pht(
'Client/server version mismatch. Upgrade your server or downgrade '.
'your client.'),
'NEW-ARC-VERSION' => pht(
'Client/server version mismatch. Upgrade your client.'),
'ERR-UNKNOWN-CLIENT' => pht('Client is unknown.'),
'ERR-INVALID-USER' => pht(
'The username you are attempting to authenticate with is not valid.'),
'ERR-INVALID-CERTIFICATE' => pht(
'Your authentication certificate for this server is invalid.'),
'ERR-INVALID-TOKEN' => pht(
"The challenge token you are authenticating with is outside of the ".
"allowed time range. Either your system clock is out of whack or ".
"you're executing a replay attack."),
'ERR-NO-CERTIFICATE' => pht('This server requires authentication.'),
);
}
protected function execute(ConduitAPIRequest $request) {
$client = $request->getValue('client');
$client_version = (int)$request->getValue('clientVersion');
$client_description = (string)$request->getValue('clientDescription');
$client_description = id(new PhutilUTF8StringTruncator())
->setMaximumBytes(255)
->truncateString($client_description);
$username = (string)$request->getValue('user');
switch ($client) {
case 'arc':
$server_version = 6;
$supported_versions = array(
$server_version => true,
// Client version 5 introduced "user.query" call
4 => true,
// Client version 6 introduced "diffusion.getlintmessages" call
5 => true,
);
if (empty($supported_versions[$client_version])) {
if ($server_version < $client_version) {
$ex = new ConduitException('ERR-BAD-VERSION');
$ex->setErrorDescription(
pht(
"Your '%s' client version is '%d', which is newer than the ".
- "server version, '%d'. Upgrade your Phabricator install.",
+ "server version, '%d'. Upgrade your server.",
'arc',
$client_version,
$server_version));
} else {
$ex = new ConduitException('NEW-ARC-VERSION');
$ex->setErrorDescription(
pht(
'A new version of arc is available! You need to upgrade '.
'to connect to this server (you are running version '.
'%d, the server is running version %d).',
$client_version,
$server_version));
}
throw $ex;
}
break;
default:
// Allow new clients by default.
break;
}
$token = $request->getValue('authToken');
$signature = $request->getValue('authSignature');
$user = id(new PhabricatorUser())->loadOneWhere('username = %s', $username);
if (!$user) {
throw new ConduitException('ERR-INVALID-USER');
}
$session_key = null;
if ($token && $signature) {
$threshold = 60 * 15;
$now = time();
if (abs($token - $now) > $threshold) {
throw id(new ConduitException('ERR-INVALID-TOKEN'))
->setErrorDescription(
pht(
'The request you submitted is signed with a timestamp, but that '.
'timestamp is not within %s of the current time. The '.
'signed timestamp is %s (%s), and the current server time is '.
'%s (%s). This is a difference of %s seconds, but the '.
'timestamp must differ from the server time by no more than '.
'%s seconds. Your client or server clock may not be set '.
'correctly.',
phutil_format_relative_time($threshold),
$token,
date('r', $token),
$now,
date('r', $now),
($token - $now),
$threshold));
}
$valid = sha1($token.$user->getConduitCertificate());
if (!phutil_hashes_are_identical($valid, $signature)) {
throw new ConduitException('ERR-INVALID-CERTIFICATE');
}
$session_key = id(new PhabricatorAuthSessionEngine())->establishSession(
PhabricatorAuthSession::TYPE_CONDUIT,
$user->getPHID(),
$partial = false);
} else {
throw new ConduitException('ERR-NO-CERTIFICATE');
}
return array(
'connectionID' => mt_rand(),
'sessionKey' => $session_key,
'userPHID' => $user->getPHID(),
);
}
}
diff --git a/src/applications/conduit/query/PhabricatorConduitLogQuery.php b/src/applications/conduit/query/PhabricatorConduitLogQuery.php
index 23a5b46786..19cb31a812 100644
--- a/src/applications/conduit/query/PhabricatorConduitLogQuery.php
+++ b/src/applications/conduit/query/PhabricatorConduitLogQuery.php
@@ -1,117 +1,113 @@
<?php
final class PhabricatorConduitLogQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $callerPHIDs;
private $methods;
private $methodStatuses;
private $epochMin;
private $epochMax;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withCallerPHIDs(array $phids) {
$this->callerPHIDs = $phids;
return $this;
}
public function withMethods(array $methods) {
$this->methods = $methods;
return $this;
}
public function withMethodStatuses(array $statuses) {
$this->methodStatuses = $statuses;
return $this;
}
public function withEpochBetween($epoch_min, $epoch_max) {
$this->epochMin = $epoch_min;
$this->epochMax = $epoch_max;
return $this;
}
public function newResultObject() {
return new PhabricatorConduitMethodCallLog();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->callerPHIDs !== null) {
$where[] = qsprintf(
$conn,
'callerPHID IN (%Ls)',
$this->callerPHIDs);
}
if ($this->methods !== null) {
$where[] = qsprintf(
$conn,
'method IN (%Ls)',
$this->methods);
}
if ($this->methodStatuses !== null) {
$statuses = array_fuse($this->methodStatuses);
$methods = id(new PhabricatorConduitMethodQuery())
->setViewer($this->getViewer())
->execute();
$method_names = array();
foreach ($methods as $method) {
$status = $method->getMethodStatus();
if (isset($statuses[$status])) {
$method_names[] = $method->getAPIMethodName();
}
}
if (!$method_names) {
throw new PhabricatorEmptyQueryException();
}
$where[] = qsprintf(
$conn,
'method IN (%Ls)',
$method_names);
}
if ($this->epochMin !== null) {
$where[] = qsprintf(
$conn,
'dateCreated >= %d',
$this->epochMin);
}
if ($this->epochMax !== null) {
$where[] = qsprintf(
$conn,
'dateCreated <= %d',
$this->epochMax);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorConduitApplication';
}
}
diff --git a/src/applications/conduit/query/PhabricatorConduitSearchEngine.php b/src/applications/conduit/query/PhabricatorConduitSearchEngine.php
index 9d244401e6..06ba536dfe 100644
--- a/src/applications/conduit/query/PhabricatorConduitSearchEngine.php
+++ b/src/applications/conduit/query/PhabricatorConduitSearchEngine.php
@@ -1,192 +1,192 @@
<?php
final class PhabricatorConduitSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
return pht('Conduit Methods');
}
public function getApplicationClassName() {
return 'PhabricatorConduitApplication';
}
public function canUseInPanelContext() {
return false;
}
public function getPageSize(PhabricatorSavedQuery $saved) {
return PHP_INT_MAX - 1;
}
public function buildSavedQueryFromRequest(AphrontRequest $request) {
$saved = new PhabricatorSavedQuery();
$saved->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(
'<strong>%s</strong>: %s',
pht('Stable Methods'),
pht('Show established API methods with stable interfaces.')),
$is_stable)
->addCheckbox(
'isUnstable',
1,
hsprintf(
'<strong>%s</strong>: %s',
pht('Unstable Methods'),
pht('Show new methods which are subject to change.')),
$is_unstable)
->addCheckbox(
'isDeprecated',
1,
hsprintf(
'<strong>%s</strong>: %s',
pht('Deprecated Methods'),
pht(
'Show old methods which will be deleted in a future '.
- 'version of Phabricator.')),
+ 'version of this software.')),
$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/conduit/query/PhabricatorConduitTokenQuery.php b/src/applications/conduit/query/PhabricatorConduitTokenQuery.php
index ef35b006a7..384efccadf 100644
--- a/src/applications/conduit/query/PhabricatorConduitTokenQuery.php
+++ b/src/applications/conduit/query/PhabricatorConduitTokenQuery.php
@@ -1,119 +1,115 @@
<?php
final class PhabricatorConduitTokenQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $objectPHIDs;
private $expired;
private $tokens;
private $tokenTypes;
public function withExpired($expired) {
$this->expired = $expired;
return $this;
}
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withObjectPHIDs(array $phids) {
$this->objectPHIDs = $phids;
return $this;
}
public function withTokens(array $tokens) {
$this->tokens = $tokens;
return $this;
}
public function withTokenTypes(array $types) {
$this->tokenTypes = $types;
return $this;
}
public function newResultObject() {
return new PhabricatorConduitToken();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->objectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'objectPHID IN (%Ls)',
$this->objectPHIDs);
}
if ($this->tokens !== null) {
$where[] = qsprintf(
$conn,
'token IN (%Ls)',
$this->tokens);
}
if ($this->tokenTypes !== null) {
$where[] = qsprintf(
$conn,
'tokenType IN (%Ls)',
$this->tokenTypes);
}
if ($this->expired !== null) {
if ($this->expired) {
$where[] = qsprintf(
$conn,
'expires <= %d',
PhabricatorTime::getNow());
} else {
$where[] = qsprintf(
$conn,
'expires IS NULL OR expires > %d',
PhabricatorTime::getNow());
}
}
return $where;
}
protected function willFilterPage(array $tokens) {
$object_phids = mpull($tokens, 'getObjectPHID');
$objects = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($object_phids)
->execute();
$objects = mpull($objects, null, 'getPHID');
foreach ($tokens as $key => $token) {
$object = idx($objects, $token->getObjectPHID(), null);
if (!$object) {
$this->didRejectResult($token);
unset($tokens[$key]);
continue;
}
$token->attachObject($object);
}
return $tokens;
}
public function getQueryApplicationClass() {
return 'PhabricatorConduitApplication';
}
}
diff --git a/src/applications/config/application/PhabricatorConfigApplication.php b/src/applications/config/application/PhabricatorConfigApplication.php
index e7a1b7eb1b..22731ac822 100644
--- a/src/applications/config/application/PhabricatorConfigApplication.php
+++ b/src/applications/config/application/PhabricatorConfigApplication.php
@@ -1,79 +1,79 @@
<?php
final class PhabricatorConfigApplication extends PhabricatorApplication {
public function getBaseURI() {
return '/config/';
}
public function getIcon() {
return 'fa-sliders';
}
public function isPinnedByDefault(PhabricatorUser $viewer) {
return $viewer->getIsAdmin();
}
public function getTitleGlyph() {
return "\xE2\x9C\xA8";
}
public function getApplicationGroup() {
return self::GROUP_ADMIN;
}
public function canUninstall() {
return false;
}
public function getName() {
return pht('Config');
}
public function getShortDescription() {
- return pht('Configure Phabricator');
+ return pht('Configure %s', PlatformSymbols::getPlatformServerName());
}
public function getRoutes() {
return array(
'/config/' => array(
'' => 'PhabricatorConfigConsoleController',
'edit/(?P<key>[\w\.\-]+)/' => 'PhabricatorConfigEditController',
'database/'.
'(?:(?P<ref>[^/]+)/'.
'(?:(?P<database>[^/]+)/'.
'(?:(?P<table>[^/]+)/'.
'(?:(?:col/(?P<column>[^/]+)|key/(?P<key>[^/]+))/)?)?)?)?'
=> 'PhabricatorConfigDatabaseStatusController',
'dbissue/' => 'PhabricatorConfigDatabaseIssueController',
'(?P<verb>ignore|unignore)/(?P<key>[^/]+)/'
=> 'PhabricatorConfigIgnoreController',
'issue/' => array(
'' => 'PhabricatorConfigIssueListController',
'panel/' => 'PhabricatorConfigIssuePanelController',
'(?P<key>[^/]+)/' => 'PhabricatorConfigIssueViewController',
),
'cache/' => array(
'' => 'PhabricatorConfigCacheController',
'purge/' => 'PhabricatorConfigPurgeCacheController',
),
'module/' => array(
'(?:(?P<module>[^/]+)/)?' => 'PhabricatorConfigModuleController',
),
'cluster/' => array(
'databases/' => 'PhabricatorConfigClusterDatabasesController',
'notifications/' => 'PhabricatorConfigClusterNotificationsController',
'repositories/' => 'PhabricatorConfigClusterRepositoriesController',
'search/' => 'PhabricatorConfigClusterSearchController',
),
'settings/' => array(
'' => 'PhabricatorConfigSettingsListController',
'(?P<filter>advanced|all)/'
=> 'PhabricatorConfigSettingsListController',
'history/' => 'PhabricatorConfigSettingsHistoryController',
),
),
);
}
}
diff --git a/src/applications/config/check/PhabricatorBaseURISetupCheck.php b/src/applications/config/check/PhabricatorBaseURISetupCheck.php
index bf3ac0b071..92e46641d7 100644
--- a/src/applications/config/check/PhabricatorBaseURISetupCheck.php
+++ b/src/applications/config/check/PhabricatorBaseURISetupCheck.php
@@ -1,104 +1,104 @@
<?php
final class PhabricatorBaseURISetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_IMPORTANT;
}
protected function executeChecks() {
$base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
$host_header = AphrontRequest::getHTTPHeader('Host');
if (strpos($host_header, '.') === false) {
if (!strlen(trim($host_header))) {
$name = pht('No "Host" Header');
$summary = pht('No "Host" header present in request.');
$message = pht(
'This request did not include a "Host" header. This may mean that '.
'your webserver (like nginx or apache) is misconfigured so the '.
- '"Host" header is not making it to Phabricator, or that you are '.
+ '"Host" header is not making it to this software, or that you are '.
'making a raw request without a "Host" header using a tool or '.
'library.'.
"\n\n".
'If you are using a web browser, check your webserver '.
'configuration. If you are using a tool or library, check how the '.
'request is being constructed.'.
"\n\n".
'It is also possible (but very unlikely) that some other network '.
'device (like a load balancer) is stripping the header.'.
"\n\n".
'Requests must include a valid "Host" header.');
} else {
$name = pht('Bad "Host" Header');
$summary = pht('Request has bad "Host" header.');
$message = pht(
'This request included an invalid "Host" header, with value "%s". '.
'Host headers must contain a dot ("."), like "example.com". This '.
'is required for some browsers to be able to set cookies.'.
"\n\n".
'This may mean the base URI is configured incorrectly. You must '.
- 'serve Phabricator from a base URI with a dot (like '.
- '"https://phabricator.mycompany.com"), not a bare domain '.
- '(like "https://phabricator/"). If you are trying to use a bare '.
+ 'serve this software from a base URI with a dot (like '.
+ '"https://devtools.example.com"), not a bare domain '.
+ '(like "https://devtools/"). If you are trying to use a bare '.
'domain, change your configuration to use a full domain with a dot '.
'in it instead.'.
"\n\n".
'This might also mean that your webserver (or some other network '.
'device, like a load balancer) is mangling the "Host" header, or '.
'you are using a tool or library to issue a request manually and '.
'setting the wrong "Host" header.'.
"\n\n".
'Requests must include a valid "Host" header.',
$host_header);
}
$this
->newIssue('request.host')
->setName($name)
->setSummary($summary)
->setMessage($message)
->setIsFatal(true);
}
if ($base_uri) {
return;
}
$base_uri_guess = PhabricatorEnv::getRequestBaseURI();
$summary = pht(
'The base URI for this install is not configured. Many major features '.
'will not work properly until you configure it.');
$message = pht(
'The base URI for this install is not configured, and major features '.
'will not work properly until you configure it.'.
"\n\n".
'You should set the base URI to the URI you will use to access '.
- 'Phabricator, like "http://phabricator.example.com/".'.
+ 'this server, like "http://devtools.example.com/".'.
"\n\n".
'Include the protocol (http or https), domain name, and port number if '.
'you are using a port other than 80 (http) or 443 (https).'.
"\n\n".
'Based on this request, it appears that the correct setting is:'.
"\n\n".
'%s'.
"\n\n".
'To configure the base URI, run the command shown below.',
$base_uri_guess);
$this
->newIssue('config.phabricator.base-uri')
->setShortName(pht('No Base URI'))
->setName(pht('Base URI Not Configured'))
->setSummary($summary)
->setMessage($message)
->addCommand(
hsprintf(
- '<tt>phabricator/ $</tt> %s',
+ '<tt>$</tt> %s',
csprintf(
'./bin/config set phabricator.base-uri %s',
$base_uri_guess)));
}
}
diff --git a/src/applications/config/check/PhabricatorBinariesSetupCheck.php b/src/applications/config/check/PhabricatorBinariesSetupCheck.php
index 5b8f93346c..b87282fcc7 100644
--- a/src/applications/config/check/PhabricatorBinariesSetupCheck.php
+++ b/src/applications/config/check/PhabricatorBinariesSetupCheck.php
@@ -1,257 +1,256 @@
<?php
final class PhabricatorBinariesSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_OTHER;
}
protected function executeChecks() {
if (phutil_is_windows()) {
$bin_name = 'where';
} else {
$bin_name = 'which';
}
if (!Filesystem::binaryExists($bin_name)) {
$message = pht(
- "Without '%s', Phabricator can not test for the availability ".
+ "Without '%s', this software can not test for the availability ".
"of other binaries.",
$bin_name);
$this->raiseWarning($bin_name, $message);
// We need to return here if we can't find the 'which' / 'where' binary
// because the other tests won't be valid.
return;
}
if (!Filesystem::binaryExists('diff')) {
$message = pht(
- "Without '%s', Phabricator will not be able to generate or render ".
+ "Without '%s', this software will not be able to generate or render ".
"diffs in multiple applications.",
'diff');
$this->raiseWarning('diff', $message);
} else {
$tmp_a = new TempFile();
$tmp_b = new TempFile();
$tmp_c = new TempFile();
Filesystem::writeFile($tmp_a, 'A');
Filesystem::writeFile($tmp_b, 'A');
Filesystem::writeFile($tmp_c, 'B');
list($err) = exec_manual('diff %s %s', $tmp_a, $tmp_b);
if ($err) {
$this->newIssue('bin.diff.same')
->setName(pht("Unexpected '%s' Behavior", 'diff'))
->setMessage(
pht(
"The '%s' binary on this system has unexpected behavior: ".
"it was expected to exit without an error code when passed ".
"identical files, but exited with code %d.",
'diff',
$err));
}
list($err) = exec_manual('diff %s %s', $tmp_a, $tmp_c);
if (!$err) {
$this->newIssue('bin.diff.diff')
->setName(pht("Unexpected 'diff' Behavior"))
->setMessage(
pht(
"The '%s' binary on this system has unexpected behavior: ".
"it was expected to exit with a nonzero error code when passed ".
"differing files, but did not.",
'diff'));
}
}
$table = new PhabricatorRepository();
$vcses = queryfx_all(
$table->establishConnection('r'),
'SELECT DISTINCT versionControlSystem FROM %T',
$table->getTableName());
foreach ($vcses as $vcs) {
switch ($vcs['versionControlSystem']) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$binary = 'git';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$binary = 'svn';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$binary = 'hg';
break;
default:
$binary = null;
break;
}
if (!$binary) {
continue;
}
if (!Filesystem::binaryExists($binary)) {
$message = pht(
'You have at least one repository configured which uses this '.
'version control system. It will not work without the VCS binary.');
$this->raiseWarning($binary, $message);
continue;
}
$version = PhutilBinaryAnalyzer::getForBinary($binary)
->getBinaryVersion();
switch ($vcs['versionControlSystem']) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$bad_versions = array();
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$bad_versions = array(
// We need 1.5 for "--depth", see T7228.
'< 1.5' => pht(
'The minimum supported version of Subversion is 1.5, which '.
'was released in 2008.'),
'= 1.7.1' => pht(
'This version of Subversion has a bug where `%s` does not work '.
'for files added in rN (Subversion issue #2873), fixed in 1.7.2.',
'svn diff -c N'),
);
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$bad_versions = array(
// We need 2.4 for utilizing `{p1node}` keyword in templates, see
// D21679 and D21681.
'< 2.4' => pht(
'The minimum supported version of Mercurial is 2.4, which was '.
'released in 2012.'),
);
break;
}
if ($version === null) {
$this->raiseUnknownVersionWarning($binary);
} else {
$version_details = array();
foreach ($bad_versions as $spec => $details) {
list($operator, $bad_version) = explode(' ', $spec, 2);
$is_bad = version_compare($version, $bad_version, $operator);
if ($is_bad) {
$version_details[] = pht(
'(%s%s) %s',
$operator,
$bad_version,
$details);
}
}
if ($version_details) {
$this->raiseBadVersionWarning(
$binary,
$version,
$version_details);
}
}
}
}
private function raiseWarning($bin, $message) {
if (phutil_is_windows()) {
$preamble = pht(
"The '%s' binary could not be found. Set the webserver's %s ".
"environmental variable to include the directory where it resides, or ".
- "add that directory to '%s' in the Phabricator configuration.",
+ "add that directory to '%s' in configuration.",
$bin,
'PATH',
'environment.append-paths');
} else {
$preamble = pht(
"The '%s' binary could not be found. Symlink it into '%s', or set the ".
"webserver's %s environmental variable to include the directory where ".
- "it resides, or add that directory to '%s' in the Phabricator ".
- "configuration.",
+ "it resides, or add that directory to '%s' in configuration.",
$bin,
- 'phabricator/support/bin/',
+ 'support/bin/',
'PATH',
'environment.append-paths');
}
$this->newIssue('bin.'.$bin)
->setShortName(pht("'%s' Missing", $bin))
->setName(pht("Missing '%s' Binary", $bin))
->setSummary(
pht("The '%s' binary could not be located or executed.", $bin))
->setMessage($preamble.' '.$message)
->addPhabricatorConfig('environment.append-paths');
}
private function raiseUnknownVersionWarning($binary) {
$summary = pht(
'Unable to determine the version number of "%s".',
$binary);
$message = pht(
'Unable to determine the version number of "%s". Usually, this means '.
- 'the program changed its version format string recently and Phabricator '.
- 'does not know how to parse the new one yet, but might indicate that '.
- 'you have a very old (or broken) binary.'.
+ 'the program changed its version format string recently and this '.
+ 'software does not know how to parse the new one yet, but might '.
+ 'indicate that you have a very old (or broken) binary.'.
"\n\n".
'Because we can not determine the version number, checks against '.
'minimum and known-bad versions will be skipped, so we might fail '.
'to detect an incompatible binary.'.
"\n\n".
- 'You may be able to resolve this issue by updating Phabricator, since '.
- 'a newer version of Phabricator is likely to be able to parse the '.
+ 'You may be able to resolve this issue by updating this server, since '.
+ 'a newer version of the software is likely to be able to parse the '.
'newer version string.'.
"\n\n".
- 'If updating Phabricator does not fix this, you can report the issue '.
+ 'If updating the software does not fix this, you can report the issue '.
'to the upstream so we can adjust the parser.'.
"\n\n".
'If you are confident you have a recent version of "%s" installed and '.
'working correctly, it is usually safe to ignore this warning.',
$binary,
$binary);
$this->newIssue('bin.'.$binary.'.unknown-version')
->setShortName(pht("Unknown '%s' Version", $binary))
->setName(pht("Unknown '%s' Version", $binary))
->setSummary($summary)
->setMessage($message)
->addLink(
PhabricatorEnv::getDoclink('Contributing Bug Reports'),
pht('Report this Issue to the Upstream'));
}
private function raiseBadVersionWarning($binary, $version, array $problems) {
$summary = pht(
'This server has a known bad version of "%s".',
$binary);
$message = array();
$message[] = pht(
'This server has a known bad version of "%s" installed ("%s"). This '.
'version is not supported, or contains important bugs or security '.
'vulnerabilities which are fixed in a newer version.',
$binary,
$version);
$message[] = pht('You should upgrade this software.');
$message[] = pht('The known issues with this old version are:');
foreach ($problems as $problem) {
$message[] = $problem;
}
$message = implode("\n\n", $message);
$this->newIssue("bin.{$binary}.bad-version")
->setName(pht('Unsupported/Insecure "%s" Version', $binary))
->setSummary($summary)
->setMessage($message);
}
}
diff --git a/src/applications/config/check/PhabricatorDaemonsSetupCheck.php b/src/applications/config/check/PhabricatorDaemonsSetupCheck.php
index 7ef44dc384..608bac675e 100644
--- a/src/applications/config/check/PhabricatorDaemonsSetupCheck.php
+++ b/src/applications/config/check/PhabricatorDaemonsSetupCheck.php
@@ -1,101 +1,100 @@
<?php
final class PhabricatorDaemonsSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_IMPORTANT;
}
protected function executeChecks() {
try {
$task_daemons = id(new PhabricatorDaemonLogQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
->withDaemonClasses(array('PhabricatorTaskmasterDaemon'))
->setLimit(1)
->execute();
$no_daemons = !$task_daemons;
} catch (Exception $ex) {
// Just skip this warning if the query fails for some reason.
$no_daemons = false;
}
if ($no_daemons) {
$doc_href = PhabricatorEnv::getDoclink('Managing Daemons with phd');
$summary = pht(
- 'You must start the Phabricator daemons to send email, rebuild '.
- 'search indexes, and do other background processing.');
+ 'You must start the daemons to send email, rebuild search indexes, '.
+ 'and do other background processing.');
$message = pht(
- 'The Phabricator daemons are not running, so Phabricator will not '.
- 'be able to perform background processing (including sending email, '.
- 'rebuilding search indexes, importing commits, cleaning up old data, '.
- 'and running builds).'.
+ 'The daemons are not running, background processing (including '.
+ 'sending email, rebuilding search indexes, importing commits, '.
+ 'cleaning up old data, and running builds) can not be performed.'.
"\n\n".
'Use %s to start daemons. See %s for more information.',
phutil_tag('tt', array(), 'bin/phd start'),
phutil_tag(
'a',
array(
'href' => $doc_href,
'target' => '_blank',
),
pht('Managing Daemons with phd')));
$this->newIssue('daemons.not-running')
->setShortName(pht('Daemons Not Running'))
- ->setName(pht('Phabricator Daemons Are Not Running'))
+ ->setName(pht('Daemons Are Not Running'))
->setSummary($summary)
->setMessage($message)
- ->addCommand('phabricator/ $ ./bin/phd start');
+ ->addCommand('$ ./bin/phd start');
}
$expect_user = PhabricatorEnv::getEnvConfig('phd.user');
if (strlen($expect_user)) {
try {
$all_daemons = id(new PhabricatorDaemonLogQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
->execute();
} catch (Exception $ex) {
// If this query fails for some reason, just skip this check.
$all_daemons = array();
}
foreach ($all_daemons as $daemon) {
$actual_user = $daemon->getRunningAsUser();
if ($actual_user == $expect_user) {
continue;
}
$summary = pht(
'At least one daemon is currently running as the wrong user.');
$message = pht(
'A daemon is running as user %s, but daemons should be '.
'running as %s.'.
"\n\n".
'Either adjust the configuration setting %s or restart the '.
'daemons. Daemons should attempt to run as the proper user when '.
'restarted.',
phutil_tag('tt', array(), $actual_user),
phutil_tag('tt', array(), $expect_user),
phutil_tag('tt', array(), 'phd.user'));
$this->newIssue('daemons.run-as-different-user')
->setName(pht('Daemon Running as Wrong User'))
->setSummary($summary)
->setMessage($message)
->addPhabricatorConfig('phd.user')
- ->addCommand('phabricator/ $ ./bin/phd restart');
+ ->addCommand('$ ./bin/phd restart');
break;
}
}
}
}
diff --git a/src/applications/config/check/PhabricatorDatabaseSetupCheck.php b/src/applications/config/check/PhabricatorDatabaseSetupCheck.php
index e48e22196e..a5989f37b3 100644
--- a/src/applications/config/check/PhabricatorDatabaseSetupCheck.php
+++ b/src/applications/config/check/PhabricatorDatabaseSetupCheck.php
@@ -1,242 +1,242 @@
<?php
final class PhabricatorDatabaseSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_IMPORTANT;
}
public function getExecutionOrder() {
// This must run after basic PHP checks, but before most other checks.
return 500;
}
protected function executeChecks() {
$host = PhabricatorEnv::getEnvConfig('mysql.host');
$matches = null;
if (preg_match('/^([^:]+):(\d+)$/', $host, $matches)) {
$host = $matches[1];
$port = $matches[2];
$this->newIssue('storage.mysql.hostport')
->setName(pht('Deprecated mysql.host Format'))
->setSummary(
pht(
'Move port information from `%s` to `%s` in your config.',
'mysql.host',
'mysql.port'))
->setMessage(
pht(
'Your `%s` configuration contains a port number, but this usage '.
'is deprecated. Instead, put the port number in `%s`.',
'mysql.host',
'mysql.port'))
->addPhabricatorConfig('mysql.host')
->addPhabricatorConfig('mysql.port')
->addCommand(
hsprintf(
- '<tt>phabricator/ $</tt> ./bin/config set mysql.host %s',
+ '<tt>$</tt> ./bin/config set mysql.host %s',
$host))
->addCommand(
hsprintf(
- '<tt>phabricator/ $</tt> ./bin/config set mysql.port %s',
+ '<tt>$</tt> ./bin/config set mysql.port %s',
$port));
}
$refs = PhabricatorDatabaseRef::queryAll();
$refs = mpull($refs, null, 'getRefKey');
// Test if we can connect to each database first. If we can not connect
// to a particular database, we only raise a warning: this allows new web
// nodes to start during a disaster, when some databases may be correctly
// configured but not reachable.
$connect_map = array();
$any_connection = false;
foreach ($refs as $ref_key => $ref) {
$conn_raw = $ref->newManagementConnection();
try {
queryfx($conn_raw, 'SELECT 1');
$database_exception = null;
$any_connection = true;
} catch (AphrontInvalidCredentialsQueryException $ex) {
$database_exception = $ex;
} catch (AphrontConnectionQueryException $ex) {
$database_exception = $ex;
}
if ($database_exception) {
$connect_map[$ref_key] = $database_exception;
unset($refs[$ref_key]);
}
}
if ($connect_map) {
// This is only a fatal error if we could not connect to anything. If
// possible, we still want to start if some database hosts can not be
// reached.
$is_fatal = !$any_connection;
foreach ($connect_map as $ref_key => $database_exception) {
$issue = PhabricatorSetupIssue::newDatabaseConnectionIssue(
$database_exception,
$is_fatal);
$this->addIssue($issue);
}
}
foreach ($refs as $ref_key => $ref) {
if ($this->executeRefChecks($ref)) {
return;
}
}
}
private function executeRefChecks(PhabricatorDatabaseRef $ref) {
$conn_raw = $ref->newManagementConnection();
$ref_key = $ref->getRefKey();
$engines = queryfx_all($conn_raw, 'SHOW ENGINES');
$engines = ipull($engines, 'Support', 'Engine');
$innodb = idx($engines, 'InnoDB');
if ($innodb != 'YES' && $innodb != 'DEFAULT') {
$message = pht(
'The "InnoDB" engine is not available in MySQL (on host "%s"). '.
'Enable InnoDB in your MySQL configuration.'.
"\n\n".
'(If you aleady created tables, MySQL incorrectly used some other '.
'engine to create them. You need to convert them or drop and '.
'reinitialize them.)',
$ref_key);
$this->newIssue('mysql.innodb')
->setName(pht('MySQL InnoDB Engine Not Available'))
->setMessage($message)
->setIsFatal(true);
return true;
}
$namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
$databases = queryfx_all($conn_raw, 'SHOW DATABASES');
$databases = ipull($databases, 'Database', 'Database');
if (empty($databases[$namespace.'_meta_data'])) {
$message = pht(
'Run the storage upgrade script to setup databases (host "%s" has '.
'not been initialized).',
$ref_key);
$this->newIssue('storage.upgrade')
->setName(pht('Setup MySQL Schema'))
->setMessage($message)
->setIsFatal(true)
- ->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade'));
+ ->addCommand(hsprintf('<tt>$</tt> ./bin/storage upgrade'));
return true;
}
$conn_meta = $ref->newApplicationConnection(
$namespace.'_meta_data');
$applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status');
$applied = ipull($applied, 'patch', 'patch');
$all = PhabricatorSQLPatchList::buildAllPatches();
$diff = array_diff_key($all, $applied);
if ($diff) {
$message = pht(
'Run the storage upgrade script to upgrade databases (host "%s" is '.
'out of date). Missing patches: %s.',
$ref_key,
implode(', ', array_keys($diff)));
$this->newIssue('storage.patch')
->setName(pht('Upgrade MySQL Schema'))
->setIsFatal(true)
->setMessage($message)
->addCommand(
- hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade'));
+ hsprintf('<tt>$</tt> ./bin/storage upgrade'));
return true;
}
// NOTE: It's possible that replication is broken but we have not been
// granted permission to "SHOW SLAVE STATUS" so we can't figure it out.
// We allow this kind of configuration and survive these checks, trusting
// that operations knows what they're doing. This issue is shown on the
// "Database Servers" console.
switch ($ref->getReplicaStatus()) {
case PhabricatorDatabaseRef::REPLICATION_MASTER_REPLICA:
$message = pht(
'Database host "%s" is configured as a master, but is replicating '.
'another host. This is dangerous and can mangle or destroy data. '.
'Only replicas should be replicating. Stop replication on the '.
- 'host or reconfigure Phabricator.',
+ 'host or adjust configuration.',
$ref->getRefKey());
$this->newIssue('db.master.replicating')
->setName(pht('Replicating Master'))
->setIsFatal(true)
->setMessage($message);
return true;
case PhabricatorDatabaseRef::REPLICATION_REPLICA_NONE:
case PhabricatorDatabaseRef::REPLICATION_NOT_REPLICATING:
if (!$ref->getIsMaster()) {
$message = pht(
'Database replica "%s" is listed as a replica, but is not '.
'currently replicating. You are vulnerable to data loss if '.
'the master fails.',
$ref->getRefKey());
// This isn't a fatal because it can normally only put data at risk,
// not actually do anything destructive or unrecoverable.
$this->newIssue('db.replica.not-replicating')
->setName(pht('Nonreplicating Replica'))
->setMessage($message);
}
break;
}
// If we have more than one master, we require that the cluster database
// configuration written to each database node is exactly the same as the
// one we are running with.
$masters = PhabricatorDatabaseRef::getAllMasterDatabaseRefs();
if (count($masters) > 1) {
$state_actual = queryfx_one(
$conn_meta,
'SELECT stateValue FROM %T WHERE stateKey = %s',
PhabricatorStorageManagementAPI::TABLE_HOSTSTATE,
'cluster.databases');
if ($state_actual) {
$state_actual = $state_actual['stateValue'];
}
$state_expect = $ref->getPartitionStateForCommit();
if ($state_expect !== $state_actual) {
$message = pht(
'Database host "%s" has a configured cluster state which disagrees '.
'with the state on this host ("%s"). Run `bin/storage partition` '.
'to commit local state to the cluster. This host may have started '.
'with an out-of-date configuration.',
$ref->getRefKey(),
php_uname('n'));
$this->newIssue('db.state.desync')
->setName(pht('Cluster Configuration Out of Sync'))
->setMessage($message)
->setIsFatal(true);
return true;
}
}
}
}
diff --git a/src/applications/config/check/PhabricatorElasticsearchSetupCheck.php b/src/applications/config/check/PhabricatorElasticsearchSetupCheck.php
index cd29ecdc78..8466c5a6c6 100644
--- a/src/applications/config/check/PhabricatorElasticsearchSetupCheck.php
+++ b/src/applications/config/check/PhabricatorElasticsearchSetupCheck.php
@@ -1,88 +1,88 @@
<?php
final class PhabricatorElasticsearchSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_OTHER;
}
protected function executeChecks() {
// TODO: Avoid fataling if we don't have a master database configured
// but have the MySQL search index configured. See T12965.
if (PhabricatorEnv::isReadOnly()) {
return;
}
$services = PhabricatorSearchService::getAllServices();
foreach ($services as $service) {
try {
$host = $service->getAnyHostForRole('read');
} catch (PhabricatorClusterNoHostForRoleException $e) {
// ignore the error
continue;
}
if ($host instanceof PhabricatorElasticsearchHost) {
$index_exists = null;
$index_sane = null;
try {
$engine = $host->getEngine();
$index_exists = $engine->indexExists();
if ($index_exists) {
$index_sane = $engine->indexIsSane();
}
} catch (Exception $ex) {
$summary = pht('Elasticsearch is not reachable as configured.');
$message = pht(
- 'Elasticsearch is configured (with the %s setting) but Phabricator'.
- ' encountered an exception when trying to test the index.'.
+ 'Elasticsearch is configured (with the %s setting) but an '.
+ 'exception was encountered when trying to test the index.'.
"\n\n".
'%s',
phutil_tag('tt', array(), 'cluster.search'),
phutil_tag('pre', array(), $ex->getMessage()));
$this->newIssue('elastic.misconfigured')
->setName(pht('Elasticsearch Misconfigured'))
->setSummary($summary)
->setMessage($message)
->addRelatedPhabricatorConfig('cluster.search');
return;
}
if (!$index_exists) {
$summary = pht(
'You enabled Elasticsearch but the index does not exist.');
$message = pht(
'You likely enabled cluster.search without creating the '.
'index. Use the following command to create a new index.');
$this
->newIssue('elastic.missing-index')
->setName(pht('Elasticsearch Index Not Found'))
->addCommand('./bin/search init')
->setSummary($summary)
->setMessage($message);
} else if (!$index_sane) {
$summary = pht(
'Elasticsearch index exists but needs correction.');
$message = pht(
- 'Either the Phabricator schema for Elasticsearch has changed '.
+ 'Either the schema for Elasticsearch has changed '.
'or Elasticsearch created the index automatically. '.
'Use the following command to rebuild the index.');
$this
->newIssue('elastic.broken-index')
->setName(pht('Elasticsearch Index Schema Mismatch'))
->addCommand('./bin/search init')
->setSummary($summary)
->setMessage($message);
}
}
}
}
}
diff --git a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php
index 1de39f3468..ae14a0ab0a 100644
--- a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php
+++ b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php
@@ -1,553 +1,551 @@
<?php
final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_OTHER;
}
protected function executeChecks() {
$ancient_config = self::getAncientConfig();
$all_keys = PhabricatorEnv::getAllConfigKeys();
$all_keys = array_keys($all_keys);
sort($all_keys);
$defined_keys = PhabricatorApplicationConfigOptions::loadAllOptions();
$stack = PhabricatorEnv::getConfigSourceStack();
$stack = $stack->getStack();
foreach ($all_keys as $key) {
if (isset($defined_keys[$key])) {
continue;
}
if (isset($ancient_config[$key])) {
$summary = pht(
'This option has been removed. You may delete it at your '.
'convenience.');
$message = pht(
"The configuration option '%s' has been removed. You may delete ".
"it at your convenience.".
"\n\n%s",
$key,
$ancient_config[$key]);
$short = pht('Obsolete Config');
$name = pht('Obsolete Configuration Option "%s"', $key);
} else {
$summary = pht('This option is not recognized. It may be misspelled.');
$message = pht(
- "The configuration option '%s' is not recognized. It may be ".
- "misspelled, or it might have existed in an older version of ".
- "Phabricator. It has no effect, and should be corrected or deleted.",
+ 'The configuration option "%s" is not recognized. It may be '.
+ 'misspelled, or it might have existed in an older version of '.
+ 'the software. It has no effect, and should be corrected or deleted.',
$key);
$short = pht('Unknown Config');
$name = pht('Unknown Configuration Option "%s"', $key);
}
$issue = $this->newIssue('config.unknown.'.$key)
->setShortName($short)
->setName($name)
->setSummary($summary);
$found = array();
$found_local = false;
$found_database = false;
foreach ($stack as $source_key => $source) {
$value = $source->getKeys(array($key));
if ($value) {
$found[] = $source->getName();
if ($source instanceof PhabricatorConfigDatabaseSource) {
$found_database = true;
}
if ($source instanceof PhabricatorConfigLocalSource) {
$found_local = true;
}
}
}
$message = $message."\n\n".pht(
'This configuration value is defined in these %d '.
'configuration source(s): %s.',
count($found),
implode(', ', $found));
$issue->setMessage($message);
if ($found_local) {
- $command = csprintf('phabricator/ $ ./bin/config delete %s', $key);
+ $command = csprintf('$ ./bin/config delete %s', $key);
$issue->addCommand($command);
}
if ($found_database) {
$issue->addPhabricatorConfig($key);
}
}
$options = PhabricatorApplicationConfigOptions::loadAllOptions();
foreach ($defined_keys as $key => $value) {
$option = idx($options, $key);
if (!$option) {
continue;
}
if (!$option->getLocked()) {
continue;
}
$found_database = false;
foreach ($stack as $source_key => $source) {
$value = $source->getKeys(array($key));
if ($value) {
if ($source instanceof PhabricatorConfigDatabaseSource) {
$found_database = true;
break;
}
}
}
if (!$found_database) {
continue;
}
// NOTE: These are values which we don't let you edit directly, but edit
// via other UI workflows. For now, don't raise this warning about them.
// In the future, before we stop reading database configuration for
// locked values, we either need to add a flag which lets these values
// continue reading from the database or move them to some other storage
// mechanism.
$soft_locks = array(
'phabricator.uninstalled-applications',
'phabricator.application-settings',
'config.ignore-issues',
'auth.lock-config',
);
$soft_locks = array_fuse($soft_locks);
if (isset($soft_locks[$key])) {
continue;
}
$doc_name = 'Configuration Guide: Locked and Hidden Configuration';
$doc_href = PhabricatorEnv::getDoclink($doc_name);
$set_command = phutil_tag(
'tt',
array(),
csprintf(
'bin/config set %R <value>',
$key));
$summary = pht(
'Configuration value "%s" is locked, but has a value in the database.',
$key);
$message = pht(
'The configuration value "%s" is locked (so it can not be edited '.
'from the web UI), but has a database value. Usually, this means '.
'that it was previously not locked, you set it using the web UI, '.
'and it later became locked.'.
"\n\n".
'You should copy this configuration value to a local configuration '.
'source (usually by using %s) and then remove it from the database '.
'with the command below.'.
"\n\n".
'For more information on locked and hidden configuration, including '.
'details about this setup issue, see %s.'.
"\n\n".
'This database value is currently respected, but a future version '.
- 'of Phabricator will stop respecting database values for locked '.
+ 'of the software will stop respecting database values for locked '.
'configuration options.',
$key,
$set_command,
phutil_tag(
'a',
array(
'href' => $doc_href,
'target' => '_blank',
),
$doc_name));
$command = csprintf(
- 'phabricator/ $ ./bin/config delete --database %R',
+ '$ ./bin/config delete --database %R',
$key);
$this->newIssue('config.locked.'.$key)
->setShortName(pht('Deprecated Config Source'))
->setName(
pht(
'Locked Configuration Option "%s" Has Database Value',
$key))
->setSummary($summary)
->setMessage($message)
->addCommand($command)
->addPhabricatorConfig($key);
}
if (PhabricatorEnv::getEnvConfig('feed.http-hooks')) {
$this->newIssue('config.deprecated.feed.http-hooks')
->setShortName(pht('Feed Hooks Deprecated'))
->setName(pht('Migrate From "feed.http-hooks" to Webhooks'))
->addPhabricatorConfig('feed.http-hooks')
->setMessage(
pht(
'The "feed.http-hooks" option is deprecated in favor of '.
'Webhooks. This option will be removed in a future version '.
- 'of Phabricator.'.
+ 'of the software.'.
"\n\n".
'You can configure Webhooks in Herald.'.
"\n\n".
'To resolve this issue, remove all URIs from "feed.http-hooks".'));
}
}
/**
* Return a map of deleted config options. Keys are option keys; values are
* explanations of what happened to the option.
*/
public static function getAncientConfig() {
$reason_auth = pht(
'This option has been migrated to the "Auth" application. Your old '.
'configuration is still in effect, but now stored in "Auth" instead of '.
'configuration. Going forward, you can manage authentication from '.
'the web UI.');
$auth_config = array(
'controller.oauth-registration',
'auth.password-auth-enabled',
'facebook.auth-enabled',
'facebook.registration-enabled',
'facebook.auth-permanent',
'facebook.application-id',
'facebook.application-secret',
'facebook.require-https-auth',
'github.auth-enabled',
'github.registration-enabled',
'github.auth-permanent',
'github.application-id',
'github.application-secret',
'google.auth-enabled',
'google.registration-enabled',
'google.auth-permanent',
'google.application-id',
'google.application-secret',
'ldap.auth-enabled',
'ldap.hostname',
'ldap.port',
'ldap.base_dn',
'ldap.search_attribute',
'ldap.search-first',
'ldap.username-attribute',
'ldap.real_name_attributes',
'ldap.activedirectory_domain',
'ldap.version',
'ldap.referrals',
'ldap.anonymous-user-name',
'ldap.anonymous-user-password',
'ldap.start-tls',
'disqus.auth-enabled',
'disqus.registration-enabled',
'disqus.auth-permanent',
'disqus.application-id',
'disqus.application-secret',
'phabricator.oauth-uri',
'phabricator.auth-enabled',
'phabricator.registration-enabled',
'phabricator.auth-permanent',
'phabricator.application-id',
'phabricator.application-secret',
);
$ancient_config = array_fill_keys($auth_config, $reason_auth);
$markup_reason = pht(
'Custom remarkup rules are now added by subclassing '.
'%s or %s.',
'PhabricatorRemarkupCustomInlineRule',
'PhabricatorRemarkupCustomBlockRule');
$session_reason = pht(
'Sessions now expire and are garbage collected rather than having an '.
'arbitrary concurrency limit.');
$differential_field_reason = pht(
'All Differential fields are now managed through the configuration '.
'option "%s". Use that option to configure which fields are shown.',
'differential.fields');
$reply_domain_reason = pht(
'Individual application reply handler domains have been removed. '.
'Configure a reply domain with "%s".',
'metamta.reply-handler-domain');
$reply_handler_reason = pht(
'Reply handlers can no longer be overridden with configuration.');
$monospace_reason = pht(
- 'Phabricator no longer supports global customization of monospaced '.
- 'fonts.');
+ 'Global customization of monospaced fonts is no longer supported.');
$public_mail_reason = pht(
'Inbound mail addresses are now configured for each application '.
'in the Applications tool.');
$gc_reason = pht(
'Garbage collectors are now configured with "%s".',
'bin/garbage set-policy');
$aphlict_reason = pht(
'Configuration of the notification server has changed substantially. '.
'For discussion, see T10794.');
$stale_reason = pht(
'The Differential revision list view age UI elements have been removed '.
'to simplify the interface.');
$global_settings_reason = pht(
'The "Re: Prefix" and "Vary Subjects" settings are now configured '.
'in global settings.');
$dashboard_reason = pht(
'This option has been removed, you can use Dashboards to provide '.
'homepage customization. See T11533 for more details.');
$elastic_reason = pht(
'Elasticsearch is now configured with "%s".',
'cluster.search');
$mailers_reason = pht(
'Inbound and outbound mail is now configured with "cluster.mailers".');
$prefix_reason = pht(
'Per-application mail subject prefix customization is no longer '.
'directly supported. Prefixes and other strings may be customized with '.
'"translation.override".');
$phd_reason = pht(
'Use "bin/phd debug ..." to get a detailed daemon execution log.');
$ancient_config += array(
'phid.external-loaders' =>
pht(
'External loaders have been replaced. Extend `%s` '.
'to implement new PHID and handle types.',
'PhabricatorPHIDType'),
'maniphest.custom-task-extensions-class' =>
pht(
'Maniphest fields are now loaded automatically. '.
'You can configure them with `%s`.',
'maniphest.fields'),
'maniphest.custom-fields' =>
pht(
'Maniphest fields are now defined in `%s`. '.
'Existing definitions have been migrated.',
'maniphest.custom-field-definitions'),
'differential.custom-remarkup-rules' => $markup_reason,
'differential.custom-remarkup-block-rules' => $markup_reason,
'auth.sshkeys.enabled' => pht(
'SSH keys are now actually useful, so they are always enabled.'),
'differential.anonymous-access' => pht(
- 'Phabricator now has meaningful global access controls. See `%s`.',
+ 'Global access controls now exist, see `%s`.',
'policy.allow-public'),
'celerity.resource-path' => pht(
'An alternate resource map is no longer supported. Instead, use '.
'multiple maps. See T4222.'),
'metamta.send-immediately' => pht(
'Mail is now always delivered by the daemons.'),
'auth.sessions.conduit' => $session_reason,
'auth.sessions.web' => $session_reason,
'tokenizer.ondemand' => pht(
- 'Phabricator now manages typeahead strategies automatically.'),
+ 'Typeahead strategies are now managed automatically.'),
'differential.revision-custom-detail-renderer' => pht(
'Obsolete; use standard rendering events instead.'),
'differential.show-host-field' => $differential_field_reason,
'differential.show-test-plan-field' => $differential_field_reason,
'differential.field-selector' => $differential_field_reason,
'phabricator.show-beta-applications' => pht(
'This option has been renamed to `%s` to emphasize the '.
'unfinished nature of many prototype applications. '.
'Your existing setting has been migrated.',
'phabricator.show-prototypes'),
'notification.user' => pht(
'The notification server no longer requires root permissions. Start '.
'the server as the user you want it to run under.'),
'notification.debug' => pht(
'Notifications no longer have a dedicated debugging mode.'),
'translation.provider' => pht(
'The translation implementation has changed and providers are no '.
'longer used or supported.'),
'config.mask' => pht(
'Use `%s` instead of this option.',
'config.hide'),
'phd.start-taskmasters' => pht(
'Taskmasters now use an autoscaling pool. You can configure the '.
'pool size with `%s`.',
'phd.taskmasters'),
'storage.engine-selector' => pht(
- 'Phabricator now automatically discovers available storage engines '.
- 'at runtime.'),
+ 'Storage engines are now discovered automatically at runtime.'),
'storage.upload-size-limit' => pht(
- 'Phabricator now supports arbitrarily large files. Consult the '.
+ 'Arbitrarily large files are now supported. Consult the '.
'documentation for configuration details.'),
'security.allow-outbound-http' => pht(
'This option has been replaced with the more granular option `%s`.',
'security.outbound-blacklist'),
'metamta.reply.show-hints' => pht(
- 'Phabricator no longer shows reply hints in mail.'),
+ 'Reply hints are no longer shown in mail.'),
'metamta.differential.reply-handler-domain' => $reply_domain_reason,
'metamta.diffusion.reply-handler-domain' => $reply_domain_reason,
'metamta.macro.reply-handler-domain' => $reply_domain_reason,
'metamta.maniphest.reply-handler-domain' => $reply_domain_reason,
'metamta.pholio.reply-handler-domain' => $reply_domain_reason,
'metamta.diffusion.reply-handler' => $reply_handler_reason,
'metamta.differential.reply-handler' => $reply_handler_reason,
'metamta.maniphest.reply-handler' => $reply_handler_reason,
'metamta.package.reply-handler' => $reply_handler_reason,
'metamta.precedence-bulk' => pht(
- 'Phabricator now always sends transaction mail with '.
- '"Precedence: bulk" to improve deliverability.'),
+ 'Transaction mail is now always sent with "Precedence: bulk" to '.
+ 'improve deliverability.'),
'style.monospace' => $monospace_reason,
'style.monospace.windows' => $monospace_reason,
'search.engine-selector' => pht(
- 'Phabricator now automatically discovers available search engines '.
- 'at runtime.'),
+ 'Available search engines are now automatically discovered at '.
+ 'runtime.'),
'metamta.files.public-create-email' => $public_mail_reason,
'metamta.maniphest.public-create-email' => $public_mail_reason,
'metamta.maniphest.default-public-author' => $public_mail_reason,
'metamta.paste.public-create-email' => $public_mail_reason,
'security.allow-conduit-act-as-user' => pht(
'Impersonating users over the API is no longer supported.'),
'feed.public' => pht('The framable public feed is no longer supported.'),
'auth.login-message' => pht(
'This configuration option has been replaced with a modular '.
'handler. See T9346.'),
'gcdaemon.ttl.herald-transcripts' => $gc_reason,
'gcdaemon.ttl.daemon-logs' => $gc_reason,
'gcdaemon.ttl.differential-parse-cache' => $gc_reason,
'gcdaemon.ttl.markup-cache' => $gc_reason,
'gcdaemon.ttl.task-archive' => $gc_reason,
'gcdaemon.ttl.general-cache' => $gc_reason,
'gcdaemon.ttl.conduit-logs' => $gc_reason,
'phd.variant-config' => pht(
'This configuration is no longer relevant because daemons '.
'restart automatically on configuration changes.'),
'notification.ssl-cert' => $aphlict_reason,
'notification.ssl-key' => $aphlict_reason,
'notification.pidfile' => $aphlict_reason,
'notification.log' => $aphlict_reason,
'notification.enabled' => $aphlict_reason,
'notification.client-uri' => $aphlict_reason,
'notification.server-uri' => $aphlict_reason,
'metamta.differential.unified-comment-context' => pht(
'Inline comments are now always rendered with a limited amount '.
'of context.'),
'differential.days-fresh' => $stale_reason,
'differential.days-stale' => $stale_reason,
'metamta.re-prefix' => $global_settings_reason,
'metamta.vary-subjects' => $global_settings_reason,
'ui.custom-header' => pht(
'This option has been replaced with `ui.logo`, which provides more '.
'flexible configuration options.'),
'welcome.html' => $dashboard_reason,
'maniphest.priorities.unbreak-now' => $dashboard_reason,
'maniphest.priorities.needs-triage' => $dashboard_reason,
'mysql.implementation' => pht(
- 'Phabricator now automatically selects the best available '.
- 'MySQL implementation.'),
+ 'The best available MYSQL implementation is now selected '.
+ 'automatically.'),
'mysql.configuration-provider' => pht(
- 'Phabricator now has application-level management of partitioning '.
- 'and replicas.'),
+ 'Partitioning and replication are now managed in primary '.
+ 'configuration.'),
'search.elastic.host' => $elastic_reason,
'search.elastic.namespace' => $elastic_reason,
'metamta.mail-adapter' => $mailers_reason,
'amazon-ses.access-key' => $mailers_reason,
'amazon-ses.secret-key' => $mailers_reason,
'amazon-ses.endpoint' => $mailers_reason,
'mailgun.domain' => $mailers_reason,
'mailgun.api-key' => $mailers_reason,
'phpmailer.mailer' => $mailers_reason,
'phpmailer.smtp-host' => $mailers_reason,
'phpmailer.smtp-port' => $mailers_reason,
'phpmailer.smtp-protocol' => $mailers_reason,
'phpmailer.smtp-user' => $mailers_reason,
'phpmailer.smtp-password' => $mailers_reason,
'phpmailer.smtp-encoding' => $mailers_reason,
'sendgrid.api-user' => $mailers_reason,
'sendgrid.api-key' => $mailers_reason,
'celerity.resource-hash' => pht(
'This option generally did not prove useful. Resource hash keys '.
'are now managed automatically.'),
'celerity.enable-deflate' => pht(
'Resource deflation is now managed automatically.'),
'celerity.minify' => pht(
'Resource minification is now managed automatically.'),
'metamta.domain' => pht(
'Mail thread IDs are now generated automatically.'),
'metamta.placeholder-to-recipient' => pht(
'Placeholder recipients are now generated automatically.'),
'metamta.mail-key' => pht(
'Mail object address hash keys are now generated automatically.'),
'phabricator.csrf-key' => pht(
'CSRF HMAC keys are now managed automatically.'),
'metamta.insecure-auth-with-reply-to' => pht(
'Authenticating users based on "Reply-To" is no longer supported.'),
'phabricator.allow-email-users' => pht(
'Public email is now accepted if the associated address has a '.
'default author, and rejected otherwise.'),
'metamta.conpherence.subject-prefix' => $prefix_reason,
'metamta.differential.subject-prefix' => $prefix_reason,
'metamta.diffusion.subject-prefix' => $prefix_reason,
'metamta.files.subject-prefix' => $prefix_reason,
'metamta.legalpad.subject-prefix' => $prefix_reason,
'metamta.macro.subject-prefix' => $prefix_reason,
'metamta.maniphest.subject-prefix' => $prefix_reason,
'metamta.package.subject-prefix' => $prefix_reason,
'metamta.paste.subject-prefix' => $prefix_reason,
'metamta.pholio.subject-prefix' => $prefix_reason,
'metamta.phriction.subject-prefix' => $prefix_reason,
'aphront.default-application-configuration-class' => pht(
'This ancient extension point has been replaced with other '.
'mechanisms, including "AphrontSite".'),
'differential.whitespace-matters' => pht(
'Whitespace rendering is now handled automatically.'),
'phd.pid-directory' => pht(
- 'Phabricator daemons no longer use PID files.'),
+ 'Daemons no longer use PID files.'),
'phd.trace' => $phd_reason,
'phd.verbose' => $phd_reason,
);
return $ancient_config;
}
}
diff --git a/src/applications/config/check/PhabricatorFileinfoSetupCheck.php b/src/applications/config/check/PhabricatorFileinfoSetupCheck.php
index 543fc4fb7c..9896daac2c 100644
--- a/src/applications/config/check/PhabricatorFileinfoSetupCheck.php
+++ b/src/applications/config/check/PhabricatorFileinfoSetupCheck.php
@@ -1,23 +1,23 @@
<?php
final class PhabricatorFileinfoSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_OTHER;
}
protected function executeChecks() {
if (!extension_loaded('fileinfo')) {
$message = pht(
"The '%s' extension is not installed. Without '%s', ".
- "support, Phabricator may not be able to determine the MIME types ".
+ "support, this software may not be able to determine the MIME types ".
"of uploaded files.",
'fileinfo',
'fileinfo');
$this->newIssue('extension.fileinfo')
->setName(pht("Missing '%s' Extension", 'fileinfo'))
->setMessage($message);
}
}
}
diff --git a/src/applications/config/check/PhabricatorGDSetupCheck.php b/src/applications/config/check/PhabricatorGDSetupCheck.php
index 7ada204801..0aa23035c7 100644
--- a/src/applications/config/check/PhabricatorGDSetupCheck.php
+++ b/src/applications/config/check/PhabricatorGDSetupCheck.php
@@ -1,58 +1,58 @@
<?php
final class PhabricatorGDSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_OTHER;
}
protected function executeChecks() {
if (!extension_loaded('gd')) {
$message = pht(
"The '%s' extension is not installed. Without '%s', support, ".
- "Phabricator will not be able to process or resize images ".
+ "this server will not be able to process or resize images ".
"(for example, to generate thumbnails). Install or enable '%s'.",
'gd',
'gd',
'gd');
$this->newIssue('extension.gd')
->setName(pht("Missing '%s' Extension", 'gd'))
->setMessage($message)
->addPHPExtension('gd');
} else {
$image_type_map = array(
'imagecreatefrompng' => 'PNG',
'imagecreatefromgif' => 'GIF',
'imagecreatefromjpeg' => 'JPEG',
);
$have = array();
foreach ($image_type_map as $function => $image_type) {
if (function_exists($function)) {
$have[] = $image_type;
}
}
$missing = array_diff($image_type_map, $have);
if ($missing) {
$missing = implode(', ', $missing);
$have = implode(', ', $have);
$message = pht(
"The '%s' extension has support for only some image types. ".
- "Phabricator will be unable to process images of the missing ".
+ "This server will be unable to process images of the missing ".
"types until you build '%s' with support for them. ".
"Supported types: %s. Missing types: %s.",
'gd',
'gd',
$have,
$missing);
$this->newIssue('extension.gd.support')
->setName(pht("Partial '%s' Support", 'gd'))
->setMessage($message);
}
}
}
}
diff --git a/src/applications/config/check/PhabricatorMailSetupCheck.php b/src/applications/config/check/PhabricatorMailSetupCheck.php
index c89e0036a2..0b1374809c 100644
--- a/src/applications/config/check/PhabricatorMailSetupCheck.php
+++ b/src/applications/config/check/PhabricatorMailSetupCheck.php
@@ -1,24 +1,24 @@
<?php
final class PhabricatorMailSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_OTHER;
}
protected function executeChecks() {
if (PhabricatorEnv::getEnvConfig('cluster.mailers')) {
return;
}
$message = pht(
- 'You haven\'t configured mailers yet, so Phabricator won\'t be able '.
+ 'You haven\'t configured mailers yet, so this server won\'t be able '.
'to send outbound mail or receive inbound mail. See the '.
- 'configuration setting cluster.mailers for details.');
+ 'configuration setting "cluster.mailers" for details.');
$this->newIssue('cluster.mailers')
->setName(pht('Mailers Not Configured'))
->setMessage($message)
->addPhabricatorConfig('cluster.mailers');
}
}
diff --git a/src/applications/config/check/PhabricatorManualActivitySetupCheck.php b/src/applications/config/check/PhabricatorManualActivitySetupCheck.php
index 04457cb12a..660792ca53 100644
--- a/src/applications/config/check/PhabricatorManualActivitySetupCheck.php
+++ b/src/applications/config/check/PhabricatorManualActivitySetupCheck.php
@@ -1,147 +1,153 @@
<?php
final class PhabricatorManualActivitySetupCheck
extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_OTHER;
}
protected function executeChecks() {
$activities = id(new PhabricatorConfigManualActivity())->loadAll();
foreach ($activities as $activity) {
$type = $activity->getActivityType();
switch ($type) {
case PhabricatorConfigManualActivity::TYPE_REINDEX:
$this->raiseSearchReindexIssue();
break;
case PhabricatorConfigManualActivity::TYPE_IDENTITIES:
$this->raiseRebuildIdentitiesIssue();
break;
default:
}
}
}
private function raiseSearchReindexIssue() {
$activity_name = pht('Rebuild Search Index');
$activity_summary = pht(
'The search index algorithm has been updated and the index needs '.
'be rebuilt.');
$message = array();
$message[] = pht(
'The indexing algorithm for the fulltext search index has been '.
'updated and the index needs to be rebuilt. Until you rebuild the '.
'index, global search (and other fulltext search) will not '.
'function correctly.');
$message[] = pht(
- 'You can rebuild the search index while Phabricator is running.');
+ 'You can rebuild the search index while the server is running.');
$message[] = pht(
'To rebuild the index, run this command:');
$message[] = phutil_tag(
'pre',
array(),
(string)csprintf(
- 'phabricator/ $ ./bin/search index --all --force --background'));
+ '$ ./bin/search index --all --force --background'));
$message[] = pht(
'You can find more information about rebuilding the search '.
'index here: %s',
phutil_tag(
'a',
array(
'href' => 'https://phurl.io/u/reindex',
'target' => '_blank',
),
'https://phurl.io/u/reindex'));
$message[] = pht(
'After rebuilding the index, run this command to clear this setup '.
'warning:');
$message[] = phutil_tag(
'pre',
array(),
- 'phabricator/ $ ./bin/config done reindex');
+ '$ ./bin/config done reindex');
$activity_message = phutil_implode_html("\n\n", $message);
$this->newIssue('manual.reindex')
->setName($activity_name)
->setSummary($activity_summary)
->setMessage($activity_message);
}
private function raiseRebuildIdentitiesIssue() {
$activity_name = pht('Rebuild Repository Identities');
$activity_summary = pht(
- 'The mapping from VCS users to Phabricator users has changed '.
- 'and must be rebuilt.');
+ 'The mapping from VCS users to %s users has changed '.
+ 'and must be rebuilt.',
+ PlatformSymbols::getPlatformServerName());
$message = array();
$message[] = pht(
- 'The way Phabricator attributes VCS activity to Phabricator users '.
- 'has changed. There is a new indirection layer between the strings '.
- 'that appear as VCS authors and committers (such as "John Developer '.
- '<johnd@bigcorp.com>") and the Phabricator user that gets associated '.
- 'with VCS commits. This is to support situations where users '.
- 'are incorrectly associated with commits by Phabricator making bad '.
- 'guesses about the identity of the corresponding Phabricator user. '.
+ 'The way VCS activity is attributed %s user accounts has changed.',
+ PlatformSymbols::getPlatformServerName());
+
+ $message[] = pht(
+ 'There is a new indirection layer between the strings that appear as '.
+ 'VCS authors and committers (such as "John Developer '.
+ '<johnd@bigcorp.com>") and the user account that gets associated '.
+ 'with VCS commits.');
+
+ $message[] = pht(
+ 'This change supports situations where users are incorrectly '.
+ 'associated with commits because the software makes a bad guess '.
+ 'about how the VCS string maps to a user account. '.
'This also helps with situations where existing repositories are '.
'imported without having created accounts for all the committers to '.
'that repository. Until you rebuild these repository identities, you '.
- 'are likely to encounter problems with future Phabricator features '.
- 'which will rely on the existence of these identities.');
+ 'are likely to encounter problems with features which rely on the '.
+ 'existence of these identities.');
$message[] = pht(
- 'You can rebuild repository identities while Phabricator is running.');
+ 'You can rebuild repository identities while the server is running.');
$message[] = pht(
'To rebuild identities, run this command:');
$message[] = phutil_tag(
'pre',
array(),
(string)csprintf(
- 'phabricator/ $ '.
- './bin/repository rebuild-identities --all-repositories'));
+ '$ ./bin/repository rebuild-identities --all-repositories'));
$message[] = pht(
'You can find more information about this new identity mapping '.
'here: %s',
phutil_tag(
'a',
array(
'href' => 'https://phurl.io/u/repoIdentities',
'target' => '_blank',
),
'https://phurl.io/u/repoIdentities'));
$message[] = pht(
'After rebuilding repository identities, run this command to clear '.
'this setup warning:');
$message[] = phutil_tag(
'pre',
array(),
- 'phabricator/ $ ./bin/config done identities');
+ '$ ./bin/config done identities');
$activity_message = phutil_implode_html("\n\n", $message);
$this->newIssue('manual.identities')
->setName($activity_name)
->setSummary($activity_summary)
->setMessage($activity_message);
}
}
diff --git a/src/applications/config/check/PhabricatorMySQLSetupCheck.php b/src/applications/config/check/PhabricatorMySQLSetupCheck.php
index de9a9e8b54..c5b71b1cda 100644
--- a/src/applications/config/check/PhabricatorMySQLSetupCheck.php
+++ b/src/applications/config/check/PhabricatorMySQLSetupCheck.php
@@ -1,399 +1,399 @@
<?php
final class PhabricatorMySQLSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_MYSQL;
}
protected function executeChecks() {
$refs = PhabricatorDatabaseRef::getActiveDatabaseRefs();
foreach ($refs as $ref) {
try {
$this->executeRefChecks($ref);
} catch (AphrontConnectionQueryException $ex) {
// If we're unable to connect to a host, just skip the checks for it.
// This can happen if we're restarting during a cluster incident. See
// T12966 for discussion.
}
}
}
private function executeRefChecks(PhabricatorDatabaseRef $ref) {
$max_allowed_packet = $ref->loadRawMySQLConfigValue('max_allowed_packet');
$host_name = $ref->getRefKey();
// This primarily supports setting the filesize limit for MySQL to 8MB,
// which may produce a >16MB packet after escaping.
$recommended_minimum = (32 * 1024 * 1024);
if ($max_allowed_packet < $recommended_minimum) {
$message = pht(
'On host "%s", MySQL is configured with a small "%s" (%d), which '.
'may cause some large writes to fail. The recommended minimum value '.
'for this setting is "%d".',
$host_name,
'max_allowed_packet',
$max_allowed_packet,
$recommended_minimum);
$this->newIssue('mysql.max_allowed_packet')
->setName(pht('Small MySQL "%s"', 'max_allowed_packet'))
->setMessage($message)
->setDatabaseRef($ref)
->addMySQLConfig('max_allowed_packet');
}
$modes = $ref->loadRawMySQLConfigValue('sql_mode');
$modes = explode(',', $modes);
if (!in_array('STRICT_ALL_TABLES', $modes)) {
$summary = pht(
'MySQL is not in strict mode (on host "%s"), but using strict mode '.
'is recommended.',
$host_name);
$message = pht(
'On database host "%s", the global "sql_mode" setting does not '.
'include the "STRICT_ALL_TABLES" mode. Enabling this mode is '.
'recommended to generally improve how MySQL handles certain errors.'.
"\n\n".
'Without this mode enabled, MySQL will silently ignore some error '.
'conditions, including inserts which attempt to store more data in '.
'a column than actually fits. This behavior is usually undesirable '.
'and can lead to data corruption (by truncating multibyte characters '.
'in the middle), data loss (by discarding the data which does not '.
'fit into the column), or security concerns (for example, by '.
'truncating keys or credentials).'.
"\n\n".
- 'Phabricator is developed and tested in "STRICT_ALL_TABLES" mode so '.
+ 'This software is developed and tested in "STRICT_ALL_TABLES" mode so '.
'you should normally never encounter these situations, but may run '.
'into them if you interact with the database directly, run '.
'third-party code, develop extensions, or just encounter a bug in '.
'the software.'.
"\n\n".
'Enabling "STRICT_ALL_TABLES" makes MySQL raise an explicit error '.
'if one of these unusual situations does occur. This is a safer '.
'behavior and prevents these situations from causing secret, subtle, '.
'and potentially serious issues later on.'.
"\n\n".
'You can find more information about this mode (and how to configure '.
'it) in the MySQL manual. Usually, it is sufficient to add this to '.
'your "my.cnf" file (in the "[mysqld]" section) and then '.
'restart "mysqld":'.
"\n\n".
'%s'.
"\n".
'Note that if you run other applications against the same database, '.
'they may not work in strict mode.'.
"\n\n".
'If you can not or do not want to enable "STRICT_ALL_TABLES", you '.
- 'can safely ignore this warning. Phabricator will work correctly '.
+ 'can safely ignore this warning. This software will work correctly '.
'with this mode enabled or disabled.',
$host_name,
phutil_tag('pre', array(), 'sql_mode=STRICT_ALL_TABLES'));
$this->newIssue('sql_mode.strict')
->setName(pht('MySQL %s Mode Not Set', 'STRICT_ALL_TABLES'))
->setSummary($summary)
->setMessage($message)
->setDatabaseRef($ref)
->addMySQLConfig('sql_mode');
}
$is_innodb_fulltext = false;
$is_myisam_fulltext = false;
if ($this->shouldUseMySQLSearchEngine()) {
if (PhabricatorSearchDocument::isInnoDBFulltextEngineAvailable()) {
$is_innodb_fulltext = true;
} else {
$is_myisam_fulltext = true;
}
}
if ($is_myisam_fulltext) {
$stopword_file = $ref->loadRawMySQLConfigValue('ft_stopword_file');
if ($stopword_file === null) {
$summary = pht(
'Your version of MySQL (on database host "%s") does not support '.
'configuration of a stopword file. You will not be able to find '.
'search results for common words.',
$host_name);
$message = pht(
"Database host \"%s\" does not support the %s option. You will not ".
"be able to find search results for common words. You can gain ".
"access to this option by upgrading MySQL to a more recent ".
"version.\n\n".
"You can ignore this warning if you plan to configure Elasticsearch ".
"later, or aren't concerned about searching for common words.",
$host_name,
phutil_tag('tt', array(), 'ft_stopword_file'));
$this->newIssue('mysql.ft_stopword_file')
->setName(pht('MySQL %s Not Supported', 'ft_stopword_file'))
->setSummary($summary)
->setMessage($message)
->setDatabaseRef($ref)
->addMySQLConfig('ft_stopword_file');
} else if ($stopword_file == '(built-in)') {
$root = dirname(phutil_get_library_root('phabricator'));
$stopword_path = $root.'/resources/sql/stopwords.txt';
$stopword_path = Filesystem::resolvePath($stopword_path);
$namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
$summary = pht(
'MySQL (on host "%s") is using a default stopword file, which '.
'will prevent searching for many common words.',
$host_name);
$message = pht(
"Database host \"%s\" is using the builtin stopword file for ".
- "building search indexes. This can make Phabricator's search ".
+ "building search indexes. This can make the search ".
"feature less useful.\n\n".
"Stopwords are common words which are not indexed and thus can not ".
"be searched for. The default stopword file has about 500 words, ".
"including various words which you are likely to wish to search ".
"for, such as 'various', 'likely', 'wish', and 'zero'.\n\n".
"To make search more useful, you can use an alternate stopword ".
"file with fewer words. Alternatively, if you aren't concerned ".
"about searching for common words, you can ignore this warning. ".
"If you later plan to configure Elasticsearch, you can also ignore ".
"this warning: this stopword file only affects MySQL fulltext ".
"indexes.\n\n".
"To choose a different stopword file, add this to your %s file ".
"(in the %s section) and then restart %s:\n\n".
"%s\n".
"(You can also use a different file if you prefer. The file ".
"suggested above has about 50 of the most common English words.)\n\n".
"Finally, run this command to rebuild indexes using the new ".
"rules:\n\n".
"%s",
$host_name,
phutil_tag('tt', array(), 'my.cnf'),
phutil_tag('tt', array(), '[mysqld]'),
phutil_tag('tt', array(), 'mysqld'),
phutil_tag('pre', array(), 'ft_stopword_file='.$stopword_path),
phutil_tag(
'pre',
array(),
"mysql> REPAIR TABLE {$namespace}_search.search_documentfield;"));
$this->newIssue('mysql.ft_stopword_file')
->setName(pht('MySQL is Using Default Stopword File'))
->setSummary($summary)
->setMessage($message)
->setDatabaseRef($ref)
->addMySQLConfig('ft_stopword_file');
}
}
if ($is_myisam_fulltext) {
$min_len = $ref->loadRawMySQLConfigValue('ft_min_word_len');
if ($min_len >= 4) {
$namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
$summary = pht(
'MySQL is configured (on host "%s") to only index words with at '.
'least %d characters.',
$host_name,
$min_len);
$message = pht(
"Database host \"%s\" is configured to use the default minimum word ".
"length when building search indexes, which is 4. This means words ".
"which are only 3 characters long will not be indexed and can not ".
"be searched for.\n\n".
"For example, you will not be able to find search results for words ".
"like 'SMS', 'web', or 'DOS'.\n\n".
"You can change this setting to 3 to allow these words to be ".
"indexed. Alternatively, you can ignore this warning if you are ".
"not concerned about searching for 3-letter words. If you later ".
"plan to configure Elasticsearch, you can also ignore this warning: ".
"only MySQL fulltext search is affected.\n\n".
"To reduce the minimum word length to 3, add this to your %s file ".
"(in the %s section) and then restart %s:\n\n".
"%s\n".
"Finally, run this command to rebuild indexes using the new ".
"rules:\n\n".
"%s",
$host_name,
phutil_tag('tt', array(), 'my.cnf'),
phutil_tag('tt', array(), '[mysqld]'),
phutil_tag('tt', array(), 'mysqld'),
phutil_tag('pre', array(), 'ft_min_word_len=3'),
phutil_tag(
'pre',
array(),
"mysql> REPAIR TABLE {$namespace}_search.search_documentfield;"));
$this->newIssue('mysql.ft_min_word_len')
->setName(pht('MySQL is Using Default Minimum Word Length'))
->setSummary($summary)
->setMessage($message)
->setDatabaseRef($ref)
->addMySQLConfig('ft_min_word_len');
}
}
// NOTE: The default value of "innodb_ft_min_token_size" is 3, which is
// a reasonable value, so we do not warn about it: if it is set to
// something else, the user adjusted it on their own.
// NOTE: We populate a stopwords table at "phabricator_search.stopwords",
// but the default InnoDB stopword list is pretty reasonable (36 words,
// versus 500+ in MyISAM). Just use the builtin list until we run into
// concrete issues with it. Users can switch to our stopword table with:
//
// [mysqld]
// innodb_ft_server_stopword_table = phabricator_search/stopwords
$innodb_pool = $ref->loadRawMySQLConfigValue('innodb_buffer_pool_size');
$innodb_bytes = phutil_parse_bytes($innodb_pool);
$innodb_readable = phutil_format_bytes($innodb_bytes);
// This is arbitrary and just trying to detect values that the user
// probably didn't set themselves. The Mac OS X default is 128MB and
// 40% of an AWS EC2 Micro instance is 245MB, so keeping it somewhere
// between those two values seems like a reasonable approximation.
$minimum_readable = '225MB';
$minimum_bytes = phutil_parse_bytes($minimum_readable);
if ($innodb_bytes < $minimum_bytes) {
$summary = pht(
'MySQL (on host "%s") is configured with a very small '.
'innodb_buffer_pool_size, which may impact performance.',
$host_name);
$message = pht(
"Database host \"%s\" is configured with a very small %s (%s). ".
"This may cause poor database performance and lock exhaustion.\n\n".
"There are no hard-and-fast rules to setting an appropriate value, ".
"but a reasonable starting point for a standard install is something ".
"like 40%% of the total memory on the machine. For example, if you ".
- "have 4GB of RAM on the machine you have installed Phabricator on, ".
+ "have 4GB of RAM on the machine you have installed this software on, ".
"you might set this value to %s.\n\n".
"You can read more about this option in the MySQL documentation to ".
"help you make a decision about how to configure it for your use ".
- "case. There are no concerns specific to Phabricator which make it ".
+ "case. There are no concerns specific to this software which make it ".
"different from normal workloads with respect to this setting.\n\n".
"To adjust the setting, add something like this to your %s file (in ".
"the %s section), replacing %s with an appropriate value for your ".
"host and use case. Then restart %s:\n\n".
"%s\n".
"If you're satisfied with the current setting, you can safely ".
"ignore this setup warning.",
$host_name,
phutil_tag('tt', array(), 'innodb_buffer_pool_size'),
phutil_tag('tt', array(), $innodb_readable),
phutil_tag('tt', array(), '1600M'),
phutil_tag('tt', array(), 'my.cnf'),
phutil_tag('tt', array(), '[mysqld]'),
phutil_tag('tt', array(), '1600M'),
phutil_tag('tt', array(), 'mysqld'),
phutil_tag('pre', array(), 'innodb_buffer_pool_size=1600M'));
$this->newIssue('mysql.innodb_buffer_pool_size')
->setName(pht('MySQL May Run Slowly'))
->setSummary($summary)
->setMessage($message)
->setDatabaseRef($ref)
->addMySQLConfig('innodb_buffer_pool_size');
}
$conn = $ref->newManagementConnection();
$ok = PhabricatorStorageManagementAPI::isCharacterSetAvailableOnConnection(
'utf8mb4',
$conn);
if (!$ok) {
$summary = pht(
'You are using an old version of MySQL (on host "%s"), and should '.
'upgrade.',
$host_name);
$message = pht(
'You are using an old version of MySQL (on host "%s") which has poor '.
'unicode support (it does not support the "utf8mb4" collation set). '.
'You will encounter limitations when working with some unicode data.'.
"\n\n".
'We strongly recommend you upgrade to MySQL 5.5 or newer.',
$host_name);
$this->newIssue('mysql.utf8mb4')
->setName(pht('Old MySQL Version'))
->setSummary($summary)
->setDatabaseRef($ref)
->setMessage($message);
}
$info = queryfx_one(
$conn,
'SELECT UNIX_TIMESTAMP() epoch');
$epoch = (int)$info['epoch'];
$local = PhabricatorTime::getNow();
$delta = (int)abs($local - $epoch);
if ($delta > 60) {
$this->newIssue('mysql.clock')
->setName(pht('Major Web/Database Clock Skew'))
->setSummary(
pht(
'This web host ("%s") is set to a very different time than a '.
'database host "%s".',
php_uname('n'),
$host_name))
->setMessage(
pht(
'A database host ("%s") and this web host ("%s") disagree on the '.
'current time by more than 60 seconds (absolute skew is %s '.
'seconds). Check that the current time is set correctly '.
'everywhere.',
$host_name,
php_uname('n'),
new PhutilNumber($delta)));
}
$local_infile = $ref->loadRawMySQLConfigValue('local_infile');
if ($local_infile) {
$summary = pht(
'The MySQL "local_infile" option is enabled. This option is '.
'unsafe.');
$message = pht(
'Your MySQL server is configured with the "local_infile" option '.
'enabled. This option allows an attacker who finds an SQL injection '.
'hole to escalate their attack by copying files from the webserver '.
'into the database with "LOAD DATA LOCAL INFILE" queries, then '.
'reading the file content with "SELECT" queries.'.
"\n\n".
'You should disable this option in your %s file, in the %s section:'.
"\n\n".
'%s',
phutil_tag('tt', array(), 'my.cnf'),
phutil_tag('tt', array(), '[mysqld]'),
phutil_tag('pre', array(), 'local_infile=0'));
$this->newIssue('mysql.local_infile')
->setName(pht('Unsafe MySQL "local_infile" Setting Enabled'))
->setSummary($summary)
->setMessage($message)
->setDatabaseRef($ref)
->addMySQLConfig('local_infile');
}
}
protected function shouldUseMySQLSearchEngine() {
$services = PhabricatorSearchService::getAllServices();
foreach ($services as $service) {
if ($service instanceof PhabricatorMySQLSearchHost) {
return true;
}
}
return false;
}
}
diff --git a/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php b/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php
index bee2bc91b8..09b96d05cf 100644
--- a/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php
+++ b/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php
@@ -1,153 +1,153 @@
<?php
/**
* Noncritical PHP configuration checks.
*
* For critical checks, see @{class:PhabricatorPHPPreflightSetupCheck}.
*/
final class PhabricatorPHPConfigSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_PHP;
}
protected function executeChecks() {
if (empty($_SERVER['REMOTE_ADDR'])) {
$doc_href = PhabricatorEnv::getDoclink('Configuring a Preamble Script');
$summary = pht(
'You likely need to fix your preamble script so '.
'REMOTE_ADDR is no longer empty.');
$message = pht(
- 'No REMOTE_ADDR is available, so Phabricator cannot determine the '.
- 'origin address for requests. This will prevent Phabricator from '.
+ 'No REMOTE_ADDR is available, so this server cannot determine the '.
+ 'origin address for requests. This will prevent the software from '.
'performing important security checks. This most often means you '.
'have a mistake in your preamble script. Consult the documentation '.
'(%s) and double-check that the script is written correctly.',
phutil_tag(
'a',
array(
'href' => $doc_href,
'target' => '_blank',
),
pht('Configuring a Preamble Script')));
$this->newIssue('php.remote_addr')
->setName(pht('No REMOTE_ADDR available'))
->setSummary($summary)
->setMessage($message);
}
if (version_compare(phpversion(), '7', '>=')) {
// This option was removed in PHP7.
$raw_post_data = -1;
} else {
$raw_post_data = (int)ini_get('always_populate_raw_post_data');
}
if ($raw_post_data != -1) {
$summary = pht(
'PHP setting "%s" should be set to "-1" to avoid deprecation '.
'warnings.',
'always_populate_raw_post_data');
$message = pht(
'The "%s" key is set to some value other than "-1" in your PHP '.
'configuration. This can cause PHP to raise deprecation warnings '.
'during process startup. Set this option to "-1" to prevent these '.
'warnings from appearing.',
'always_populate_raw_post_data');
$this->newIssue('php.always_populate_raw_post_data')
->setName(pht('Disable PHP %s', 'always_populate_raw_post_data'))
->setSummary($summary)
->setMessage($message)
->addPHPConfig('always_populate_raw_post_data');
}
if (!extension_loaded('mysqli')) {
$summary = pht(
'Install the MySQLi extension to improve database behavior.');
$message = pht(
'PHP is currently using the very old "mysql" extension to interact '.
'with the database. You should install the newer "mysqli" extension '.
'to improve behaviors (like error handling and query timeouts).'.
"\n\n".
- 'Phabricator will work with the older extension, but upgrading to the '.
- 'newer extension is recommended.'.
+ 'This software will work with the older extension, but upgrading to '.
+ 'the newer extension is recommended.'.
"\n\n".
'You may be able to install the extension with a command like: %s',
// NOTE: We're intentionally telling you to install "mysqlnd" here; on
// Ubuntu, there's no separate "mysqli" package.
phutil_tag('tt', array(), 'sudo apt-get install php5-mysqlnd'));
$this->newIssue('php.mysqli')
->setName(pht('MySQLi Extension Not Available'))
->setSummary($summary)
->setMessage($message);
} else if (!defined('MYSQLI_ASYNC')) {
$summary = pht(
'Configure the MySQL Native Driver to improve database behavior.');
$message = pht(
'PHP is currently using the older MySQL external driver instead of '.
'the newer MySQL native driver. The older driver lacks options and '.
- 'features (like support for query timeouts) which allow Phabricator '.
+ 'features (like support for query timeouts) which allow this server '.
'to interact better with the database.'.
"\n\n".
- 'Phabricator will work with the older driver, but upgrading to the '.
+ 'This software will work with the older driver, but upgrading to the '.
'native driver is recommended.'.
"\n\n".
'You may be able to install the native driver with a command like: %s',
phutil_tag('tt', array(), 'sudo apt-get install php5-mysqlnd'));
$this->newIssue('php.myqlnd')
->setName(pht('MySQL Native Driver Not Available'))
->setSummary($summary)
->setMessage($message);
}
if (extension_loaded('mysqli')) {
$infile_key = 'mysqli.allow_local_infile';
} else {
$infile_key = 'mysql.allow_local_infile';
}
if (ini_get($infile_key)) {
$summary = pht(
'Disable unsafe option "%s" in PHP configuration.',
$infile_key);
$message = pht(
'PHP is currently configured to honor requests from any MySQL server '.
'it connects to for the content of any local file.'.
"\n\n".
'This capability supports MySQL "LOAD DATA LOCAL INFILE" queries, but '.
'allows a malicious MySQL server read access to the local disk: the '.
'server can ask the client to send the content of any local file, '.
'and the client will comply.'.
"\n\n".
'Although it is normally difficult for an attacker to convince '.
- 'Phabricator to connect to a malicious MySQL server, you should '.
+ 'this software to connect to a malicious MySQL server, you should '.
'disable this option: this capability is unnecessary and inherently '.
'dangerous.'.
"\n\n".
'To disable this option, set: %s',
phutil_tag('tt', array(), pht('%s = 0', $infile_key)));
$this->newIssue('php.'.$infile_key)
->setName(pht('Unsafe PHP "Local Infile" Configuration'))
->setSummary($summary)
->setMessage($message)
->addPHPConfig($infile_key);
}
}
}
diff --git a/src/applications/config/check/PhabricatorPHPPreflightSetupCheck.php b/src/applications/config/check/PhabricatorPHPPreflightSetupCheck.php
index 30c6036c8f..02c1134dc3 100644
--- a/src/applications/config/check/PhabricatorPHPPreflightSetupCheck.php
+++ b/src/applications/config/check/PhabricatorPHPPreflightSetupCheck.php
@@ -1,147 +1,150 @@
<?php
final class PhabricatorPHPPreflightSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_PHP;
}
public function isPreflightCheck() {
return true;
}
protected function executeChecks() {
$version = phpversion();
if (version_compare($version, 7, '>=') &&
version_compare($version, 7.1, '<')) {
$message = pht(
- 'You are running PHP version %s. Phabricator does not support PHP '.
- 'versions between 7.0 and 7.1.'.
+ 'You are running PHP version %s. PHP versions between 7.0 and 7.1 '.
+ 'are not supported'.
"\n\n".
- 'PHP removed signal handling features that Phabricator requires in '.
- 'PHP 7.0, and did not restore them until PHP 7.1.'.
+ 'PHP removed reqiured signal handling features in '.
+ 'PHP 7.0, and did not restore an equivalent mechanism until PHP 7.1.'.
"\n\n".
'Upgrade to PHP 7.1 or newer (recommended) or downgrade to an older '.
'version of PHP 5 (discouraged).',
$version);
$this->newIssue('php.version7')
->setIsFatal(true)
->setName(pht('PHP 7.0-7.1 Not Supported'))
->setMessage($message)
->addLink(
'https://phurl.io/u/php7',
- pht('Phabricator PHP 7 Compatibility Information'));
+ pht('PHP 7 Compatibility Information'));
return;
}
+ // TODO: This can be removed entirely because the minimum PHP version is
+ // now PHP 5.5, which does not have safe mode.
+
$safe_mode = ini_get('safe_mode');
if ($safe_mode) {
$message = pht(
- "You have '%s' enabled in your PHP configuration, but Phabricator ".
+ "You have '%s' enabled in your PHP configuration, but this software ".
"will not run in safe mode. Safe mode has been deprecated in PHP 5.3 ".
"and removed in PHP 5.4.\n\nDisable safe mode to continue.",
'safe_mode');
$this->newIssue('php.safe_mode')
->setIsFatal(true)
->setName(pht('Disable PHP %s', 'safe_mode'))
->setMessage($message)
->addPHPConfig('safe_mode');
return;
}
// Check for `disable_functions` or `disable_classes`. Although it's
// possible to disable a bunch of functions (say, `array_change_key_case()`)
// and classes and still have Phabricator work fine, it's unreasonably
// difficult for us to be sure we'll even survive setup if these options
// are enabled. Phabricator needs access to the most dangerous functions,
// so there is no reasonable configuration value here which actually
// provides a benefit while guaranteeing Phabricator will run properly.
$disable_options = array('disable_functions', 'disable_classes');
foreach ($disable_options as $disable_option) {
$disable_value = ini_get($disable_option);
if ($disable_value) {
// By default Debian installs the pcntl extension but disables all of
// its functions using configuration. Whitelist disabling these
// functions so that Debian PHP works out of the box (we do not need to
// call these functions from the web UI). This is pretty ridiculous but
// it's not the users' fault and they haven't done anything crazy to
// get here, so don't make them pay for Debian's unusual choices.
// See: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=605571
$fatal = true;
if ($disable_option == 'disable_functions') {
$functions = preg_split('/[, ]+/', $disable_value);
$functions = array_filter($functions);
foreach ($functions as $k => $function) {
if (preg_match('/^pcntl_/', $function)) {
unset($functions[$k]);
}
}
if (!$functions) {
$fatal = false;
}
}
if ($fatal) {
$message = pht(
"You have '%s' enabled in your PHP configuration.\n\n".
- "This option is not compatible with Phabricator. Remove ".
+ "This option is not compatible with this software. Remove ".
"'%s' from your configuration to continue.",
$disable_option,
$disable_option);
$this->newIssue('php.'.$disable_option)
->setIsFatal(true)
->setName(pht('Remove PHP %s', $disable_option))
->setMessage($message)
->addPHPConfig($disable_option);
}
}
}
$overload_option = 'mbstring.func_overload';
$func_overload = ini_get($overload_option);
if ($func_overload) {
$message = pht(
"You have '%s' enabled in your PHP configuration.\n\n".
- "This option is not compatible with Phabricator. Disable ".
+ "This option is not compatible with this software. Disable ".
"'%s' in your PHP configuration to continue.",
$overload_option,
$overload_option);
$this->newIssue('php'.$overload_option)
->setIsFatal(true)
->setName(pht('Disable PHP %s', $overload_option))
->setMessage($message)
->addPHPConfig($overload_option);
}
$open_basedir = ini_get('open_basedir');
if (strlen($open_basedir)) {
// If `open_basedir` is set, just fatal. It's technically possible for
// us to run with certain values of `open_basedir`, but: we can only
// raise fatal errors from preflight steps, so we'd have to do this check
// in two parts to support fatal and advisory versions; it's much simpler
// to just fatal instead of trying to test all the different things we
// may need to access in the filesystem; and use of this option seems
// rare (particularly in supported environments).
$message = pht(
- "Your server is configured with '%s', which prevents Phabricator ".
+ "Your server is configured with '%s', which prevents this software ".
"from opening files it requires access to.\n\n".
"Disable this setting to continue.",
'open_basedir');
$issue = $this->newIssue('php.open_basedir')
->setName(pht('Disable PHP %s', 'open_basedir'))
->addPHPConfig('open_basedir')
->setIsFatal(true)
->setMessage($message);
}
}
}
diff --git a/src/applications/config/check/PhabricatorPathSetupCheck.php b/src/applications/config/check/PhabricatorPathSetupCheck.php
index 75c89d332a..8a98c5ba9f 100644
--- a/src/applications/config/check/PhabricatorPathSetupCheck.php
+++ b/src/applications/config/check/PhabricatorPathSetupCheck.php
@@ -1,136 +1,136 @@
<?php
final class PhabricatorPathSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_OTHER;
}
protected function executeChecks() {
// NOTE: We've already appended `environment.append-paths`, so we don't
// need to explicitly check for it.
$path = getenv('PATH');
if (!$path) {
$summary = pht(
- 'The environmental variable %s is empty. Phabricator will not '.
+ 'The environmental variable %s is empty. This server will not '.
'be able to execute some commands.',
'$PATH');
$message = pht(
- "The environmental variable %s is empty. Phabricator needs to execute ".
+ "The environmental variable %s is empty. This server needs to execute ".
"some system commands, like `%s`, `%s`, `%s`, and `%s`. To execute ".
"these commands, the binaries must be available in the webserver's ".
- "%s. You can set additional paths in Phabricator configuration.",
+ "%s. You can set additional paths in configuration.",
'$PATH',
'svn',
'git',
'hg',
'diff',
'$PATH');
$this
->newIssue('config.environment.append-paths')
->setName(pht('%s Not Set', '$PATH'))
->setSummary($summary)
->setMessage($message)
->addPhabricatorConfig('environment.append-paths');
// Bail on checks below.
return;
}
// Users are remarkably industrious at misconfiguring software. Try to
// catch mistaken configuration of PATH.
$path_parts = explode(PATH_SEPARATOR, $path);
$bad_paths = array();
foreach ($path_parts as $path_part) {
if (!strlen($path_part)) {
continue;
}
$message = null;
$not_exists = false;
foreach (Filesystem::walkToRoot($path_part) as $part) {
if (!Filesystem::pathExists($part)) {
$not_exists = $part;
// Walk up so we can tell if this is a readability issue or not.
continue;
} else if (!is_dir(Filesystem::resolvePath($part))) {
$message = pht(
"The PATH component '%s' (which resolves as the absolute path ".
"'%s') is not usable because '%s' is not a directory.",
$path_part,
Filesystem::resolvePath($path_part),
$part);
} else if (!is_readable($part)) {
$message = pht(
"The PATH component '%s' (which resolves as the absolute path ".
"'%s') is not usable because '%s' is not readable.",
$path_part,
Filesystem::resolvePath($path_part),
$part);
} else if ($not_exists) {
$message = pht(
"The PATH component '%s' (which resolves as the absolute path ".
"'%s') is not usable because '%s' does not exist.",
$path_part,
Filesystem::resolvePath($path_part),
$not_exists);
} else {
// Everything seems good.
break;
}
if ($message !== null) {
break;
}
}
if ($message === null) {
if (!phutil_is_windows() && !@file_exists($path_part.'/.')) {
$message = pht(
"The PATH component '%s' (which resolves as the absolute path ".
"'%s') is not usable because it is not traversable (its '%s' ".
"permission bit is not set).",
$path_part,
Filesystem::resolvePath($path_part),
'+x');
}
}
if ($message !== null) {
$bad_paths[$path_part] = $message;
}
}
if ($bad_paths) {
foreach ($bad_paths as $path_part => $message) {
$digest = substr(PhabricatorHash::weakDigest($path_part), 0, 8);
$this
->newIssue('config.PATH.'.$digest)
->setName(pht('%s Component Unusable', '$PATH'))
->setSummary(
pht(
'A component of the configured PATH can not be used by '.
'the webserver: %s',
$path_part))
->setMessage(
pht(
"The configured PATH includes a component which is not usable. ".
- "Phabricator will be unable to find or execute binaries located ".
+ "This server will be unable to find or execute binaries located ".
"here:".
"\n\n".
"%s".
"\n\n".
"The user that the webserver runs as must be able to read all ".
"the directories in PATH in order to make use of them.",
$message))
->addPhabricatorConfig('environment.append-paths');
}
}
}
}
diff --git a/src/applications/config/check/PhabricatorPygmentSetupCheck.php b/src/applications/config/check/PhabricatorPygmentSetupCheck.php
index 0ffb4ff7ad..74dfe5a324 100644
--- a/src/applications/config/check/PhabricatorPygmentSetupCheck.php
+++ b/src/applications/config/check/PhabricatorPygmentSetupCheck.php
@@ -1,83 +1,83 @@
<?php
final class PhabricatorPygmentSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_OTHER;
}
protected function executeChecks() {
$pygment = PhabricatorEnv::getEnvConfig('pygments.enabled');
if ($pygment) {
if (!Filesystem::binaryExists('pygmentize')) {
$summary = pht(
'You enabled pygments but the %s script is not '.
'actually available, your %s is probably broken.',
'pygmentize',
'$PATH');
$message = pht(
'The environmental variable %s does not contain %s. '.
'You have enabled pygments, which requires '.
'%s to be available in your %s variable.',
'$PATH',
'pygmentize',
'pygmentize',
'$PATH');
$this
->newIssue('pygments.enabled')
->setName(pht('%s Not Found', 'pygmentize'))
->setSummary($summary)
->setMessage($message)
->addRelatedPhabricatorConfig('pygments.enabled')
->addPhabricatorConfig('environment.append-paths');
} else {
list($err) = exec_manual('pygmentize -h');
if ($err) {
$summary = pht(
'You have enabled pygments and the %s script is '.
'available, but does not seem to work.',
'pygmentize');
$message = pht(
- 'Phabricator has %s available in %s, but the binary '.
+ 'This server has %s available in %s, but the binary '.
'exited with an error code when run as %s. Check that it is '.
'installed correctly.',
phutil_tag('tt', array(), 'pygmentize'),
phutil_tag('tt', array(), '$PATH'),
phutil_tag('tt', array(), 'pygmentize -h'));
$this
->newIssue('pygments.failed')
->setName(pht('%s Not Working', 'pygmentize'))
->setSummary($summary)
->setMessage($message)
->addRelatedPhabricatorConfig('pygments.enabled')
->addPhabricatorConfig('environment.append-paths');
}
}
} else {
$summary = pht(
'Pygments should be installed and enabled '.
'to provide advanced syntax highlighting.');
$message = pht(
- 'Phabricator can highlight a few languages by default, '.
+ 'This software can highlight a few languages by default, '.
'but installing and enabling Pygments (a third-party highlighting '.
"tool) will add syntax highlighting for many more languages. \n\n".
'For instructions on installing and enabling Pygments, see the '.
'%s configuration option.'."\n\n".
'If you do not want to install Pygments, you can ignore this issue.',
phutil_tag('tt', array(), 'pygments.enabled'));
$this
->newIssue('pygments.noenabled')
->setName(pht('Install Pygments to Improve Syntax Highlighting'))
->setSummary($summary)
->setMessage($message)
->addRelatedPhabricatorConfig('pygments.enabled');
}
}
}
diff --git a/src/applications/config/check/PhabricatorRepositoriesSetupCheck.php b/src/applications/config/check/PhabricatorRepositoriesSetupCheck.php
index 64073ec1ed..7d010fd954 100644
--- a/src/applications/config/check/PhabricatorRepositoriesSetupCheck.php
+++ b/src/applications/config/check/PhabricatorRepositoriesSetupCheck.php
@@ -1,66 +1,66 @@
<?php
final class PhabricatorRepositoriesSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_OTHER;
}
protected function executeChecks() {
$cluster_services = id(new AlmanacServiceQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withServiceTypes(
array(
AlmanacClusterRepositoryServiceType::SERVICETYPE,
))
->setLimit(1)
->execute();
if ($cluster_services) {
// If cluster repository services are defined, these checks aren't useful
// because some nodes (like web nodes) will usually not have any local
// repository information.
// Errors with this configuration will still be detected by checks on
// individual repositories.
return;
}
$repo_path = PhabricatorEnv::getEnvConfig('repository.default-local-path');
if (!$repo_path) {
$summary = pht(
"The configuration option '%s' is not set.",
'repository.default-local-path');
$this->newIssue('repository.default-local-path.empty')
->setName(pht('Missing Repository Local Path'))
->setSummary($summary)
->addPhabricatorConfig('repository.default-local-path');
return;
}
if (!Filesystem::pathExists($repo_path)) {
$summary = pht(
'The path for local repositories does not exist, or is not '.
'readable by the webserver.');
$message = pht(
"The directory for local repositories (%s) does not exist, or is not ".
- "readable by the webserver. Phabricator uses this directory to store ".
- "information about repositories. If this directory does not exist, ".
- "create it:\n\n".
+ "readable by the webserver. This software uses this directory to ".
+ "store information about repositories. If this directory does not ".
+ "exist, create it:\n\n".
"%s\n".
"If this directory exists, make it readable to the webserver. You ".
"can also edit the configuration below to use some other directory.",
phutil_tag('tt', array(), $repo_path),
phutil_tag('pre', array(), csprintf('$ mkdir -p %s', $repo_path)));
$this->newIssue('repository.default-local-path.empty')
->setName(pht('Missing Repository Local Path'))
->setSummary($summary)
->setMessage($message)
->addPhabricatorConfig('repository.default-local-path');
}
}
}
diff --git a/src/applications/config/check/PhabricatorSecuritySetupCheck.php b/src/applications/config/check/PhabricatorSecuritySetupCheck.php
index 0cd1b41504..e84e26a208 100644
--- a/src/applications/config/check/PhabricatorSecuritySetupCheck.php
+++ b/src/applications/config/check/PhabricatorSecuritySetupCheck.php
@@ -1,77 +1,77 @@
<?php
final class PhabricatorSecuritySetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_OTHER;
}
protected function executeChecks() {
// This checks for a version of bash with the "Shellshock" vulnerability.
// For details, see T6185.
$payload = array(
'SHELLSHOCK_PAYLOAD' => '() { :;} ; echo VULNERABLE',
);
list($err, $stdout) = id(new ExecFuture('echo shellshock-test'))
->setEnv($payload, $wipe_process_env = true)
->resolve();
if (!$err && preg_match('/VULNERABLE/', $stdout)) {
$summary = pht(
'This system has an unpatched version of Bash with a severe, widely '.
'disclosed vulnerability.');
$message = pht(
'The version of %s on this system is out of date and contains a '.
'major, widely disclosed vulnerability (the "Shellshock" '.
'vulnerability).'.
"\n\n".
'Upgrade %s to a patched version.'.
"\n\n".
- 'To learn more about how this issue affects Phabricator, see %s.',
+ 'To learn more about how this issue affects this software, see %s.',
phutil_tag('tt', array(), 'bash'),
phutil_tag('tt', array(), 'bash'),
phutil_tag(
'a',
array(
'href' => 'https://secure.phabricator.com/T6185',
'target' => '_blank',
),
pht('T6185 "Shellshock" Bash Vulnerability')));
$this
->newIssue('security.shellshock')
->setName(pht('Severe Security Vulnerability: Unpatched Bash'))
->setSummary($summary)
->setMessage($message);
}
$file_key = 'security.alternate-file-domain';
$file_domain = PhabricatorEnv::getEnvConfig($file_key);
if (!$file_domain) {
$doc_href = PhabricatorEnv::getDoclink('Configuring a File Domain');
$this->newIssue('security.'.$file_key)
->setName(pht('Alternate File Domain Not Configured'))
->setSummary(
pht(
'Improve security by configuring an alternate file domain.'))
->setMessage(
pht(
- 'Phabricator is currently configured to serve user uploads '.
+ 'This software is currently configured to serve user uploads '.
'directly from the same domain as other content. This is a '.
'security risk.'.
"\n\n".
'Configure a CDN (or alternate file domain) to eliminate this '.
'risk. Using a CDN will also improve performance. See the '.
'guide below for instructions.'))
->addPhabricatorConfig($file_key)
->addLink(
$doc_href,
pht('Configuration Guide: Configuring a File Domain'));
}
}
}
diff --git a/src/applications/config/check/PhabricatorStorageSetupCheck.php b/src/applications/config/check/PhabricatorStorageSetupCheck.php
index cc74cce2ea..86aa1f2ebd 100644
--- a/src/applications/config/check/PhabricatorStorageSetupCheck.php
+++ b/src/applications/config/check/PhabricatorStorageSetupCheck.php
@@ -1,196 +1,196 @@
<?php
final class PhabricatorStorageSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_OTHER;
}
/**
* @phutil-external-symbol class PhabricatorStartup
*/
protected function executeChecks() {
$engines = PhabricatorFileStorageEngine::loadWritableChunkEngines();
$chunk_engine_active = (bool)$engines;
$this->checkS3();
if (!$chunk_engine_active) {
$doc_href = PhabricatorEnv::getDoclink('Configuring File Storage');
$message = pht(
'Large file storage has not been configured, which will limit '.
'the maximum size of file uploads. See %s for '.
'instructions on configuring uploads and storage.',
phutil_tag(
'a',
array(
'href' => $doc_href,
'target' => '_blank',
),
pht('Configuring File Storage')));
$this
->newIssue('large-files')
->setShortName(pht('Large Files'))
->setName(pht('Large File Storage Not Configured'))
->setMessage($message);
}
$post_max_size = ini_get('post_max_size');
if ($post_max_size && ((int)$post_max_size > 0)) {
$post_max_bytes = phutil_parse_bytes($post_max_size);
$post_max_need = (32 * 1024 * 1024);
if ($post_max_need > $post_max_bytes) {
$summary = pht(
'Set %s in your PHP configuration to at least 32MB '.
'to support large file uploads.',
phutil_tag('tt', array(), 'post_max_size'));
$message = pht(
'Adjust %s in your PHP configuration to at least 32MB. When '.
'set to smaller value, large file uploads may not work properly.',
phutil_tag('tt', array(), 'post_max_size'));
$this
->newIssue('php.post_max_size')
->setName(pht('PHP post_max_size Not Configured'))
->setSummary($summary)
->setMessage($message)
->setGroup(self::GROUP_PHP)
->addPHPConfig('post_max_size');
}
}
// This is somewhat arbitrary, but make sure we have enough headroom to
// upload a default file at the chunk threshold (8MB), which may be
// base64 encoded, then JSON encoded in the request, and may need to be
// held in memory in the raw and as a query string.
$need_bytes = (64 * 1024 * 1024);
$memory_limit = PhabricatorStartup::getOldMemoryLimit();
if ($memory_limit && ((int)$memory_limit > 0)) {
$memory_limit_bytes = phutil_parse_bytes($memory_limit);
$memory_usage_bytes = memory_get_usage();
$available_bytes = ($memory_limit_bytes - $memory_usage_bytes);
if ($need_bytes > $available_bytes) {
$summary = pht(
'Your PHP memory limit is configured in a way that may prevent '.
'you from uploading large files or handling large requests.');
$message = pht(
'When you upload a file via drag-and-drop or the API, chunks must '.
'be buffered into memory before being written to permanent '.
- 'storage. Phabricator needs memory available to store these '.
+ 'storage. This server needs memory available to store these '.
'chunks while they are uploaded, but PHP is currently configured '.
'to severely limit the available memory.'.
"\n\n".
'PHP processes currently have very little free memory available '.
'(%s). To work well, processes should have at least %s.'.
"\n\n".
'(Note that the application itself must also fit in available '.
'memory, so not all of the memory under the memory limit is '.
'available for running workloads.)'.
"\n\n".
"The easiest way to resolve this issue is to set %s to %s in your ".
"PHP configuration, to disable the memory limit. There is ".
"usually little or no value to using this option to limit ".
- "Phabricator process memory.".
+ "process memory.".
"\n\n".
"You can also increase the limit or ignore this issue and accept ".
"that you may encounter problems uploading large files and ".
"processing large requests.",
phutil_format_bytes($available_bytes),
phutil_format_bytes($need_bytes),
phutil_tag('tt', array(), 'memory_limit'),
phutil_tag('tt', array(), '-1'));
$this
->newIssue('php.memory_limit.upload')
->setName(pht('Memory Limit Restricts File Uploads'))
->setSummary($summary)
->setMessage($message)
->setGroup(self::GROUP_PHP)
->addPHPConfig('memory_limit')
->addPHPConfigOriginalValue('memory_limit', $memory_limit);
}
}
$local_path = PhabricatorEnv::getEnvConfig('storage.local-disk.path');
if (!$local_path) {
return;
}
if (!Filesystem::pathExists($local_path) ||
!is_readable($local_path) ||
!is_writable($local_path)) {
$message = pht(
'Configured location for storing uploaded files on disk ("%s") does '.
'not exist, or is not readable or writable. Verify the directory '.
'exists and is readable and writable by the webserver.',
$local_path);
$this
->newIssue('config.storage.local-disk.path')
->setShortName(pht('Local Disk Storage'))
->setName(pht('Local Disk Storage Not Readable/Writable'))
->setMessage($message)
->addPhabricatorConfig('storage.local-disk.path');
}
}
private function checkS3() {
$access_key = PhabricatorEnv::getEnvConfig('amazon-s3.access-key');
$secret_key = PhabricatorEnv::getEnvConfig('amazon-s3.secret-key');
$region = PhabricatorEnv::getEnvConfig('amazon-s3.region');
$endpoint = PhabricatorEnv::getEnvConfig('amazon-s3.endpoint');
$how_many = 0;
if (strlen($access_key)) {
$how_many++;
}
if (strlen($secret_key)) {
$how_many++;
}
if (strlen($region)) {
$how_many++;
}
if (strlen($endpoint)) {
$how_many++;
}
// Nothing configured, no issues here.
if ($how_many === 0) {
return;
}
// Everything configured, no issues here.
if ($how_many === 4) {
return;
}
$message = pht(
'File storage in Amazon S3 has been partially configured, but you are '.
'missing some required settings. S3 will not be available to store '.
'files until you complete the configuration. Either configure S3 fully '.
'or remove the partial configuration.');
$this->newIssue('storage.s3.partial-config')
->setShortName(pht('S3 Partially Configured'))
->setName(pht('Amazon S3 is Only Partially Configured'))
->setMessage($message)
->addPhabricatorConfig('amazon-s3.access-key')
->addPhabricatorConfig('amazon-s3.secret-key')
->addPhabricatorConfig('amazon-s3.region')
->addPhabricatorConfig('amazon-s3.endpoint');
}
}
diff --git a/src/applications/config/check/PhabricatorTimezoneSetupCheck.php b/src/applications/config/check/PhabricatorTimezoneSetupCheck.php
index 08f6a28d10..71036724f7 100644
--- a/src/applications/config/check/PhabricatorTimezoneSetupCheck.php
+++ b/src/applications/config/check/PhabricatorTimezoneSetupCheck.php
@@ -1,57 +1,58 @@
<?php
final class PhabricatorTimezoneSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_OTHER;
}
protected function executeChecks() {
$php_value = ini_get('date.timezone');
if ($php_value) {
$old = date_default_timezone_get();
$ok = @date_default_timezone_set($php_value);
date_default_timezone_set($old);
if (!$ok) {
$message = pht(
'Your PHP configuration selects an invalid timezone. '.
'Select a valid timezone.');
$this
->newIssue('php.date.timezone')
->setShortName(pht('PHP Timezone'))
->setName(pht('PHP Timezone Invalid'))
->setMessage($message)
->addPHPConfig('date.timezone');
}
}
$timezone = nonempty(
PhabricatorEnv::getEnvConfig('phabricator.timezone'),
ini_get('date.timezone'));
if ($timezone) {
return;
}
$summary = pht(
'Without a configured timezone, PHP will emit warnings when working '.
'with dates, and dates and times may not display correctly.');
$message = pht(
"Your configuration fails to specify a server timezone. You can either ".
- "set the PHP configuration value '%s' or the Phabricator ".
- "configuration value '%s' to specify one.",
+ "set the PHP configuration value '%s' or the %s configuration ".
+ "value '%s' to specify one.",
'date.timezone',
+ PlatformSymbols::getPlatformServerName(),
'phabricator.timezone');
$this
->newIssue('config.timezone')
->setShortName(pht('Timezone'))
->setName(pht('Server Timezone Not Configured'))
->setSummary($summary)
->setMessage($message)
->addPHPConfig('date.timezone')
->addPhabricatorConfig('phabricator.timezone');
}
}
diff --git a/src/applications/config/check/PhabricatorWebServerSetupCheck.php b/src/applications/config/check/PhabricatorWebServerSetupCheck.php
index 46255629e7..cc9660325c 100644
--- a/src/applications/config/check/PhabricatorWebServerSetupCheck.php
+++ b/src/applications/config/check/PhabricatorWebServerSetupCheck.php
@@ -1,384 +1,384 @@
<?php
final class PhabricatorWebServerSetupCheck extends PhabricatorSetupCheck {
public function getDefaultGroup() {
return self::GROUP_OTHER;
}
protected function executeChecks() {
// The documentation says these headers exist, but it's not clear if they
// are entirely reliable in practice.
if (isset($_SERVER['HTTP_X_MOD_PAGESPEED']) ||
isset($_SERVER['HTTP_X_PAGE_SPEED'])) {
$this->newIssue('webserver.pagespeed')
->setName(pht('Disable Pagespeed'))
->setSummary(pht('Pagespeed is enabled, but should be disabled.'))
->setMessage(
pht(
- 'Phabricator received an "X-Mod-Pagespeed" or "X-Page-Speed" '.
+ 'This server received an "X-Mod-Pagespeed" or "X-Page-Speed" '.
'HTTP header on this request, which indicates that you have '.
'enabled "mod_pagespeed" on this server. This module is not '.
- 'compatible with Phabricator. You should disable it.'));
+ 'compatible with this software. You should disable the module.'));
}
$base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
if (!strlen($base_uri)) {
// If `phabricator.base-uri` is not set then we can't really do
// anything.
return;
}
$expect_user = 'alincoln';
$expect_pass = 'hunter2';
$send_path = '/test-%252A/';
$expect_path = '/test-%2A/';
$expect_key = 'duck-sound';
$expect_value = 'quack';
$base_uri = id(new PhutilURI($base_uri))
->setPath($send_path)
->replaceQueryParam($expect_key, $expect_value);
$self_future = id(new HTTPSFuture($base_uri))
- ->addHeader('X-Phabricator-SelfCheck', 1)
+ ->addHeader('X-Setup-SelfCheck', 1)
->addHeader('Accept-Encoding', 'gzip')
->setDisableContentDecoding(true)
->setHTTPBasicAuthCredentials(
$expect_user,
new PhutilOpaqueEnvelope($expect_pass))
->setTimeout(5);
if (AphrontRequestStream::supportsGzip()) {
$gzip_uncompressed = str_repeat('Quack! ', 128);
$gzip_compressed = gzencode($gzip_uncompressed);
$gzip_future = id(new HTTPSFuture($base_uri))
- ->addHeader('X-Phabricator-SelfCheck', 1)
+ ->addHeader('X-Setup-SelfCheck', 1)
->addHeader('Content-Encoding', 'gzip')
->setTimeout(5)
->setData($gzip_compressed);
} else {
$gzip_future = null;
}
// Make a request to the metadata service available on EC2 instances,
// to test if we're running on a T2 instance in AWS so we can warn that
// this is a bad idea. Outside of AWS, this request will just fail.
$ec2_uri = 'http://169.254.169.254/latest/meta-data/instance-type';
$ec2_future = id(new HTTPSFuture($ec2_uri))
->setTimeout(1);
$futures = array(
$self_future,
$ec2_future,
);
if ($gzip_future) {
$futures[] = $gzip_future;
}
$futures = new FutureIterator($futures);
foreach ($futures as $future) {
// Just resolve the futures here.
}
try {
list($body) = $ec2_future->resolvex();
$body = trim($body);
if (preg_match('/^t2/', $body)) {
$message = pht(
- 'Phabricator appears to be installed on a very small EC2 instance '.
+ 'This software appears to be installed on a very small EC2 instance '.
'(of class "%s") with burstable CPU. This is strongly discouraged. '.
- 'Phabricator regularly needs CPU, and these instances are often '.
+ 'This software regularly needs CPU, and these instances are often '.
'choked to death by CPU throttling. Use an instance with a normal '.
'CPU instead.',
$body);
$this->newIssue('ec2.burstable')
->setName(pht('Installed on Burstable CPU Instance'))
->setSummary(
pht(
- 'Do not install Phabricator on an instance class with '.
+ 'Do not install this software on an instance class with '.
'burstable CPU.'))
->setMessage($message);
}
} catch (Exception $ex) {
// If this fails, just continue. We're probably not running in EC2.
}
try {
list($body, $headers) = $self_future->resolvex();
} catch (Exception $ex) {
// If this fails for whatever reason, just ignore it. Hopefully, the
// error is obvious and the user can correct it on their own, but we
// can't do much to offer diagnostic advice.
return;
}
if (BaseHTTPFuture::getHeader($headers, 'Content-Encoding') != 'gzip') {
$message = pht(
- 'Phabricator sent itself a request with "Accept-Encoding: gzip", '.
+ 'This software sent itself a request with "Accept-Encoding: gzip", '.
'but received an uncompressed response.'.
"\n\n".
'This may indicate that your webserver is not configured to '.
'compress responses. If so, you should enable compression. '.
'Compression can dramatically improve performance, especially '.
'for clients with less bandwidth.');
$this->newIssue('webserver.gzip')
->setName(pht('GZip Compression May Not Be Enabled'))
->setSummary(pht('Your webserver may have compression disabled.'))
->setMessage($message);
} else {
if (function_exists('gzdecode')) {
$body = @gzdecode($body);
} else {
$body = null;
}
if (!$body) {
// For now, just bail if we can't decode the response.
// This might need to use the stronger magic in "AphrontRequestStream"
// to decode more reliably.
return;
}
}
$structure = null;
$extra_whitespace = ($body !== trim($body));
try {
$structure = phutil_json_decode(trim($body));
} catch (Exception $ex) {
// Ignore the exception, we only care if the decode worked or not.
}
if (!$structure || $extra_whitespace) {
if (!$structure) {
$short = id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs(1024)
->truncateString($body);
$message = pht(
- 'Phabricator sent itself a test request with the '.
- '"X-Phabricator-SelfCheck" header and expected to get a valid JSON '.
+ 'This software sent itself a test request with the '.
+ '"X-Setup-SelfCheck" header and expected to get a valid JSON '.
'response back. Instead, the response begins:'.
"\n\n".
'%s'.
"\n\n".
'Something is misconfigured or otherwise mangling responses.',
phutil_tag('pre', array(), $short));
} else {
$message = pht(
- 'Phabricator sent itself a test request and expected to get a bare '.
- 'JSON response back. It received a JSON response, but the response '.
- 'had extra whitespace at the beginning or end.'.
+ 'This software sent itself a test request and expected to get a '.
+ 'bare JSON response back. It received a JSON response, but the '.
+ 'response had extra whitespace at the beginning or end.'.
"\n\n".
'This usually means you have edited a file and left whitespace '.
'characters before the opening %s tag, or after a closing %s tag. '.
'Remove any leading whitespace, and prefer to omit closing tags.',
phutil_tag('tt', array(), '<?php'),
phutil_tag('tt', array(), '?>'));
}
$this->newIssue('webserver.mangle')
->setName(pht('Mangled Webserver Response'))
->setSummary(pht('Your webserver produced an unexpected response.'))
->setMessage($message);
// We can't run the other checks if we could not decode the response.
if (!$structure) {
return;
}
}
$actual_user = idx($structure, 'user');
$actual_pass = idx($structure, 'pass');
if (($expect_user != $actual_user) || ($actual_pass != $expect_pass)) {
$message = pht(
- 'Phabricator sent itself a test request with an "Authorization" HTTP '.
- 'header, and expected those credentials to be transmitted. However, '.
- 'they were absent or incorrect when received. Phabricator sent '.
- 'username "%s" with password "%s"; received username "%s" and '.
- 'password "%s".'.
+ 'This software sent itself a test request with an "Authorization" '.
+ 'HTTP header, and expected those credentials to be transmitted. '.
+ 'However, they were absent or incorrect when received. This '.
+ 'software sent username "%s" with password "%s"; received '.
+ 'username "%s" and password "%s".'.
"\n\n".
'Your webserver may not be configured to forward HTTP basic '.
'authentication. If you plan to use basic authentication (for '.
'example, to access repositories) you should reconfigure it.',
$expect_user,
$expect_pass,
$actual_user,
$actual_pass);
$this->newIssue('webserver.basic-auth')
->setName(pht('HTTP Basic Auth Not Configured'))
->setSummary(pht('Your webserver is not forwarding credentials.'))
->setMessage($message);
}
$actual_path = idx($structure, 'path');
if ($expect_path != $actual_path) {
$message = pht(
- 'Phabricator sent itself a test request with an unusual path, to '.
+ 'This software sent itself a test request with an unusual path, to '.
'test if your webserver is rewriting paths correctly. The path was '.
'not transmitted correctly.'.
"\n\n".
- 'Phabricator sent a request to path "%s", and expected the webserver '.
- 'to decode and rewrite that path so that it received a request for '.
- '"%s". However, it received a request for "%s" instead.'.
+ 'This software sent a request to path "%s", and expected the '.
+ 'webserver to decode and rewrite that path so that it received a '.
+ 'request for "%s". However, it received a request for "%s" instead.'.
"\n\n".
'Verify that your rewrite rules are configured correctly, following '.
'the instructions in the documentation. If path encoding is not '.
'working properly you will be unable to access files with unusual '.
'names in repositories, among other issues.'.
"\n\n".
'(This problem can be caused by a missing "B" in your RewriteRule.)',
$send_path,
$expect_path,
$actual_path);
$this->newIssue('webserver.rewrites')
->setName(pht('HTTP Path Rewriting Incorrect'))
->setSummary(pht('Your webserver is rewriting paths improperly.'))
->setMessage($message);
}
$actual_key = pht('<none>');
$actual_value = pht('<none>');
foreach (idx($structure, 'params', array()) as $pair) {
if (idx($pair, 'name') == $expect_key) {
$actual_key = idx($pair, 'name');
$actual_value = idx($pair, 'value');
break;
}
}
if (($expect_key !== $actual_key) || ($expect_value !== $actual_value)) {
$message = pht(
- 'Phabricator sent itself a test request with an HTTP GET parameter, '.
+ 'This software sent itself a test request with an HTTP GET parameter, '.
'but the parameter was not transmitted. Sent "%s" with value "%s", '.
'got "%s" with value "%s".'.
"\n\n".
'Your webserver is configured incorrectly and large parts of '.
- 'Phabricator will not work until this issue is corrected.'.
+ 'this software will not work until this issue is corrected.'.
"\n\n".
'(This problem can be caused by a missing "QSA" in your RewriteRule.)',
$expect_key,
$expect_value,
$actual_key,
$actual_value);
$this->newIssue('webserver.parameters')
->setName(pht('HTTP Parameters Not Transmitting'))
->setSummary(
pht('Your webserver is not handling GET parameters properly.'))
->setMessage($message);
}
if ($gzip_future) {
$this->checkGzipResponse(
$gzip_future,
$gzip_uncompressed,
$gzip_compressed);
}
}
private function checkGzipResponse(
Future $future,
$uncompressed,
$compressed) {
try {
list($body, $headers) = $future->resolvex();
} catch (Exception $ex) {
return;
}
try {
$structure = phutil_json_decode(trim($body));
} catch (Exception $ex) {
return;
}
$raw_body = idx($structure, 'raw.base64');
$raw_body = @base64_decode($raw_body);
// The server received the exact compressed bytes we expected it to, so
// everything is working great.
if ($raw_body === $compressed) {
return;
}
// If the server received a prefix of the raw uncompressed string, it
// is almost certainly configured to decompress responses inline. Guide
// users to this problem narrowly.
// Otherwise, something is wrong but we don't have much of a clue what.
$message = array();
$message[] = pht(
- 'Phabricator sent itself a test request that was compressed with '.
+ 'This software sent itself a test request that was compressed with '.
'"Content-Encoding: gzip", but received different bytes than it '.
'sent.');
$prefix_len = min(strlen($raw_body), strlen($uncompressed));
if ($prefix_len > 16 && !strncmp($raw_body, $uncompressed, $prefix_len)) {
$message[] = pht(
'The request body that the server received had already been '.
'decompressed. This strongly suggests your webserver is configured '.
'to decompress requests inline, before they reach PHP.');
$message[] = pht(
'If you are using Apache, your server may be configured with '.
'"SetInputFilter DEFLATE". This directive destructively mangles '.
'requests and emits them with "Content-Length" and '.
'"Content-Encoding" headers that no longer match the data in the '.
'request body.');
} else {
$message[] = pht(
'This suggests your webserver is configured to decompress or mangle '.
'compressed requests.');
$message[] = pht(
- 'The request body Phabricator sent began:');
+ 'The request body that was sent began:');
$message[] = $this->snipBytes($compressed);
$message[] = pht(
- 'The request body Phabricator received began:');
+ 'The request body that was received began:');
$message[] = $this->snipBytes($raw_body);
}
$message[] = pht(
'Identify the component in your webserver configuration which is '.
- 'decompressing or mangling requests and disable it. Phabricator '.
+ 'decompressing or mangling requests and disable it. This software '.
'will not work properly until you do.');
$message = phutil_implode_html("\n\n", $message);
$this->newIssue('webserver.accept-gzip')
->setName(pht('Compressed Requests Not Received Properly'))
->setSummary(
pht(
'Your webserver is not handling compressed request bodies '.
'properly.'))
->setMessage($message);
}
private function snipBytes($raw) {
if (!strlen($raw)) {
$display = pht('<empty>');
} else {
$snip = substr($raw, 0, 24);
$display = phutil_loggable_string($snip);
if (strlen($snip) < strlen($raw)) {
$display .= '...';
}
}
return phutil_tag('tt', array(), $display);
}
}
diff --git a/src/applications/config/controller/PhabricatorConfigConsoleController.php b/src/applications/config/controller/PhabricatorConfigConsoleController.php
index 0528be347a..bd23d6dde5 100644
--- a/src/applications/config/controller/PhabricatorConfigConsoleController.php
+++ b/src/applications/config/controller/PhabricatorConfigConsoleController.php
@@ -1,336 +1,336 @@
<?php
final class PhabricatorConfigConsoleController
extends PhabricatorConfigController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$menu = id(new PHUIObjectItemListView())
->setViewer($viewer)
->setBig(true);
$menu->addItem(
id(new PHUIObjectItemView())
->setHeader(pht('Settings'))
->setHref($this->getApplicationURI('settings/'))
->setImageIcon('fa-wrench')
->setClickable(true)
->addAttribute(
pht(
'Review and modify configuration settings.')));
$menu->addItem(
id(new PHUIObjectItemView())
->setHeader(pht('Setup Issues'))
->setHref($this->getApplicationURI('issue/'))
->setImageIcon('fa-exclamation-triangle')
->setClickable(true)
->addAttribute(
pht(
'Show unresolved issues with setup and configuration.')));
$menu->addItem(
id(new PHUIObjectItemView())
->setHeader(pht('Services'))
->setHref($this->getApplicationURI('cluster/databases/'))
->setImageIcon('fa-server')
->setClickable(true)
->addAttribute(
pht(
'View status information for databases, caches, repositories, '.
'and other services.')));
$menu->addItem(
id(new PHUIObjectItemView())
->setHeader(pht('Extensions/Modules'))
->setHref($this->getApplicationURI('module/'))
->setImageIcon('fa-gear')
->setClickable(true)
->addAttribute(
pht(
'Show installed extensions and modules.')));
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Console'))
->setBorder(true);
$box = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Phabricator Configuration'))
+ ->setHeaderText(pht('Configuration'))
->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->setObjectList($menu);
$versions = $this->newLibraryVersionTable($viewer);
$binary_versions = $this->newBinaryVersionTable();
$launcher_view = id(new PHUILauncherView())
->appendChild($box)
->appendChild($versions)
->appendChild($binary_versions);
$view = id(new PHUITwoColumnView())
->setFooter($launcher_view);
return $this->newPage()
- ->setTitle(pht('Phabricator Configuration'))
+ ->setTitle(pht('Configuration'))
->setCrumbs($crumbs)
->appendChild($view);
}
public function newLibraryVersionTable() {
$viewer = $this->getViewer();
$versions = $this->loadVersions($viewer);
$rows = array();
foreach ($versions as $name => $info) {
$branchpoint = $info['branchpoint'];
if (strlen($branchpoint)) {
$branchpoint = substr($branchpoint, 0, 12);
} else {
$branchpoint = null;
}
$version = $info['hash'];
if (strlen($version)) {
$version = substr($version, 0, 12);
} else {
$version = pht('Unknown');
}
$epoch = $info['epoch'];
if ($epoch) {
$epoch = phabricator_date($epoch, $viewer);
} else {
$epoch = null;
}
$rows[] = array(
$name,
$version,
$epoch,
$branchpoint,
);
}
$table_view = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Library'),
pht('Version'),
pht('Date'),
pht('Branchpoint'),
))
->setColumnClasses(
array(
'pri',
null,
null,
'wide',
));
return id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Phabricator Version Information'))
+ ->setHeaderText(pht('Version Information'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($table_view);
}
private function loadVersions(PhabricatorUser $viewer) {
$specs = array(
'phabricator',
'arcanist',
);
$all_libraries = PhutilBootloader::getInstance()->getAllLibraries();
// This puts the core libraries at the top:
$other_libraries = array_diff($all_libraries, $specs);
$specs = array_merge($specs, $other_libraries);
$log_futures = array();
$remote_futures = array();
foreach ($specs as $lib) {
$root = dirname(phutil_get_library_root($lib));
$log_command = csprintf(
'git log --format=%s -n 1 --',
'%H %ct');
$remote_command = csprintf(
'git remote -v');
$log_futures[$lib] = id(new ExecFuture('%C', $log_command))
->setCWD($root);
$remote_futures[$lib] = id(new ExecFuture('%C', $remote_command))
->setCWD($root);
}
$all_futures = array_merge($log_futures, $remote_futures);
id(new FutureIterator($all_futures))
->resolveAll();
// A repository may have a bunch of remotes, but we're only going to look
// for remotes we host to try to figure out where this repository branched.
$upstream_pattern = '(github\.com/phacility/|secure\.phabricator\.com/)';
$upstream_futures = array();
$lib_upstreams = array();
foreach ($specs as $lib) {
$remote_future = $remote_futures[$lib];
list($err, $stdout) = $remote_future->resolve();
if ($err) {
// If this fails for whatever reason, just move on.
continue;
}
// These look like this, with a tab separating the first two fields:
// remote-name http://remote.uri/ (push)
$upstreams = array();
$remotes = phutil_split_lines($stdout, false);
foreach ($remotes as $remote) {
$remote_pattern = '/^([^\t]+)\t([^ ]+) \(([^)]+)\)\z/';
$matches = null;
if (!preg_match($remote_pattern, $remote, $matches)) {
continue;
}
// Remote URIs are either "push" or "fetch": we only care about "fetch"
// URIs.
$type = $matches[3];
if ($type != 'fetch') {
continue;
}
$uri = $matches[2];
$is_upstream = preg_match($upstream_pattern, $uri);
if (!$is_upstream) {
continue;
}
$name = $matches[1];
$upstreams[$name] = $name;
}
// If we have several suitable upstreams, try to pick the one named
// "origin", if it exists. Otherwise, just pick the first one.
if (isset($upstreams['origin'])) {
$upstream = $upstreams['origin'];
} else if ($upstreams) {
$upstream = head($upstreams);
} else {
$upstream = null;
}
if (!$upstream) {
continue;
}
$lib_upstreams[$lib] = $upstream;
$merge_base_command = csprintf(
'git merge-base HEAD %s/master --',
$upstream);
$root = dirname(phutil_get_library_root($lib));
$upstream_futures[$lib] = id(new ExecFuture('%C', $merge_base_command))
->setCWD($root);
}
if ($upstream_futures) {
id(new FutureIterator($upstream_futures))
->resolveAll();
}
$results = array();
foreach ($log_futures as $lib => $future) {
list($err, $stdout) = $future->resolve();
if (!$err) {
list($hash, $epoch) = explode(' ', $stdout);
} else {
$hash = null;
$epoch = null;
}
$result = array(
'hash' => $hash,
'epoch' => $epoch,
'upstream' => null,
'branchpoint' => null,
);
$upstream_future = idx($upstream_futures, $lib);
if ($upstream_future) {
list($err, $stdout) = $upstream_future->resolve();
if (!$err) {
$branchpoint = trim($stdout);
if (strlen($branchpoint)) {
// We only list a branchpoint if it differs from HEAD.
if ($branchpoint != $hash) {
$result['upstream'] = $lib_upstreams[$lib];
$result['branchpoint'] = trim($stdout);
}
}
}
}
$results[$lib] = $result;
}
return $results;
}
private function newBinaryVersionTable() {
$rows = array();
$rows[] = array(
'php',
phpversion(),
php_sapi_name(),
);
$binaries = PhutilBinaryAnalyzer::getAllBinaries();
foreach ($binaries as $binary) {
if (!$binary->isBinaryAvailable()) {
$binary_version = pht('Not Available');
$binary_path = null;
} else {
$binary_version = $binary->getBinaryVersion();
$binary_path = $binary->getBinaryPath();
}
$rows[] = array(
$binary->getBinaryName(),
$binary_version,
$binary_path,
);
}
$table_view = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Binary'),
pht('Version'),
pht('Path'),
))
->setColumnClasses(
array(
'pri',
null,
'wide',
));
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Other Version Information'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($table_view);
}
}
diff --git a/src/applications/config/controller/services/PhabricatorConfigClusterDatabasesController.php b/src/applications/config/controller/services/PhabricatorConfigClusterDatabasesController.php
index ca22698212..096495cc84 100644
--- a/src/applications/config/controller/services/PhabricatorConfigClusterDatabasesController.php
+++ b/src/applications/config/controller/services/PhabricatorConfigClusterDatabasesController.php
@@ -1,239 +1,239 @@
<?php
final class PhabricatorConfigClusterDatabasesController
extends PhabricatorConfigServicesController {
public function handleRequest(AphrontRequest $request) {
$nav = $this->newNavigation('database-servers');
$title = pht('Database Servers');
$doc_href = PhabricatorEnv::getDoclink('Cluster: Databases');
$button = id(new PHUIButtonView())
->setIcon('fa-book')
->setHref($doc_href)
->setTag('a')
->setText(pht('Documentation'));
$header = $this->buildHeaderView($title, $button);
$database_status = $this->buildClusterDatabaseStatus();
$status = $this->buildConfigBoxView(pht('Status'), $database_status);
$crumbs = $this->newCrumbs()
->addTextCrumb($title);
$content = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter($status);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->setNavigation($nav)
->appendChild($content);
}
private function buildClusterDatabaseStatus() {
$viewer = $this->getViewer();
$databases = PhabricatorDatabaseRef::queryAll();
$connection_map = PhabricatorDatabaseRef::getConnectionStatusMap();
$replica_map = PhabricatorDatabaseRef::getReplicaStatusMap();
Javelin::initBehavior('phabricator-tooltips');
$rows = array();
foreach ($databases as $database) {
$messages = array();
if ($database->getIsMaster()) {
$role_icon = id(new PHUIIconView())
->setIcon('fa-database sky')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Master'),
));
} else {
$role_icon = id(new PHUIIconView())
->setIcon('fa-download')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Replica'),
));
}
if ($database->getDisabled()) {
$conn_icon = 'fa-times';
$conn_color = 'grey';
$conn_label = pht('Disabled');
} else {
$status = $database->getConnectionStatus();
$info = idx($connection_map, $status, array());
$conn_icon = idx($info, 'icon');
$conn_color = idx($info, 'color');
$conn_label = idx($info, 'label');
if ($status === PhabricatorDatabaseRef::STATUS_OKAY) {
$latency = $database->getConnectionLatency();
$latency = (int)(1000000 * $latency);
$conn_label = pht('%s us', new PhutilNumber($latency));
}
}
$connection = array(
id(new PHUIIconView())->setIcon("{$conn_icon} {$conn_color}"),
' ',
$conn_label,
);
if ($database->getDisabled()) {
$replica_icon = 'fa-times';
$replica_color = 'grey';
$replica_label = pht('Disabled');
} else {
$status = $database->getReplicaStatus();
$info = idx($replica_map, $status, array());
$replica_icon = idx($info, 'icon');
$replica_color = idx($info, 'color');
$replica_label = idx($info, 'label');
if ($database->getIsMaster()) {
if ($status === PhabricatorDatabaseRef::REPLICATION_OKAY) {
$replica_icon = 'fa-database';
}
} else {
switch ($status) {
case PhabricatorDatabaseRef::REPLICATION_OKAY:
case PhabricatorDatabaseRef::REPLICATION_SLOW:
$delay = $database->getReplicaDelay();
if ($delay) {
$replica_label = pht('%ss Behind', new PhutilNumber($delay));
} else {
$replica_label = pht('Up to Date');
}
break;
}
}
}
$replication = array(
id(new PHUIIconView())->setIcon("{$replica_icon} {$replica_color}"),
' ',
$replica_label,
);
$health = $database->getHealthRecord();
$health_up = $health->getUpEventCount();
$health_down = $health->getDownEventCount();
if ($health->getIsHealthy()) {
$health_icon = id(new PHUIIconView())
->setIcon('fa-plus green');
} else {
$health_icon = id(new PHUIIconView())
->setIcon('fa-times red');
$messages[] = pht(
'UNHEALTHY: This database has failed recent health checks. Traffic '.
'will not be sent to it until it recovers.');
}
$health_count = pht(
'%s / %s',
new PhutilNumber($health_up),
new PhutilNumber($health_up + $health_down));
$health_status = array(
$health_icon,
' ',
$health_count,
);
$conn_message = $database->getConnectionMessage();
if ($conn_message) {
$messages[] = $conn_message;
}
$replica_message = $database->getReplicaMessage();
if ($replica_message) {
$messages[] = $replica_message;
}
$messages = phutil_implode_html(phutil_tag('br'), $messages);
$partition = null;
if ($database->getIsMaster()) {
if ($database->getIsDefaultPartition()) {
$partition = id(new PHUIIconView())
->setIcon('fa-circle sky')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Default Partition'),
));
} else {
$map = $database->getApplicationMap();
if ($map) {
$list = implode(', ', $map);
} else {
$list = pht('Empty');
}
$partition = id(new PHUIIconView())
->setIcon('fa-adjust sky')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Partition: %s', $list),
));
}
}
$rows[] = array(
$role_icon,
$partition,
$database->getHost(),
$database->getPort(),
$database->getUser(),
$connection,
$replication,
$health_status,
$messages,
);
}
$table = id(new AphrontTableView($rows))
->setNoDataString(
- pht('Phabricator is not configured in cluster mode.'))
+ pht('This server is not configured in cluster mode.'))
->setHeaders(
array(
null,
null,
pht('Host'),
pht('Port'),
pht('User'),
pht('Connection'),
pht('Replication'),
pht('Health'),
pht('Messages'),
))
->setColumnClasses(
array(
null,
null,
null,
null,
null,
null,
null,
null,
'wide',
));
return $table;
}
}
diff --git a/src/applications/config/controller/settings/PhabricatorConfigEditController.php b/src/applications/config/controller/settings/PhabricatorConfigEditController.php
index 2cb217db69..38c592efca 100644
--- a/src/applications/config/controller/settings/PhabricatorConfigEditController.php
+++ b/src/applications/config/controller/settings/PhabricatorConfigEditController.php
@@ -1,472 +1,472 @@
<?php
final class PhabricatorConfigEditController
extends PhabricatorConfigSettingsController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$key = $request->getURIData('key');
$options = PhabricatorApplicationConfigOptions::loadAllOptions();
if (empty($options[$key])) {
$ancient = PhabricatorExtraConfigSetupCheck::getAncientConfig();
if (isset($ancient[$key])) {
$desc = pht(
"This configuration has been removed. You can safely delete ".
"it.\n\n%s",
$ancient[$key]);
} else {
$desc = pht(
'This configuration option is unknown. It may be misspelled, '.
- 'or have existed in a previous version of Phabricator.');
+ 'or have existed in a previous version of the software.');
}
// This may be a dead config entry, which existed in the past but no
// longer exists. Allow it to be edited so it can be reviewed and
// deleted.
$option = id(new PhabricatorConfigOption())
->setKey($key)
->setType('wild')
->setDefault(null)
->setDescription($desc);
$group = null;
} else {
$option = $options[$key];
$group = $option->getGroup();
}
$issue = $request->getStr('issue');
if ($issue) {
// If the user came here from an open setup issue, send them back.
$done_uri = $this->getApplicationURI('issue/'.$issue.'/');
} else {
$done_uri = $this->getApplicationURI('settings/');
}
// Check if the config key is already stored in the database.
// Grab the value if it is.
$config_entry = id(new PhabricatorConfigEntry())
->loadOneWhere(
'configKey = %s AND namespace = %s',
$key,
'default');
if (!$config_entry) {
$config_entry = id(new PhabricatorConfigEntry())
->setConfigKey($key)
->setNamespace('default')
->setIsDeleted(true);
$config_entry->setPHID($config_entry->generatePHID());
}
$e_value = null;
$errors = array();
if ($request->isFormPost() && !$option->getLocked()) {
$result = $this->readRequest(
$option,
$request);
list($e_value, $value_errors, $display_value, $xaction) = $result;
$errors = array_merge($errors, $value_errors);
if (!$errors) {
$editor = id(new PhabricatorConfigEditor())
->setActor($viewer)
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request);
try {
$editor->applyTransactions($config_entry, array($xaction));
return id(new AphrontRedirectResponse())->setURI($done_uri);
} catch (PhabricatorConfigValidationException $ex) {
$e_value = pht('Invalid');
$errors[] = $ex->getMessage();
}
}
} else {
if ($config_entry->getIsDeleted()) {
$display_value = null;
} else {
$display_value = $this->getDisplayValue(
$option,
$config_entry,
$config_entry->getValue());
}
}
$form = id(new AphrontFormView())
->setEncType('multipart/form-data');
$error_view = null;
if ($errors) {
$error_view = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_ERROR)
->setErrors($errors);
}
$doc_href = PhabricatorEnv::getDoclink(
'Configuration Guide: Locked and Hidden Configuration');
$doc_link = phutil_tag(
'a',
array(
'href' => $doc_href,
'target' => '_blank',
),
pht('Learn more about locked and hidden options.'));
$status_items = array();
$tag = null;
if ($option->getHidden()) {
$tag = id(new PHUITagView())
->setName(pht('Hidden'))
->setColor(PHUITagView::COLOR_GREY)
->setBorder(PHUITagView::BORDER_NONE)
->setType(PHUITagView::TYPE_SHADE);
$message = pht(
'This configuration is hidden and can not be edited or viewed from '.
'the web interface.');
$status_items[] = id(new PHUIInfoView())
->appendChild(array($message, ' ', $doc_link));
} else if ($option->getLocked()) {
$tag = id(new PHUITagView())
->setName(pht('Locked'))
->setColor(PHUITagView::COLOR_RED)
->setBorder(PHUITagView::BORDER_NONE)
->setType(PHUITagView::TYPE_SHADE);
$message = $option->getLockedMessage();
$status_items[] = id(new PHUIInfoView())
->appendChild(array($message, ' ', $doc_link));
}
if ($option->getHidden() || $option->getLocked()) {
$controls = array();
} else {
$controls = $this->renderControls(
$option,
$display_value,
$e_value);
}
$form
->setUser($viewer)
->addHiddenInput('issue', $request->getStr('issue'));
$description = $option->newDescriptionRemarkupView($viewer);
if ($description) {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Description'))
->setValue($description));
}
if ($group) {
$extra = $group->renderContextualDescription(
$option,
$request);
if ($extra !== null) {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setValue($extra));
}
}
foreach ($controls as $control) {
$form->appendControl($control);
}
if (!$option->getLocked()) {
$form->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($done_uri)
->setValue(pht('Save Config Entry')));
}
$current_config = null;
if (!$option->getHidden()) {
$current_config = $this->renderDefaults($option, $config_entry);
$current_config = $this->buildConfigBoxView(
pht('Current Configuration'),
$current_config);
}
$examples = $this->renderExamples($option);
if ($examples) {
$examples = $this->buildConfigBoxView(
pht('Examples'),
$examples);
}
$title = $key;
$box_header = array();
$box_header[] = $key;
$crumbs = $this->newCrumbs()
->addTextCrumb($key, '/config/edit/'.$key);
$form_box = $this->buildConfigBoxView($box_header, $form, $tag);
$timeline = $this->buildTransactionTimeline(
$config_entry,
new PhabricatorConfigTransactionQuery());
$timeline->setShouldTerminate(true);
$header = $this->buildHeaderView($title);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(
array(
$error_view,
$form_box,
$status_items,
$examples,
$current_config,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
private function readRequest(
PhabricatorConfigOption $option,
AphrontRequest $request) {
$type = $option->newOptionType();
if ($type) {
$is_set = $type->isValuePresentInRequest($option, $request);
if ($is_set) {
$value = $type->readValueFromRequest($option, $request);
$errors = array();
try {
$canonical_value = $type->newValueFromRequestValue(
$option,
$value);
$type->validateStoredValue($option, $canonical_value);
$xaction = $type->newTransaction($option, $canonical_value);
} catch (PhabricatorConfigValidationException $ex) {
$errors[] = $ex->getMessage();
$xaction = null;
} catch (Exception $ex) {
// NOTE: Some older validators throw bare exceptions. Purely in good
// taste, it would be nice to convert these at some point.
$errors[] = $ex->getMessage();
$xaction = null;
}
return array(
$errors ? pht('Invalid') : null,
$errors,
$value,
$xaction,
);
} else {
$delete_xaction = id(new PhabricatorConfigTransaction())
->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT)
->setNewValue(
array(
'deleted' => true,
'value' => null,
));
return array(
null,
array(),
null,
$delete_xaction,
);
}
}
// TODO: If we missed on the new `PhabricatorConfigType` map, fall back
// to the old semi-modular, semi-hacky way of doing things.
$xaction = new PhabricatorConfigTransaction();
$xaction->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT);
$e_value = null;
$errors = array();
if ($option->isCustomType()) {
$info = $option->getCustomObject()->readRequest($option, $request);
list($e_value, $errors, $set_value, $value) = $info;
} else {
throw new Exception(
pht(
'Unknown configuration option type "%s".',
$option->getType()));
}
if (!$errors) {
$xaction->setNewValue(
array(
'deleted' => false,
'value' => $set_value,
));
} else {
$xaction = null;
}
return array($e_value, $errors, $value, $xaction);
}
private function getDisplayValue(
PhabricatorConfigOption $option,
PhabricatorConfigEntry $entry,
$value) {
$type = $option->newOptionType();
if ($type) {
return $type->newDisplayValue($option, $value);
}
if ($option->isCustomType()) {
return $option->getCustomObject()->getDisplayValue(
$option,
$entry,
$value);
}
throw new Exception(
pht(
'Unknown configuration option type "%s".',
$option->getType()));
}
private function renderControls(
PhabricatorConfigOption $option,
$display_value,
$e_value) {
$type = $option->newOptionType();
if ($type) {
return $type->newControls(
$option,
$display_value,
$e_value);
}
if ($option->isCustomType()) {
$controls = $option->getCustomObject()->renderControls(
$option,
$display_value,
$e_value);
} else {
throw new Exception(
pht(
'Unknown configuration option type "%s".',
$option->getType()));
}
return $controls;
}
private function renderExamples(PhabricatorConfigOption $option) {
$examples = $option->getExamples();
if (!$examples) {
return null;
}
$table = array();
$table[] = phutil_tag('tr', array('class' => 'column-labels'), array(
phutil_tag('th', array(), pht('Example')),
phutil_tag('th', array(), pht('Value')),
));
foreach ($examples as $example) {
list($value, $description) = $example;
if ($value === null) {
$value = phutil_tag('em', array(), pht('(empty)'));
} else {
if (is_array($value)) {
$value = implode("\n", $value);
}
}
$table[] = phutil_tag('tr', array('class' => 'column-labels'), array(
phutil_tag('th', array(), $description),
phutil_tag('td', array(), $value),
));
}
require_celerity_resource('config-options-css');
return phutil_tag(
'table',
array(
'class' => 'config-option-table',
'cellspacing' => '0',
'cellpadding' => '0',
),
$table);
}
private function renderDefaults(
PhabricatorConfigOption $option,
PhabricatorConfigEntry $entry) {
$stack = PhabricatorEnv::getConfigSourceStack();
$stack = $stack->getStack();
$table = array();
$table[] = phutil_tag('tr', array('class' => 'column-labels'), array(
phutil_tag('th', array(), pht('Source')),
phutil_tag('th', array(), pht('Value')),
));
$is_effective_value = true;
foreach ($stack as $key => $source) {
$row_classes = array(
'column-labels',
);
$value = $source->getKeys(
array(
$option->getKey(),
));
if (!array_key_exists($option->getKey(), $value)) {
$value = phutil_tag('em', array(), pht('(No Value Configured)'));
} else {
$value = $this->getDisplayValue(
$option,
$entry,
$value[$option->getKey()]);
if ($is_effective_value) {
$is_effective_value = false;
$row_classes[] = 'config-options-effective-value';
}
}
$table[] = phutil_tag(
'tr',
array(
'class' => implode(' ', $row_classes),
),
array(
phutil_tag('th', array(), $source->getName()),
phutil_tag('td', array(), $value),
));
}
require_celerity_resource('config-options-css');
return phutil_tag(
'table',
array(
'class' => 'config-option-table',
'cellspacing' => '0',
'cellpadding' => '0',
),
$table);
}
}
diff --git a/src/applications/config/custom/PhabricatorCustomLogoConfigType.php b/src/applications/config/custom/PhabricatorCustomLogoConfigType.php
index ff29050602..cc20768119 100644
--- a/src/applications/config/custom/PhabricatorCustomLogoConfigType.php
+++ b/src/applications/config/custom/PhabricatorCustomLogoConfigType.php
@@ -1,118 +1,118 @@
<?php
final class PhabricatorCustomLogoConfigType
extends PhabricatorConfigOptionType {
public static function getLogoImagePHID() {
$logo = PhabricatorEnv::getEnvConfig('ui.logo');
return idx($logo, 'logoImagePHID');
}
public static function getLogoWordmark() {
$logo = PhabricatorEnv::getEnvConfig('ui.logo');
return idx($logo, 'wordmarkText');
}
public function validateOption(PhabricatorConfigOption $option, $value) {
if (!is_array($value)) {
throw new Exception(
pht(
'Logo configuration is not valid: value must be a dictionary.'));
}
PhutilTypeSpec::checkMap(
$value,
array(
'logoImagePHID' => 'optional string|null',
'wordmarkText' => 'optional string|null',
));
}
public function readRequest(
PhabricatorConfigOption $option,
AphrontRequest $request) {
$viewer = $request->getViewer();
$view_policy = PhabricatorPolicies::POLICY_PUBLIC;
if ($request->getBool('removeLogo')) {
$logo_image_phid = null;
} else if ($request->getFileExists('logoImage')) {
$logo_image = PhabricatorFile::newFromPHPUpload(
idx($_FILES, 'logoImage'),
array(
'name' => 'logo',
'authorPHID' => $viewer->getPHID(),
'viewPolicy' => $view_policy,
'canCDN' => true,
'isExplicitUpload' => true,
));
$logo_image_phid = $logo_image->getPHID();
} else {
$logo_image_phid = self::getLogoImagePHID();
}
$wordmark_text = $request->getStr('wordmarkText');
$value = array(
'logoImagePHID' => $logo_image_phid,
'wordmarkText' => $wordmark_text,
);
$errors = array();
$e_value = null;
try {
$this->validateOption($option, $value);
} catch (Exception $ex) {
$e_value = pht('Invalid');
$errors[] = $ex->getMessage();
$value = array();
}
return array($e_value, $errors, $value, phutil_json_encode($value));
}
public function renderControls(
PhabricatorConfigOption $option,
$display_value,
$e_value) {
try {
$value = phutil_json_decode($display_value);
} catch (Exception $ex) {
$value = array();
}
$logo_image_phid = idx($value, 'logoImagePHID');
$wordmark_text = idx($value, 'wordmarkText');
$controls = array();
// TODO: This should be a PHUIFormFileControl, but that currently only
// works in "workflow" forms. It isn't trivial to convert this form into
// a workflow form, nor is it trivial to make the newer control work
// in non-workflow forms.
$controls[] = id(new AphrontFormFileControl())
->setName('logoImage')
->setLabel(pht('Logo Image'));
if ($logo_image_phid) {
$controls[] = id(new AphrontFormCheckboxControl())
->addCheckbox(
'removeLogo',
1,
pht('Remove Custom Logo'));
}
$controls[] = id(new AphrontFormTextControl())
->setName('wordmarkText')
->setLabel(pht('Wordmark'))
- ->setPlaceholder(pht('Phabricator'))
+ ->setPlaceholder(PlatformSymbols::getPlatformServerName())
->setValue($wordmark_text);
return $controls;
}
}
diff --git a/src/applications/config/editor/PhabricatorConfigEditor.php b/src/applications/config/editor/PhabricatorConfigEditor.php
index deccf1ef5c..7a8833c84e 100644
--- a/src/applications/config/editor/PhabricatorConfigEditor.php
+++ b/src/applications/config/editor/PhabricatorConfigEditor.php
@@ -1,165 +1,165 @@
<?php
final class PhabricatorConfigEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorConfigApplication';
}
public function getEditorObjectsDescription() {
- return pht('Phabricator Configuration');
+ return pht('Configuration');
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorConfigTransaction::TYPE_EDIT;
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorConfigTransaction::TYPE_EDIT:
return array(
'deleted' => (int)$object->getIsDeleted(),
'value' => $object->getValue(),
);
}
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorConfigTransaction::TYPE_EDIT:
return $xaction->getNewValue();
}
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorConfigTransaction::TYPE_EDIT:
$v = $xaction->getNewValue();
// If this is a defined configuration option (vs a straggler from an
// old version of Phabricator or a configuration file misspelling)
// submit it to the validation gauntlet.
$key = $object->getConfigKey();
$all_options = PhabricatorApplicationConfigOptions::loadAllOptions();
$option = idx($all_options, $key);
if ($option) {
$option->getGroup()->validateOption(
$option,
$v['value']);
}
$object->setIsDeleted((int)$v['deleted']);
$object->setValue($v['value']);
break;
}
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
return;
}
protected function mergeTransactions(
PhabricatorApplicationTransaction $u,
PhabricatorApplicationTransaction $v) {
$type = $u->getTransactionType();
switch ($type) {
case PhabricatorConfigTransaction::TYPE_EDIT:
return $v;
}
return parent::mergeTransactions($u, $v);
}
protected function transactionHasEffect(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$type = $xaction->getTransactionType();
switch ($type) {
case PhabricatorConfigTransaction::TYPE_EDIT:
// If an edit deletes an already-deleted entry, no-op it.
if (idx($old, 'deleted') && idx($new, 'deleted')) {
return false;
}
break;
}
return parent::transactionHasEffect($object, $xaction);
}
protected function didApplyTransactions($object, array $xactions) {
// Force all the setup checks to run on the next page load.
PhabricatorSetupCheck::deleteSetupCheckCache();
return $xactions;
}
public static function storeNewValue(
PhabricatorUser $user,
PhabricatorConfigEntry $config_entry,
$value,
PhabricatorContentSource $source,
$acting_as_phid = null) {
$xaction = id(new PhabricatorConfigTransaction())
->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT)
->setNewValue(
array(
'deleted' => false,
'value' => $value,
));
$editor = id(new PhabricatorConfigEditor())
->setActor($user)
->setContinueOnNoEffect(true)
->setContentSource($source);
if ($acting_as_phid) {
$editor->setActingAsPHID($acting_as_phid);
}
$editor->applyTransactions($config_entry, array($xaction));
}
public static function deleteConfig(
PhabricatorUser $user,
PhabricatorConfigEntry $config_entry,
PhabricatorContentSource $source) {
$xaction = id(new PhabricatorConfigTransaction())
->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT)
->setNewValue(
array(
'deleted' => true,
'value' => null,
));
$editor = id(new PhabricatorConfigEditor())
->setActor($user)
->setContinueOnNoEffect(true)
->setContentSource($source);
$editor->applyTransactions($config_entry, array($xaction));
}
}
diff --git a/src/applications/config/issue/PhabricatorSetupIssue.php b/src/applications/config/issue/PhabricatorSetupIssue.php
index a2e9532ede..cadedfc7da 100644
--- a/src/applications/config/issue/PhabricatorSetupIssue.php
+++ b/src/applications/config/issue/PhabricatorSetupIssue.php
@@ -1,230 +1,231 @@
<?php
final class PhabricatorSetupIssue extends Phobject {
private $issueKey;
private $name;
private $message;
private $isFatal;
private $summary;
private $shortName;
private $group;
private $databaseRef;
private $isIgnored = false;
private $phpExtensions = array();
private $phabricatorConfig = array();
private $relatedPhabricatorConfig = array();
private $phpConfig = array();
private $commands = array();
private $mysqlConfig = array();
private $originalPHPConfigValues = array();
private $links;
public static function newDatabaseConnectionIssue(
Exception $ex,
$is_fatal) {
$message = pht(
"Unable to connect to MySQL!\n\n".
"%s\n\n".
- "Make sure Phabricator and MySQL are correctly configured.",
+ "Make sure databases connection information and MySQL are ".
+ "correctly configured.",
$ex->getMessage());
$issue = id(new self())
->setIssueKey('mysql.connect')
->setName(pht('Can Not Connect to MySQL'))
->setMessage($message)
->setIsFatal($is_fatal)
->addRelatedPhabricatorConfig('mysql.host')
->addRelatedPhabricatorConfig('mysql.port')
->addRelatedPhabricatorConfig('mysql.user')
->addRelatedPhabricatorConfig('mysql.pass');
if (PhabricatorEnv::getEnvConfig('cluster.databases')) {
$issue->addRelatedPhabricatorConfig('cluster.databases');
}
return $issue;
}
public function addCommand($command) {
$this->commands[] = $command;
return $this;
}
public function getCommands() {
return $this->commands;
}
public function setShortName($short_name) {
$this->shortName = $short_name;
return $this;
}
public function getShortName() {
if ($this->shortName === null) {
return $this->getName();
}
return $this->shortName;
}
public function setDatabaseRef(PhabricatorDatabaseRef $database_ref) {
$this->databaseRef = $database_ref;
return $this;
}
public function getDatabaseRef() {
return $this->databaseRef;
}
public function setGroup($group) {
$this->group = $group;
return $this;
}
public function getGroup() {
if ($this->group) {
return $this->group;
} else {
return PhabricatorSetupCheck::GROUP_OTHER;
}
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setSummary($summary) {
$this->summary = $summary;
return $this;
}
public function getSummary() {
if ($this->summary === null) {
return $this->getMessage();
}
return $this->summary;
}
public function setIssueKey($issue_key) {
$this->issueKey = $issue_key;
return $this;
}
public function getIssueKey() {
return $this->issueKey;
}
public function setIsFatal($is_fatal) {
$this->isFatal = $is_fatal;
return $this;
}
public function getIsFatal() {
return $this->isFatal;
}
public function addPHPConfig($php_config) {
$this->phpConfig[] = $php_config;
return $this;
}
/**
* Set an explicit value to display when showing the user PHP configuration
* values.
*
* If Phabricator has changed a value by the time a config issue is raised,
* you can provide the original value here so the UI makes sense. For example,
* we alter `memory_limit` during startup, so if the original value is not
* provided it will look like it is always set to `-1`.
*
* @param string PHP configuration option to provide a value for.
* @param string Explicit value to show in the UI.
* @return this
*/
public function addPHPConfigOriginalValue($php_config, $value) {
$this->originalPHPConfigValues[$php_config] = $value;
return $this;
}
public function getPHPConfigOriginalValue($php_config, $default = null) {
return idx($this->originalPHPConfigValues, $php_config, $default);
}
public function getPHPConfig() {
return $this->phpConfig;
}
public function addMySQLConfig($mysql_config) {
$this->mysqlConfig[] = $mysql_config;
return $this;
}
public function getMySQLConfig() {
return $this->mysqlConfig;
}
public function addPhabricatorConfig($phabricator_config) {
$this->phabricatorConfig[] = $phabricator_config;
return $this;
}
public function getPhabricatorConfig() {
return $this->phabricatorConfig;
}
public function addRelatedPhabricatorConfig($phabricator_config) {
$this->relatedPhabricatorConfig[] = $phabricator_config;
return $this;
}
public function getRelatedPhabricatorConfig() {
return $this->relatedPhabricatorConfig;
}
public function addPHPExtension($php_extension) {
$this->phpExtensions[] = $php_extension;
return $this;
}
public function getPHPExtensions() {
return $this->phpExtensions;
}
public function setMessage($message) {
$this->message = $message;
return $this;
}
public function getMessage() {
return $this->message;
}
public function setIsIgnored($is_ignored) {
$this->isIgnored = $is_ignored;
return $this;
}
public function getIsIgnored() {
return $this->isIgnored;
}
public function addLink($href, $name) {
$this->links[] = array(
'href' => $href,
'name' => $name,
);
return $this;
}
public function getLinks() {
return $this->links;
}
}
diff --git a/src/applications/config/option/PhabricatorAWSConfigOptions.php b/src/applications/config/option/PhabricatorAWSConfigOptions.php
index 5f2246fdaa..a7a3a1e2ea 100644
--- a/src/applications/config/option/PhabricatorAWSConfigOptions.php
+++ b/src/applications/config/option/PhabricatorAWSConfigOptions.php
@@ -1,60 +1,60 @@
<?php
final class PhabricatorAWSConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Amazon Web Services');
}
public function getDescription() {
return pht('Configure integration with AWS (EC2, SES, S3, etc).');
}
public function getIcon() {
return 'fa-server';
}
public function getGroup() {
return 'core';
}
public function getOptions() {
return array(
$this->newOption('amazon-s3.access-key', 'string', null)
->setLocked(true)
->setDescription(pht('Access key for Amazon S3.')),
$this->newOption('amazon-s3.secret-key', 'string', null)
->setHidden(true)
->setDescription(pht('Secret key for Amazon S3.')),
$this->newOption('amazon-s3.region', 'string', null)
->setLocked(true)
->setDescription(
pht(
'Amazon S3 region where your S3 bucket is located. When you '.
'specify a region, you should also specify a corresponding '.
'endpoint with `amazon-s3.endpoint`. You can find a list of '.
'available regions and endpoints in the AWS documentation.'))
->addExample('us-west-1', pht('USWest Region')),
$this->newOption('amazon-s3.endpoint', 'string', null)
->setLocked(true)
->setDescription(
pht(
'Explicit S3 endpoint to use. This should be the endpoint '.
'which corresponds to the region you have selected in '.
- '`amazon-s3.region`. Phabricator can not determine the correct '.
+ '`amazon-s3.region`. This software can not determine the correct '.
'endpoint automatically because some endpoint locations are '.
'irregular.'))
->addExample(
's3-us-west-1.amazonaws.com',
pht('Use specific endpoint')),
$this->newOption('amazon-ec2.access-key', 'string', null)
->setLocked(true)
->setDescription(pht('Access key for Amazon EC2.')),
$this->newOption('amazon-ec2.secret-key', 'string', null)
->setHidden(true)
->setDescription(pht('Secret key for Amazon EC2.')),
);
}
}
diff --git a/src/applications/config/option/PhabricatorAccessLogConfigOptions.php b/src/applications/config/option/PhabricatorAccessLogConfigOptions.php
index 45f730b977..0e3d336fc5 100644
--- a/src/applications/config/option/PhabricatorAccessLogConfigOptions.php
+++ b/src/applications/config/option/PhabricatorAccessLogConfigOptions.php
@@ -1,154 +1,154 @@
<?php
final class PhabricatorAccessLogConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Access Logs');
}
public function getDescription() {
return pht('Configure the access logs, which log HTTP/SSH requests.');
}
public function getIcon() {
return 'fa-list';
}
public function getGroup() {
return 'core';
}
public function getOptions() {
$common_map = array(
'C' => pht('The controller or workflow which handled the request.'),
'c' => pht('The HTTP response code or process exit code.'),
'D' => pht('The request date.'),
'e' => pht('Epoch timestamp.'),
'h' => pht("The webserver's host name."),
'p' => pht('The PID of the server process.'),
'r' => pht('The remote IP.'),
'T' => pht('The request duration, in microseconds.'),
'U' => pht('The request path, or request target.'),
'm' => pht('For conduit, the Conduit method which was invoked.'),
'u' => pht('The logged-in username, if one is logged in.'),
'P' => pht('The logged-in user PHID, if one is logged in.'),
'i' => pht('Request input, in bytes.'),
'o' => pht('Request output, in bytes.'),
'I' => pht('Cluster instance name, if configured.'),
);
$http_map = $common_map + array(
'R' => pht('The HTTP referrer.'),
'M' => pht('The HTTP method.'),
);
$ssh_map = $common_map + array(
's' => pht('The system user.'),
'S' => pht('The system sudo user.'),
'k' => pht('ID of the SSH key used to authenticate the request.'),
// TODO: This is a reasonable thing to support in the HTTP access
// log, too.
'Q' => pht('A random, unique string which identifies the request.'),
);
$http_desc = pht(
'Format for the HTTP access log. Use `%s` to set the path. '.
'Available variables are:',
'log.access.path');
$http_desc .= "\n\n";
$http_desc .= $this->renderMapHelp($http_map);
$ssh_desc = pht(
'Format for the SSH access log. Use %s to set the path. '.
'Available variables are:',
'log.ssh.path');
$ssh_desc .= "\n\n";
$ssh_desc .= $this->renderMapHelp($ssh_map);
return array(
$this->newOption('log.access.path', 'string', null)
->setLocked(true)
->setSummary(pht('Access log location.'))
->setDescription(
pht(
- "To enable the Phabricator access log, specify a path. The ".
- "Phabricator access than normal HTTP access logs (for instance, ".
+ "To enable the HTTP access log, specify a path. This log is ".
+ "more detailed than normal HTTP access logs (for instance, ".
"it can show logged-in users, controllers, and other application ".
"data).\n\n".
"If not set, no log will be written."))
->addExample(
null,
pht('Disable access log.'))
->addExample(
- '/var/log/phabricator/access.log',
+ '/var/log/devtools/access.log',
pht('Write access log here.')),
$this->newOption(
'log.access.format',
// NOTE: This is 'wild' intead of 'string' so "\t" and such can be
// specified.
'wild',
"[%D]\t%p\t%h\t%r\t%u\t%C\t%m\t%U\t%R\t%c\t%T")
->setLocked(true)
->setSummary(pht('Access log format.'))
->setDescription($http_desc),
$this->newOption('log.ssh.path', 'string', null)
->setLocked(true)
->setSummary(pht('SSH log location.'))
->setDescription(
pht(
- "To enable the Phabricator SSH log, specify a path. The ".
- "access log can provide more detailed information about SSH ".
- "access than a normal SSH log (for instance, it can show ".
- "logged-in users, commands, and other application data).\n\n".
+ "To enable the SSH log, specify a path. This log can provide ".
+ "more detailed information about SSH access than a normal SSH ".
+ "log (for instance, it can show logged-in users, commands, and ".
+ "other application data).\n\n".
"If not set, no log will be written."))
->addExample(
null,
pht('Disable SSH log.'))
->addExample(
- '/var/log/phabricator/ssh.log',
+ '/var/log/devtools/ssh.log',
pht('Write SSH log here.')),
$this->newOption(
'log.ssh.format',
'wild',
"[%D]\t%p\t%h\t%r\t%s\t%S\t%u\t%C\t%U\t%c\t%T\t%i\t%o")
->setLocked(true)
->setSummary(pht('SSH log format.'))
->setDescription($ssh_desc),
$this->newOption('log.ssh-error.path', 'string', null)
->setLocked(true)
->setSummary(pht('SSH error log location.'))
->setDescription(
pht(
- 'To enable the Phabricator SSH error log, specify a path. Errors '.
- 'occurring in contexts where Phabricator is serving SSH requests '.
+ 'To enable the SSH error log, specify a path. Errors occurring '.
+ 'in contexts where this software is serving SSH requests '.
'will be written to this log.'.
"\n\n".
'If not set, no log will be written.'))
->addExample(null, pht('Disable SSH error log.'))
->addExample(
- '/var/log/phabricator/ssh-error.log',
+ '/var/log/devtools/ssh-error.log',
pht('Write SSH error log here.')),
);
}
private function renderMapHelp(array $map) {
$desc = '';
foreach ($map as $key => $kdesc) {
$desc .= " - `%".$key."` ".$kdesc."\n";
}
$desc .= "\n";
$desc .= pht(
"If a variable isn't available (for example, %%m appears in the file ".
"format but the request is not a Conduit request), it will be rendered ".
"as '-'");
$desc .= "\n\n";
$desc .= pht(
"Note that the default format is subject to change in the future, so ".
"if you rely on the log's format, specify it explicitly.");
return $desc;
}
}
diff --git a/src/applications/config/option/PhabricatorAuthenticationConfigOptions.php b/src/applications/config/option/PhabricatorAuthenticationConfigOptions.php
index 8058ecafcb..976e27b324 100644
--- a/src/applications/config/option/PhabricatorAuthenticationConfigOptions.php
+++ b/src/applications/config/option/PhabricatorAuthenticationConfigOptions.php
@@ -1,123 +1,123 @@
<?php
final class PhabricatorAuthenticationConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Authentication');
}
public function getDescription() {
return pht('Options relating to authentication.');
}
public function getIcon() {
return 'fa-key';
}
public function getGroup() {
return 'core';
}
public function getOptions() {
return array(
$this->newOption('auth.require-email-verification', 'bool', false)
->setBoolOptions(
array(
pht('Require email verification'),
pht("Don't require email verification"),
))
->setSummary(
pht('Require email verification before a user can log in.'))
->setDescription(
pht(
'If true, email addresses must be verified (by clicking a link '.
'in an email) before a user can login. By default, verification '.
'is optional unless @{config:auth.email-domains} is nonempty.')),
$this->newOption('auth.require-approval', 'bool', true)
->setBoolOptions(
array(
pht('Require Administrators to Approve Accounts'),
pht("Don't Require Manual Approval"),
))
->setSummary(
pht('Require administrators to approve new accounts.'))
->setDescription(
pht(
- "Newly registered Phabricator accounts can either be placed ".
+ "Newly registered accounts can either be placed ".
"into a manual approval queue for administrative review, or ".
"automatically activated immediately. The approval queue is ".
"enabled by default because it gives you greater control over ".
- "who can register an account and access Phabricator.\n\n".
+ "who can register an account and access the server.\n\n".
"If your install is completely public, or on a VPN, or users can ".
"only register with a trusted provider like LDAP, or you've ".
- "otherwise configured Phabricator to prevent unauthorized ".
+ "otherwise configured the server to prevent unauthorized ".
"registration, you can disable the queue to reduce administrative ".
"overhead.\n\n".
"NOTE: Before you disable the queue, make sure ".
"@{config:auth.email-domains} is configured correctly ".
"for your install!")),
$this->newOption('auth.email-domains', 'list<string>', array())
->setSummary(pht('Only allow registration from particular domains.'))
->setDescription(
pht(
"You can restrict allowed email addresses to certain domains ".
"(like `yourcompany.com`) by setting a list of allowed domains ".
"here.\n\nUsers will only be allowed to register using email ".
"addresses at one of the domains, and will only be able to add ".
"new email addresses for these domains. If you configure this, ".
"it implies @{config:auth.require-email-verification}.\n\n".
"You should omit the `@` from domains. Note that the domain must ".
"match exactly. If you allow `yourcompany.com`, that permits ".
"`joe@yourcompany.com` but rejects `joe@mail.yourcompany.com`."))
->addExample(
"yourcompany.com\nmail.yourcompany.com",
pht('Valid Setting')),
$this->newOption('auth.lock-config', 'bool', false)
->setBoolOptions(
array(
pht('Auth provider config must be unlocked before editing'),
pht('Auth provider config can be edited without unlocking'),
))
->setSummary(
pht(
'Require administrators to unlock the authentication provider '.
'configuration from the CLI before it can be edited.'))
->setDescription(
pht(
'When set to `true`, the authentication provider configuration '.
'for this instance can not be modified without first running '.
'`bin/auth unlock` from the command line. This is to reduce '.
'the security impact of a compromised administrator account. '.
"\n\n".
'After running `bin/auth unlock` and making your changes to the '.
'authentication provider config, you should run `bin/auth lock`.'))
->setLocked(true),
$this->newOption('account.editable', 'bool', true)
->setBoolOptions(
array(
pht('Allow editing'),
pht('Prevent editing'),
))
->setSummary(
pht(
'Determines whether or not basic account information is editable.'))
->setDescription(
pht(
'This option controls whether users can edit account email '.
'addresses and profile real names.'.
"\n\n".
- 'If you set up Phabricator to automatically synchronize account '.
+ 'If you set things up to automatically synchronize account '.
'information from some other authoritative system, you can '.
'prevent users from making these edits to ensure information '.
'remains consistent across both systems.')),
$this->newOption('account.minimum-password-length', 'int', 8)
->setSummary(pht('Minimum password length.'))
->setDescription(
pht(
'When users set or reset a password, it must have at least this '.
'many characters.')),
);
}
}
diff --git a/src/applications/config/option/PhabricatorClusterConfigOptions.php b/src/applications/config/option/PhabricatorClusterConfigOptions.php
index ca4153a8b2..3cbdf6706f 100644
--- a/src/applications/config/option/PhabricatorClusterConfigOptions.php
+++ b/src/applications/config/option/PhabricatorClusterConfigOptions.php
@@ -1,146 +1,146 @@
<?php
final class PhabricatorClusterConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Cluster Setup');
}
public function getDescription() {
- return pht('Configure Phabricator to run on a cluster of hosts.');
+ return pht('Configure services to run on a cluster of hosts.');
}
public function getIcon() {
return 'fa-sitemap';
}
public function getGroup() {
return 'core';
}
public function getOptions() {
$databases_type = 'cluster.databases';
$databases_help = $this->deformat(pht(<<<EOTEXT
WARNING: This is a prototype option and the description below is currently pure
fantasy.
-This option allows you to make Phabricator aware of database read replicas so
+This option allows you to make this service aware of database read replicas so
it can monitor database health, spread load, and degrade gracefully to
read-only mode in the event of a failure on the primary host. For help with
configuring cluster databases, see **[[ %s | %s ]]** in the documentation.
EOTEXT
,
PhabricatorEnv::getDoclink('Cluster: Databases'),
pht('Cluster: Databases')));
$intro_href = PhabricatorEnv::getDoclink('Clustering Introduction');
$intro_name = pht('Clustering Introduction');
$search_type = 'cluster.search';
$search_help = $this->deformat(pht(<<<EOTEXT
Define one or more fulltext storage services. Here you can configure which
hosts will handle fulltext search queries and indexing. For help with
configuring fulltext search clusters, see **[[ %s | %s ]]** in the
documentation.
EOTEXT
,
PhabricatorEnv::getDoclink('Cluster: Search'),
pht('Cluster: Search')));
return array(
$this->newOption('cluster.addresses', 'list<string>', array())
->setLocked(true)
->setSummary(pht('Address ranges of cluster hosts.'))
->setDescription(
pht(
- 'Define a Phabricator cluster by providing a whitelist of host '.
+ 'Define a cluster by providing a whitelist of host '.
'addresses that are part of the cluster.'.
"\n\n".
'Hosts on this whitelist have special powers. These hosts are '.
'permitted to bend security rules, and misconfiguring this list '.
'can make your install less secure. For more information, '.
'see **[[ %s | %s ]]**.'.
"\n\n".
'Define a list of CIDR blocks which whitelist all hosts in the '.
'cluster and no additional hosts. See the examples below for '.
'details.'.
"\n\n".
- 'When cluster addresses are defined, Phabricator hosts will also '.
+ 'When cluster addresses are defined, hosts will also '.
'reject requests to interfaces which are not whitelisted.',
$intro_href,
$intro_name))
->addExample(
array(
'23.24.25.80/32',
'23.24.25.81/32',
),
pht('Whitelist Specific Addresses'))
->addExample(
array(
'1.2.3.0/24',
),
pht('Whitelist 1.2.3.*'))
->addExample(
array(
'1.2.0.0/16',
),
pht('Whitelist 1.2.*.*'))
->addExample(
array(
'0.0.0.0/0',
),
pht('Allow Any Host (Insecure!)')),
$this->newOption('cluster.instance', 'string', null)
->setLocked(true)
->setSummary(pht('Instance identifier for multi-tenant clusters.'))
->setDescription(
pht(
'WARNING: This is a very advanced option, and only useful for '.
'hosting providers running multi-tenant clusters.'.
"\n\n".
'If you provide an instance identifier here (normally by '.
- 'injecting it with a `%s`), Phabricator will pass it to '.
+ 'injecting it with a `%s`), the server will pass it to '.
'subprocesses and commit hooks in the `%s` environmental variable.',
'PhabricatorConfigSiteSource',
'PHABRICATOR_INSTANCE')),
$this->newOption('cluster.read-only', 'bool', false)
->setLocked(true)
->setSummary(
pht(
'Activate read-only mode for maintenance or disaster recovery.'))
->setDescription(
pht(
'WARNING: This is a prototype option and the description below '.
'is currently pure fantasy.'.
"\n\n".
- 'Switch Phabricator to read-only mode. In this mode, users will '.
+ 'Switch the service to read-only mode. In this mode, users will '.
'be unable to write new data. Normally, the cluster degrades '.
'into this mode automatically when it detects that the database '.
'master is unreachable, but you can activate it manually in '.
'order to perform maintenance or test configuration.')),
$this->newOption('cluster.databases', $databases_type, array())
->setHidden(true)
->setSummary(
pht('Configure database read replicas.'))
->setDescription($databases_help),
$this->newOption('cluster.search', $search_type, array())
->setLocked(true)
->setSummary(
pht('Configure full-text search services.'))
->setDescription($search_help)
->setDefault(
array(
array(
'type' => 'mysql',
'roles' => array(
'read' => true,
'write' => true,
),
),
)),
);
}
}
diff --git a/src/applications/config/option/PhabricatorCoreConfigOptions.php b/src/applications/config/option/PhabricatorCoreConfigOptions.php
index 77c48115a4..b09c12e269 100644
--- a/src/applications/config/option/PhabricatorCoreConfigOptions.php
+++ b/src/applications/config/option/PhabricatorCoreConfigOptions.php
@@ -1,335 +1,335 @@
<?php
final class PhabricatorCoreConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Core');
}
public function getDescription() {
return pht('Configure core options, including URIs.');
}
public function getIcon() {
return 'fa-bullseye';
}
public function getGroup() {
return 'core';
}
public function getOptions() {
if (phutil_is_windows()) {
$paths = array();
} else {
$paths = array(
'/bin',
'/usr/bin',
'/usr/local/bin',
);
}
$path = getenv('PATH');
$proto_doc_href = PhabricatorEnv::getDoclink(
'User Guide: Prototype Applications');
$proto_doc_name = pht('User Guide: Prototype Applications');
$applications_app_href = '/applications/';
$silent_description = $this->deformat(pht(<<<EOREMARKUP
-This option allows you to stop Phabricator from sending data to most external
+This option allows you to stop this service from sending data to most external
services: it will disable email, SMS, repository mirroring, remote builds,
Doorkeeper writes, and webhooks.
-This option is intended to allow a Phabricator instance to be exported, copied,
-imported, and run in a test environment without impacting users. For example,
-if you are migrating to new hardware, you could perform a test migration first
-with this flag set, make sure things work, and then do a production cutover
-later with higher confidence and less disruption.
+This option is intended to allow an instance to be exported, copied, imported,
+and run in a test environment without impacting users. For example, if you are
+migrating to new hardware, you could perform a test migration first with this
+flag set, make sure things work, and then do a production cutover later with
+higher confidence and less disruption.
Without making use of this flag to silence the temporary test environment,
users would receive duplicate email during the time the test instance and old
production instance were both in operation.
EOREMARKUP
));
$timezone_description = $this->deformat(pht(<<<EOREMARKUP
PHP date functions will emit a warning if they are called when no default
server timezone is configured.
Usually, you configure a default timezone in `php.ini` by setting the
configuration value `date.timezone`.
If you prefer, you can configure a default timezone here instead. To configure
a default timezone, select a timezone from the
[[ %s | PHP List of Supported Timezones ]].
EOREMARKUP
,
'https://php.net/manual/timezones.php'));
return array(
$this->newOption('phabricator.base-uri', 'string', null)
->setLocked(true)
- ->setSummary(pht('URI where Phabricator is installed.'))
+ ->setSummary(pht('URI where this software is installed.'))
->setDescription(
pht(
- 'Set the URI where Phabricator is installed. Setting this '.
+ 'Set the URI where this software is installed. Setting this '.
'improves security by preventing cookies from being set on other '.
'domains, and allows daemons to send emails with links that have '.
'the correct domain.'))
- ->addExample('http://phabricator.example.com/', pht('Valid Setting')),
+ ->addExample('http://devtools.example.com/', pht('Valid Setting')),
$this->newOption('phabricator.production-uri', 'string', null)
->setSummary(
pht('Primary install URI, for multi-environment installs.'))
->setDescription(
pht(
- 'If you have multiple Phabricator environments (like a '.
- 'development/staging environment for working on testing '.
- 'Phabricator, and a production environment for deploying it), '.
+ 'If you have multiple %s environments (like a '.
+ 'development/staging environment and a production environment), '.
'set the production environment URI here so that emails and other '.
'durable URIs will always generate with links pointing at the '.
'production environment. If unset, defaults to `%s`. Most '.
'installs do not need to set this option.',
+ PlatformSymbols::getPlatformServerName(),
'phabricator.base-uri'))
- ->addExample('http://phabricator.example.com/', pht('Valid Setting')),
+ ->addExample('http://devtools.example.com/', pht('Valid Setting')),
$this->newOption('phabricator.allowed-uris', 'list<string>', array())
->setLocked(true)
- ->setSummary(pht('Alternative URIs that can access Phabricator.'))
+ ->setSummary(pht('Alternative URIs that can access this service.'))
->setDescription(
pht(
"These alternative URIs will be able to access 'normal' pages ".
- "on your Phabricator install. Other features such as OAuth ".
+ "on your this install. Other features such as OAuth ".
"won't work. The major use case for this is moving installs ".
"across domains."))
->addExample(
"http://phabricator2.example.com/\n".
"http://phabricator3.example.com/",
pht('Valid Setting')),
$this->newOption('phabricator.timezone', 'string', null)
->setSummary(
- pht('The timezone Phabricator should use.'))
+ pht('The timezone this software should use by default.'))
->setDescription($timezone_description)
->addExample('America/New_York', pht('US East (EDT)'))
->addExample('America/Chicago', pht('US Central (CDT)'))
->addExample('America/Boise', pht('US Mountain (MDT)'))
->addExample('America/Los_Angeles', pht('US West (PDT)')),
$this->newOption('phabricator.cookie-prefix', 'string', null)
->setLocked(true)
->setSummary(
pht(
- 'Set a string Phabricator should use to prefix cookie names.'))
+ 'Set a string this software should use to prefix cookie names.'))
->setDescription(
pht(
'Cookies set for x.com are also sent for y.x.com. Assuming '.
- 'Phabricator instances are running on both domains, this will '.
- 'create a collision preventing you from logging in.'))
+ 'instances are running on both domains, this will create a '.
+ 'collision preventing you from logging in.'))
->addExample('dev', pht('Prefix cookie with "%s"', 'dev')),
$this->newOption('phabricator.show-prototypes', 'bool', false)
->setLocked(true)
->setBoolOptions(
array(
pht('Enable Prototypes'),
pht('Disable Prototypes'),
))
->setSummary(
pht(
'Install applications which are still under development.'))
->setDescription(
pht(
"IMPORTANT: The upstream does not provide support for prototype ".
"applications.".
"\n\n".
- "Phabricator includes prototype applications which are in an ".
+ "This platform includes prototype applications which are in an ".
"**early stage of development**. By default, prototype ".
"applications are not installed, because they are often not yet ".
"developed enough to be generally usable. You can enable ".
- "this option to install them if you're developing Phabricator ".
+ "this option to install them if you're developing applications ".
"or are interested in previewing upcoming features.".
"\n\n".
"To learn more about prototypes, see [[ %s | %s ]].".
"\n\n".
"After enabling prototypes, you can selectively uninstall them ".
"(like normal applications).",
$proto_doc_href,
$proto_doc_name)),
$this->newOption('phabricator.serious-business', 'bool', false)
->setBoolOptions(
array(
pht('Serious business'),
pht('Shenanigans'), // That should be interesting to translate. :P
))
->setSummary(
pht('Allows you to remove levity and jokes from the UI.'))
->setDescription(
pht(
- 'By default, Phabricator includes some flavor text in the UI, '.
+ 'By default, this software includes some flavor text in the UI, '.
'like a prompt to "Weigh In" rather than "Add Comment" in '.
'Maniphest. If you\'d prefer more traditional UI strings like '.
'"Add Comment", you can set this flag to disable most of the '.
'extra flavor.')),
$this->newOption(
'remarkup.ignored-object-names',
'string',
// Q1, Q2, etc., are common abbreviations for "Quarter".
// V1, V2, etc., are common abbreviations for "Version".
// P1, P2, etc., are common abbreviations for "Priority".
// M1 is a computer chip manufactured by Apple.
// M2 (commonly spelled "M.2") is an expansion slot on motherboards.
// M4 is a carbine.
// M8 is a phonetic spelling of "mate", used in culturally significant
// copypasta about navy seals.
'/^(Q|V|M|P)\d$/')
->setSummary(
pht('Text values that match this regex and are also object names '.
'will not be linked.'))
->setDescription(
pht(
- 'By default, Phabricator links object names in Remarkup fields '.
+ 'By default, this software links object names in Remarkup fields '.
'to the corresponding object. This regex can be used to modify '.
'this behavior; object names that match this regex will not be '.
'linked.')),
$this->newOption('environment.append-paths', 'list<string>', $paths)
->setSummary(
pht(
'These paths get appended to your %s environment variable.',
'$PATH'))
->setDescription(
pht(
- "Phabricator occasionally shells out to other binaries on the ".
+ "Thhi software sometimes executes other binaries on the ".
"server. An example of this is the `%s` command, used to ".
"syntax-highlight code written in languages other than PHP. By ".
"default, it is assumed that these binaries are in the %s of the ".
- "user running Phabricator (normally 'apache', 'httpd', or ".
+ "user running this software (normally 'apache', 'httpd', or ".
"'nobody'). Here you can add extra directories to the %s ".
"environment variable, for when these binaries are in ".
"non-standard locations.\n\n".
"Note that you can also put binaries in `%s` (for example, by ".
"symlinking them).\n\n".
"The current value of PATH after configuration is applied is:\n\n".
" lang=text\n".
" %s",
'pygmentize',
'$PATH',
'$PATH',
- 'phabricator/support/bin/',
+ 'support/bin/',
$path))
->setLocked(true)
->addExample('/usr/local/bin', pht('Add One Path'))
->addExample("/usr/bin\n/usr/local/bin", pht('Add Multiple Paths')),
$this->newOption('config.lock', 'set', array())
->setLocked(true)
->setDescription(pht('Additional configuration options to lock.')),
$this->newOption('config.hide', 'set', array())
->setLocked(true)
->setDescription(pht('Additional configuration options to hide.')),
$this->newOption('config.ignore-issues', 'set', array())
->setLocked(true)
->setDescription(pht('Setup issues to ignore.')),
$this->newOption('phabricator.env', 'string', null)
->setLocked(true)
->setDescription(pht('Internal.')),
$this->newOption('test.value', 'wild', null)
->setLocked(true)
->setDescription(pht('Unit test value.')),
$this->newOption('phabricator.uninstalled-applications', 'set', array())
->setLocked(true)
->setLockedMessage(pht(
'Use the %s to manage installed applications.',
phutil_tag(
'a',
array(
'href' => $applications_app_href,
),
pht('Applications application'))))
->setDescription(
pht('Array containing list of uninstalled applications.')),
$this->newOption('phabricator.application-settings', 'wild', array())
->setLocked(true)
->setDescription(
- pht('Customized settings for Phabricator applications.')),
+ pht('Customized settings for applications.')),
$this->newOption('phabricator.cache-namespace', 'string', 'phabricator')
->setLocked(true)
->setDescription(pht('Cache namespace.')),
$this->newOption('phabricator.silent', 'bool', false)
->setLocked(true)
->setBoolOptions(
array(
pht('Run Silently'),
pht('Run Normally'),
))
- ->setSummary(pht('Stop Phabricator from sending any email, etc.'))
+ ->setSummary(pht('Stop this software from sending any email, etc.'))
->setDescription($silent_description),
);
}
protected function didValidateOption(
PhabricatorConfigOption $option,
$value) {
$key = $option->getKey();
if ($key == 'phabricator.base-uri' ||
$key == 'phabricator.production-uri') {
$uri = new PhutilURI($value);
$protocol = $uri->getProtocol();
if ($protocol !== 'http' && $protocol !== 'https') {
throw new PhabricatorConfigValidationException(
pht(
'Config option "%s" is invalid. The URI must start with '.
'"%s" or "%s".',
$key,
'http://',
'https://'));
}
$domain = $uri->getDomain();
if (strpos($domain, '.') === false) {
throw new PhabricatorConfigValidationException(
pht(
'Config option "%s" is invalid. The URI must contain a dot '.
'("%s"), like "%s", not just a bare name like "%s". Some web '.
'browsers will not set cookies on domains with no TLD.',
$key,
'.',
'http://example.com/',
'http://example/'));
}
$path = $uri->getPath();
if ($path !== '' && $path !== '/') {
throw new PhabricatorConfigValidationException(
pht(
"Config option '%s' is invalid. The URI must NOT have a path, ".
- "e.g. '%s' is OK, but '%s' is not. Phabricator must be installed ".
- "on an entire domain; it can not be installed on a path.",
+ "e.g. '%s' is OK, but '%s' is not. This software must be '.
+ 'installed on an entire domain; it can not be installed on a path.",
$key,
- 'http://phabricator.example.com/',
- 'http://example.com/phabricator/'));
+ 'http://devtools.example.com/',
+ 'http://example.com/devtools/'));
}
}
if ($key === 'phabricator.timezone') {
$old = date_default_timezone_get();
$ok = @date_default_timezone_set($value);
@date_default_timezone_set($old);
if (!$ok) {
throw new PhabricatorConfigValidationException(
pht(
'Config option "%s" is invalid. The timezone identifier must '.
'be a valid timezone identifier recognized by PHP, like "%s".',
$key,
'America/Los_Angeles'));
}
}
}
}
diff --git a/src/applications/config/option/PhabricatorDeveloperConfigOptions.php b/src/applications/config/option/PhabricatorDeveloperConfigOptions.php
index f4b8e8b157..cd09717bc7 100644
--- a/src/applications/config/option/PhabricatorDeveloperConfigOptions.php
+++ b/src/applications/config/option/PhabricatorDeveloperConfigOptions.php
@@ -1,157 +1,158 @@
<?php
final class PhabricatorDeveloperConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Developer / Debugging');
}
public function getDescription() {
- return pht('Options for Phabricator developers, including debugging.');
+ return pht('Options for platform developers, including debugging.');
}
public function getIcon() {
return 'fa-bug';
}
public function getGroup() {
return 'core';
}
public function getOptions() {
return array(
$this->newOption('darkconsole.enabled', 'bool', false)
->setBoolOptions(
array(
pht('Enable DarkConsole'),
pht('Disable DarkConsole'),
))
- ->setSummary(pht("Enable Phabricator's debugging console."))
+ ->setSummary(pht('Enable the debugging console.'))
->setDescription(
pht(
"DarkConsole is a development and profiling tool built into ".
- "Phabricator's web interface. You should leave it disabled unless ".
- "you are developing or debugging Phabricator.\n\n".
+ "the web interface. You should leave it disabled unless ".
+ "you are developing or debugging %s.\n\n".
"Once you activate DarkConsole for the install, **you need to ".
"enable it for your account before it will actually appear on ".
"pages.** You can do this in Settings > Developer Settings.\n\n".
"DarkConsole exposes potentially sensitive data (like queries, ".
"stack traces, and configuration) so you generally should not ".
- "turn it on in production.")),
+ "turn it on in production.",
+ PlatformSymbols::getPlatformServerName())),
$this->newOption('darkconsole.always-on', 'bool', false)
->setBoolOptions(
array(
pht('Always Activate DarkConsole'),
pht('Require DarkConsole Activation'),
))
->setSummary(pht('Activate DarkConsole on every page.'))
->setDescription(
pht(
"This option allows you to enable DarkConsole on every page, ".
"even for logged-out users. This is only really useful if you ".
"need to debug something on a logged-out page. You should not ".
"enable this option in production.\n\n".
"You must enable DarkConsole by setting '%s' ".
"before this option will have any effect.",
'darkconsole.enabled')),
$this->newOption('debug.time-limit', 'int', null)
->setSummary(
pht(
'Limit page execution time to debug hangs.'))
->setDescription(
pht(
"This option can help debug pages which are taking a very ".
"long time (more than 30 seconds) to render.\n\n".
"If a page is slow to render (but taking less than 30 seconds), ".
"the best tools to use to figure out why it is slow are usually ".
"the DarkConsole service call profiler and XHProf.\n\n".
"However, if a request takes a very long time to return, some ".
"components (like Apache, nginx, or PHP itself) may abort the ".
"request before it finishes. This can prevent you from using ".
"profiling tools to understand page performance in detail.\n\n".
"In these cases, you can use this option to force the page to ".
"abort after a smaller number of seconds (for example, 10), and ".
"dump a useful stack trace. This can provide useful information ".
"about why a page is hanging.\n\n".
"To use this option, set it to a small number (like 10), and ".
"reload a hanging page. The page should exit after 10 seconds ".
"and give you a stack trace.\n\n".
"You should turn this option off (set it to 0) when you are ".
"done with it. Leaving it on creates a small amount of overhead ".
"for all requests, even if they do not hit the time limit.")),
$this->newOption('debug.stop-on-redirect', 'bool', false)
->setBoolOptions(
array(
pht('Stop Before HTTP Redirect'),
pht('Use Normal HTTP Redirects'),
))
->setSummary(
pht(
'Confirm before redirecting so DarkConsole can be examined.'))
->setDescription(
pht(
- 'Normally, Phabricator issues HTTP redirects after a successful '.
+ 'Normally, this software issues HTTP redirects after a successful '.
'POST. This can make it difficult to debug things which happen '.
'while processing the POST, because service and profiling '.
'information are lost. By setting this configuration option, '.
- 'Phabricator will show a page instead of automatically '.
+ 'an interstitial page will be shown instead of automatically '.
'redirecting, allowing you to examine service and profiling '.
'information. It also makes the UX awful, so you should only '.
'enable it when debugging.')),
$this->newOption('debug.profile-rate', 'int', 0)
->addExample(0, pht('No profiling'))
->addExample(1, pht('Profile every request (slow)'))
->addExample(1000, pht('Profile 0.1%% of all requests'))
->setSummary(pht('Automatically profile some percentage of pages.'))
->setDescription(
pht(
- "Normally, Phabricator profiles pages only when explicitly ".
+ "Normally, pages are profiled only when explicitly ".
"requested via DarkConsole. However, it may be useful to profile ".
"some pages automatically.\n\n".
"Set this option to a positive integer N to profile 1 / N pages ".
"automatically. For example, setting it to 1 will profile every ".
"page, while setting it to 1000 will profile 1 page per 1000 ".
"requests (i.e., 0.1%% of requests).\n\n".
"Since profiling is slow and generates a lot of data, you should ".
"set this to 0 in production (to disable it) or to a large number ".
"(to collect a few samples, if you're interested in having some ".
"data to look at eventually). In development, it may be useful to ".
"set it to 1 in order to debug performance problems.\n\n".
"NOTE: You must install XHProf for profiling to work.")),
$this->newOption('debug.sample-rate', 'int', 1000)
->setLocked(true)
->addExample(0, pht('No performance sampling.'))
->addExample(1, pht('Sample every request (slow).'))
->addExample(1000, pht('Sample 0.1%% of requests.'))
->setSummary(pht('Automatically sample some fraction of requests.'))
->setDescription(
pht(
"The Multimeter application collects performance samples. You ".
- "can use this data to help you understand what Phabricator is ".
+ "can use this data to help you understand what the software is ".
"spending time and resources doing, and to identify problematic ".
"access patterns.".
"\n\n".
"This option controls how frequently sampling activates. Set it ".
"to some positive integer N to sample every 1 / N pages.".
"\n\n".
"For most installs, the default value (1 sample per 1000 pages) ".
"should collect enough data to be useful without requiring much ".
"storage or meaningfully impacting performance. If you're ".
"investigating performance issues, you can adjust the rate ".
"in order to collect more data.")),
$this->newOption('phabricator.developer-mode', 'bool', false)
->setBoolOptions(
array(
pht('Enable developer mode'),
pht('Disable developer mode'),
))
->setSummary(pht('Enable verbose error reporting and disk reads.'))
->setDescription(
pht(
'This option enables verbose error reporting (stack traces, '.
'error callouts) and forces disk reads of static assets on '.
'every reload.')),
);
}
}
diff --git a/src/applications/config/option/PhabricatorExtendingPhabricatorConfigOptions.php b/src/applications/config/option/PhabricatorExtendingPhabricatorConfigOptions.php
index 0a3ea32e44..b4ecb4bdc1 100644
--- a/src/applications/config/option/PhabricatorExtendingPhabricatorConfigOptions.php
+++ b/src/applications/config/option/PhabricatorExtendingPhabricatorConfigOptions.php
@@ -1,42 +1,42 @@
<?php
final class PhabricatorExtendingPhabricatorConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
- return pht('Extending Phabricator');
+ return pht('Extensions');
}
public function getDescription() {
- return pht('Make Phabricator even cooler!');
+ return pht('Manage extensions.');
}
public function getIcon() {
return 'fa-rocket';
}
public function getGroup() {
return 'core';
}
public function getOptions() {
return array(
$this->newOption('load-libraries', 'list<string>', array())
->setLocked(true)
->setSummary(pht('Paths to additional phutil libraries to load.'))
->addExample('/srv/our-libs/sekrit-phutil', pht('Valid Setting')),
$this->newOption('events.listeners', 'list<string>', array())
->setLocked(true)
->setSummary(
pht('Listeners receive callbacks when interesting things occur.'))
->setDescription(
pht(
'You can respond to various application events by installing '.
'listeners, which will receive callbacks when interesting things '.
'occur. Specify a list of classes which extend '.
'PhabricatorEventListener here.'))
->addExample('MyEventListener', pht('Valid Setting')),
);
}
}
diff --git a/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php b/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php
index 7e6978dfd8..cca8794055 100644
--- a/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php
+++ b/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php
@@ -1,309 +1,311 @@
<?php
final class PhabricatorMetaMTAConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Mail');
}
public function getDescription() {
return pht('Configure Mail.');
}
public function getIcon() {
return 'fa-send';
}
public function getGroup() {
return 'core';
}
public function getOptions() {
$send_as_user_desc = $this->deformat(pht(<<<EODOC
When a user takes an action which generates an email notification (like
-commenting on a Differential revision), Phabricator can either send that mail
-"From" the user's email address (like "alincoln@logcabin.com") or "From" the
-'%s' address.
+commenting on a Differential revision), the "From" address can either be set
+to the user's email address (like "alincoln@logcabin.com") or the
+"metamta.defualt-address" address.
-The user experience is generally better if Phabricator uses the user's real
-address as the "From" since the messages are easier to organize when they appear
-in mail clients, but this will only work if the server is authorized to send
-email on behalf of the "From" domain. Practically, this means:
+The user experience is generally better if the user's real address is used as
+the "From" header value, since the messages are easier to organize when they
+appear in mail clients, but this will only work if the server is authorized to
+send email on behalf of the "From" domain. Practically, this means:
- If you are doing an install for Example Corp and all the users will have
- corporate @corp.example.com addresses and any hosts Phabricator is running
+ corporate @corp.example.com addresses and any hosts this software is running
on are authorized to send email from corp.example.com, you can enable this
to make the user experience a little better.
- If you are doing an install for an open source project and your users will
- be registering via Facebook and using personal email addresses, you probably
- should not enable this or all of your outgoing email might vanish into SFP
- blackholes.
+ be registering via third-party services and/or using personal email
+ addresses, you probably should not enable this or all of your outgoing
+ email might vanish into SFP blackholes.
- If your install is anything else, you're safer leaving this off, at least
initially, since the risk in turning it on is that your outgoing mail will
never arrive.
EODOC
- ,
- 'metamta.default-address'));
+ ));
$one_mail_per_recipient_desc = $this->deformat(pht(<<<EODOC
When a message is sent to multiple recipients (for example, several reviewers on
-a code review), Phabricator can either deliver one email to everyone (e.g., "To:
+a code review), it can either be delieverd as one email to everyone (e.g., "To:
alincoln, usgrant, htaft") or separate emails to each user (e.g., "To:
alincoln", "To: usgrant", "To: htaft"). The major advantages and disadvantages
of each approach are:
- One mail to everyone:
- This violates policy controls. The body of the mail is generated without
respect for object policies.
- Recipients can see To/Cc at a glance.
- If you use mailing lists, you won't get duplicate mail if you're
a normal recipient and also Cc'd on a mailing list.
- Getting threading to work properly is harder, and probably requires
making mail less useful by turning off options.
- Sometimes people will "Reply All", which can send mail to too many
- recipients. Phabricator will try not to send mail to users who already
+ recipients. This software will try not to send mail to users who already
received a similar message, but can not prevent all stray email arising
from "Reply All".
- Not supported with a private reply-to address.
- Mail messages are sent in the server default translation.
- Mail that must be delivered over secure channels will leak the recipient
list in the "To" and "Cc" headers.
- One mail to each user:
- Policy controls work correctly and are enforced per-user.
- Recipients need to look in the mail body to see To/Cc.
- If you use mailing lists, recipients may sometimes get duplicate
mail.
- Getting threading to work properly is easier, and threading settings
can be customzied by each user.
- "Reply All" will never send extra mail to other users involved in the
thread.
- Required if private reply-to addresses are configured.
- Mail messages are sent in the language of user preference.
EODOC
));
$reply_hints_description = $this->deformat(pht(<<<EODOC
You can disable the hints under "REPLY HANDLER ACTIONS" if users prefer
smaller messages. The actions themselves will still work properly.
EODOC
));
$recipient_hints_description = $this->deformat(pht(<<<EODOC
You can disable the "To:" and "Cc:" footers in mail if users prefer smaller
messages.
EODOC
));
$email_preferences_description = $this->deformat(pht(<<<EODOC
You can disable the email preference link in emails if users prefer smaller
emails.
EODOC
));
$re_prefix_description = $this->deformat(pht(<<<EODOC
Mail.app on OS X Lion won't respect threading headers unless the subject is
-prefixed with "Re:". If you enable this option, Phabricator will add "Re:" to
+prefixed with "Re:". If you enable this option, this software will add "Re:" to
the subject line of all mail which is expected to thread. If you've set
'metamta.one-mail-per-recipient', users can override this setting in their
preferences.
EODOC
));
$vary_subjects_description = $this->deformat(pht(<<<EODOC
If true, allow MetaMTA to change mail subjects to put text like '[Accepted]' and
'[Commented]' in them. This makes subjects more useful, but might break
threading on some clients. If you've set '%s', users can override this setting
in their preferences.
EODOC
,
'metamta.one-mail-per-recipient'));
$reply_to_description = $this->deformat(pht(<<<EODOC
-If you enable `%s`, Phabricator uses "From" to authenticate users. You can
+If you enable `%s`, this software uses "From" to authenticate users. You can
additionally enable this setting to try to authenticate with 'Reply-To'. Note
that this is completely spoofable and insecure (any user can set any 'Reply-To'
address) but depending on the nature of your install or other deliverability
conditions this might be okay. Generally, you can't do much more by spoofing
Reply-To than be annoying (you can write but not read content). But this is
still **COMPLETELY INSECURE**.
EODOC
,
'metamta.public-replies'));
$adapter_description = $this->deformat(pht(<<<EODOC
Adapter class to use to transmit mail to the MTA. The default uses
PHPMailerLite, which will invoke "sendmail". This is appropriate if sendmail
actually works on your host, but if you haven't configured mail it may not be so
great. A number of other mailers are available (e.g., SES, SendGrid, SMTP,
custom mailers). This option is deprecated in favor of 'cluster.mailers'.
EODOC
));
$public_replies_description = $this->deformat(pht(<<<EODOC
-By default, Phabricator generates unique reply-to addresses and sends a separate
-email to each recipient when you enable reply handling. This is more secure than
-using "From" to establish user identity, but can mean users may receive multiple
-emails when they are on mailing lists. Instead, you can use a single, non-unique
-reply to address and authenticate users based on the "From" address by setting
-this to 'true'. This trades away a little bit of security for convenience, but
-it's reasonable in many installs. Object interactions are still protected using
-hashes in the single public email address, so objects can not be replied to
-blindly.
+By default, this software generates unique reply-to addresses and sends a
+separate email to each recipient when you enable reply handling. This is more
+secure than using "From" to establish user identity, but can mean users may
+receive multiple emails when they are on mailing lists. Instead, you can use a
+single, non-unique reply to address and authenticate users based on the "From"
+address by setting this to 'true'. This trades away a little bit of security
+for convenience, but it's reasonable in many installs. Object interactions are
+still protected using hashes in the single public email address, so objects
+can not be replied to blindly.
EODOC
));
$single_description = $this->deformat(pht(<<<EODOC
-If you want to use a single mailbox for Phabricator reply mail, you can use this
-and set a common prefix for reply addresses generated by Phabricator. It will
+If you want to use a single mailbox for reply mail, you can use this
+and set a common prefix for generated reply addresses. It will
make use of the fact that a mail-address such as
-`phabricator+D123+1hjk213h@example.com` will be delivered to the `phabricator`
+`devtools+D123+1hjk213h@example.com` will be delivered to the `devtools`
user's mailbox. Set this to the left part of the email address and it will be
prepended to all generated reply addresses.
-For example, if you want to use `phabricator@example.com`, this should be set
-to `phabricator`.
+For example, if you want to use `devtools@example.com`, this should be set
+to `devtools`.
EODOC
));
$address_description = $this->deformat(pht(<<<EODOC
-When email is sent, what format should Phabricator use for user's email
+When email is sent, what format should the software use for users' email
addresses? Valid values are:
- `short`: 'gwashington <gwashington@example.com>'
- `real`: 'George Washington <gwashington@example.com>'
- `full`: 'gwashington (George Washington) <gwashington@example.com>'
The default is `full`.
EODOC
));
$mailers_description = $this->deformat(pht(<<<EODOC
Define one or more mail transmission services. For help with configuring
mailers, see **[[ %s | %s ]]** in the documentation.
EODOC
,
PhabricatorEnv::getDoclink('Configuring Outbound Email'),
pht('Configuring Outbound Email')));
$default_description = $this->deformat(pht(<<<EODOC
Default address used as a "From" or "To" email address when an address is
required but no meaningful address is available.
If you configure inbound mail, you generally do not need to set this:
-Phabricator will automatically generate and use a suitable mailbox on the
+the software will automatically generate and use a suitable mailbox on the
inbound mail domain.
Otherwise, this option should be configured to point at a valid mailbox which
discards all mail sent to it. If you point it at an invalid mailbox, mail sent
-by Phabricator and some mail sent by users will bounce. If you point it at a
+by the software and some mail sent by users will bounce. If you point it at a
real user mailbox, that user will get a lot of mail they don't want.
For further guidance, see **[[ %s | %s ]]** in the documentation.
EODOC
,
PhabricatorEnv::getDoclink('Configuring Outbound Email'),
pht('Configuring Outbound Email')));
return array(
$this->newOption('cluster.mailers', 'cluster.mailers', array())
->setHidden(true)
->setDescription($mailers_description),
$this->newOption('metamta.default-address', 'string', null)
->setLocked(true)
->setSummary(pht('Default address used when generating mail.'))
->setDescription($default_description),
$this->newOption(
'metamta.one-mail-per-recipient',
'bool',
true)
->setLocked(true)
->setBoolOptions(
array(
pht('Send Mail To Each Recipient'),
pht('Send Mail To All Recipients'),
))
->setSummary(
pht(
- 'Controls whether Phabricator sends one email with multiple '.
- 'recipients in the "To:" line, or multiple emails, each with a '.
- 'single recipient in the "To:" line.'))
+ 'Controls whether email for multiple recipients is sent by '.
+ 'creating one message with everyone in the "To:" line, or '.
+ 'multiple messages that each have a single recipeint in the '.
+ '"To:" line.'))
->setDescription($one_mail_per_recipient_desc),
$this->newOption('metamta.can-send-as-user', 'bool', false)
->setBoolOptions(
array(
pht('Send as User Taking Action'),
- pht('Send as Phabricator'),
+ pht(
+ 'Send as %s',
+ PlatformSymbols::getPlatformServerName()),
))
->setSummary(
pht(
- 'Controls whether Phabricator sends email "From" users.'))
+ 'Controls whether email is sent "From" users.'))
->setDescription($send_as_user_desc),
$this->newOption(
'metamta.reply-handler-domain',
'string',
null)
->setLocked(true)
->setDescription(pht('Domain used for reply email addresses.'))
- ->addExample('phabricator.example.com', ''),
+ ->addExample('devtools.example.com', ''),
$this->newOption('metamta.recipients.show-hints', 'bool', true)
->setBoolOptions(
array(
pht('Show Recipient Hints'),
pht('No Recipient Hints'),
))
->setSummary(pht('Show "To:" and "Cc:" footer hints in email.'))
->setDescription($recipient_hints_description),
$this->newOption('metamta.email-preferences', 'bool', true)
->setBoolOptions(
array(
pht('Show Email Preferences Link'),
pht('No Email Preferences Link'),
))
->setSummary(pht('Show email preferences link in email.'))
->setDescription($email_preferences_description),
$this->newOption('metamta.public-replies', 'bool', false)
->setBoolOptions(
array(
pht('Use Public Replies (Less Secure)'),
pht('Use Private Replies (More Secure)'),
))
->setSummary(
pht(
- 'Phabricator can use less-secure but mailing list friendly public '.
- 'reply addresses.'))
+ 'Reply addresses can either be private (more secure) or '.
+ 'public (which works better with mailing lists).'))
->setDescription($public_replies_description),
$this->newOption('metamta.single-reply-handler-prefix', 'string', null)
->setSummary(
- pht('Allow Phabricator to use a single mailbox for all replies.'))
+ pht('Allow a single mailbox to be used for all replies.'))
->setDescription($single_description),
$this->newOption('metamta.user-address-format', 'enum', 'full')
->setEnumOptions(
array(
'short' => pht('Short'),
'real' => pht('Real'),
'full' => pht('Full'),
))
- ->setSummary(pht('Control how Phabricator renders user names in mail.'))
+ ->setSummary(pht('Control how user names are rendered in mail.'))
->setDescription($address_description)
->addExample('gwashington <gwashington@example.com>', 'short')
->addExample('George Washington <gwashington@example.com>', 'real')
->addExample(
'gwashington (George Washington) <gwashington@example.com>',
'full'),
$this->newOption('metamta.email-body-limit', 'int', 524288)
->setDescription(
pht(
'You can set a limit for the maximum byte size of outbound mail. '.
'Mail which is larger than this limit will be truncated before '.
'being sent. This can be useful if your MTA rejects mail which '.
'exceeds some limit (this is reasonably common). Specify a value '.
'in bytes.'))
->setSummary(pht('Global cap for size of generated emails (bytes).'))
->addExample(524288, pht('Truncate at 512KB'))
->addExample(1048576, pht('Truncate at 1MB')),
);
}
}
diff --git a/src/applications/config/option/PhabricatorMySQLConfigOptions.php b/src/applications/config/option/PhabricatorMySQLConfigOptions.php
index 97ae6af016..7b472cf379 100644
--- a/src/applications/config/option/PhabricatorMySQLConfigOptions.php
+++ b/src/applications/config/option/PhabricatorMySQLConfigOptions.php
@@ -1,57 +1,57 @@
<?php
final class PhabricatorMySQLConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('MySQL');
}
public function getDescription() {
return pht('Database configuration.');
}
public function getIcon() {
return 'fa-database';
}
public function getGroup() {
return 'core';
}
public function getOptions() {
return array(
$this->newOption('mysql.host', 'string', 'localhost')
->setLocked(true)
->setDescription(
pht('MySQL database hostname.'))
->addExample('localhost', pht('MySQL on this machine'))
->addExample('db.example.com:3300', pht('Nonstandard port')),
$this->newOption('mysql.user', 'string', 'root')
->setLocked(true)
->setDescription(
pht('MySQL username to use when connecting to the database.')),
$this->newOption('mysql.pass', 'string', null)
->setHidden(true)
->setDescription(
pht('MySQL password to use when connecting to the database.')),
$this->newOption('storage.default-namespace', 'string', 'phabricator')
->setLocked(true)
->setSummary(
- pht('The namespace that Phabricator databases should use.'))
+ pht('The namespace that databases should use.'))
->setDescription(
pht(
- "Phabricator puts databases in a namespace, which defaults to ".
+ "Databases are created in a namespace, which defaults to ".
"'phabricator' -- for instance, the Differential database is ".
"named 'phabricator_differential' by default. You can change ".
"this namespace if you want. Normally, you should not do this ".
- "unless you are developing Phabricator and using namespaces to ".
+ "unless you are developing extensions and using namespaces to ".
"separate multiple sandbox datasets.")),
$this->newOption('mysql.port', 'string', null)
->setLocked(true)
->setDescription(
pht('MySQL port to use when connecting to the database.')),
);
}
}
diff --git a/src/applications/config/option/PhabricatorPHDConfigOptions.php b/src/applications/config/option/PhabricatorPHDConfigOptions.php
index 259660d562..5243bb3ee8 100644
--- a/src/applications/config/option/PhabricatorPHDConfigOptions.php
+++ b/src/applications/config/option/PhabricatorPHDConfigOptions.php
@@ -1,69 +1,69 @@
<?php
final class PhabricatorPHDConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Daemons');
}
public function getDescription() {
return pht('Options relating to PHD (daemons).');
}
public function getIcon() {
return 'fa-pied-piper-alt';
}
public function getGroup() {
return 'core';
}
public function getOptions() {
return array(
$this->newOption('phd.log-directory', 'string', '/var/tmp/phd/log')
->setLocked(true)
->setDescription(
pht('Directory that the daemons should use to store log files.')),
$this->newOption('phd.taskmasters', 'int', 4)
->setLocked(true)
->setSummary(pht('Maximum taskmaster daemon pool size.'))
->setDescription(
pht(
"Maximum number of taskmaster daemons to run at once. Raising ".
"this can increase the maximum throughput of the task queue. The ".
"pool will automatically scale down when unutilized.".
"\n\n".
"If you are running a cluster, this limit applies separately ".
"to each instance of `phd`. For example, if this limit is set ".
"to `4` and you have three hosts running daemons, the effective ".
"global limit will be 12.".
"\n\n".
"After changing this value, you must restart the daemons. Most ".
"configuration changes are picked up by the daemons ".
"automatically, but pool sizes can not be changed without a ".
"restart.")),
$this->newOption('phd.user', 'string', null)
->setLocked(true)
->setSummary(pht('System user to run daemons as.'))
->setDescription(
pht(
'Specify a system user to run the daemons as. Primarily, this '.
'user will own the working copies of any repositories that '.
- 'Phabricator imports or manages. This option is new and '.
+ 'this software imports or manages. This option is new and '.
'experimental.')),
$this->newOption('phd.garbage-collection', 'wild', array())
->setLocked(true)
->setLockedMessage(
pht(
'This option can not be edited from the web UI. Use %s to adjust '.
'garbage collector policies.',
phutil_tag('tt', array(), 'bin/garbage set-policy')))
->setSummary(pht('Retention policies for garbage collection.'))
->setDescription(
pht(
'Customizes retention policies for garbage collectors.')),
);
}
}
diff --git a/src/applications/config/option/PhabricatorSecurityConfigOptions.php b/src/applications/config/option/PhabricatorSecurityConfigOptions.php
index 3438d3bd67..c57bd2ad87 100644
--- a/src/applications/config/option/PhabricatorSecurityConfigOptions.php
+++ b/src/applications/config/option/PhabricatorSecurityConfigOptions.php
@@ -1,339 +1,337 @@
<?php
final class PhabricatorSecurityConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Security');
}
public function getDescription() {
return pht('Security options.');
}
public function getIcon() {
return 'fa-lock';
}
public function getGroup() {
return 'core';
}
public function getOptions() {
$doc_href = PhabricatorEnv::getDoclink('Configuring a File Domain');
$doc_name = pht('Configuration Guide: Configuring a File Domain');
$default_address_blacklist = array(
// This is all of the IANA special/reserved blocks in IPv4 space.
'0.0.0.0/8',
'10.0.0.0/8',
'100.64.0.0/10',
'127.0.0.0/8',
'169.254.0.0/16',
'172.16.0.0/12',
'192.0.0.0/24',
'192.0.2.0/24',
'192.88.99.0/24',
'192.168.0.0/16',
'198.18.0.0/15',
'198.51.100.0/24',
'203.0.113.0/24',
'224.0.0.0/4',
'240.0.0.0/4',
'255.255.255.255/32',
// And these are the IANA special/reserved blocks in IPv6 space.
'::/128',
'::1/128',
'::ffff:0:0/96',
'100::/64',
'64:ff9b::/96',
'2001::/32',
'2001:10::/28',
'2001:20::/28',
'2001:db8::/32',
'2002::/16',
'fc00::/7',
'fe80::/10',
'ff00::/8',
);
$keyring_type = 'custom:PhabricatorKeyringConfigOptionType';
$keyring_description = $this->deformat(pht(<<<EOTEXT
The keyring stores master encryption keys. For help with configuring a keyring
and encryption, see **[[ %s | Configuring Encryption ]]**.
EOTEXT
,
PhabricatorEnv::getDoclink('Configuring Encryption')));
$require_mfa_description = $this->deformat(pht(<<<EOTEXT
-By default, Phabricator allows users to add multi-factor authentication to
+By default, this software allows users to add multi-factor authentication to
their accounts, but does not require it. By enabling this option, you can
force all users to add at least one authentication factor before they can use
their accounts.
Administrators can query a list of users who do not have MFA configured in
{nav People}:
- **[[ %s | %s ]]**
EOTEXT
,
'/people/?mfa=false',
pht('List of Users Without MFA')));
return array(
$this->newOption('security.alternate-file-domain', 'string', null)
->setLocked(true)
->setSummary(pht('Alternate domain to serve files from.'))
->setDescription(
pht(
- 'By default, Phabricator serves files from the same domain '.
+ 'By default, this software serves files from the same domain '.
'the application is served from. This is convenient, but '.
'presents a security risk.'.
"\n\n".
'You should configure a CDN or alternate file domain to mitigate '.
'this risk. Configuring a CDN will also improve performance. See '.
'[[ %s | %s ]] for instructions.',
$doc_href,
$doc_name))
->addExample('https://files.phabcdn.net/', pht('Valid Setting')),
$this->newOption(
'security.hmac-key',
'string',
'[D\t~Y7eNmnQGJ;rnH6aF;m2!vJ8@v8C=Cs:aQS\.Qw')
->setHidden(true)
->setSummary(
pht('Key for HMAC digests.'))
->setDescription(
pht(
'Default key for HMAC digests where the key is not important '.
'(i.e., the hash itself is secret). You can change this if you '.
'want (to any other string), but doing so will break existing '.
'sessions and CSRF tokens. This option is deprecated. Newer '.
'code automatically manages HMAC keys.')),
$this->newOption('security.require-https', 'bool', false)
->setLocked(true)
->setSummary(
pht('Force users to connect via HTTPS instead of HTTP.'))
->setDescription(
pht(
"If the web server responds to both HTTP and HTTPS requests but ".
"you want users to connect with only HTTPS, you can set this ".
- "to `true` to make Phabricator redirect HTTP requests to HTTPS.".
+ "to `true` to make this service redirect HTTP requests to HTTPS.".
"\n\n".
"Normally, you should just configure your server not to accept ".
"HTTP traffic, but this setting may be useful if you originally ".
"used HTTP and have now switched to HTTPS but don't want to ".
"break old links, or if your webserver sits behind a load ".
"balancer which terminates HTTPS connections and you can not ".
"reasonably configure more granular behavior there.".
"\n\n".
- "IMPORTANT: Phabricator determines if a request is HTTPS or not ".
- "by examining the PHP `%s` variable. If you run ".
- "Apache/mod_php this will probably be set correctly for you ".
- "automatically, but if you run Phabricator as CGI/FCGI (e.g., ".
- "through nginx or lighttpd), you need to configure your web ".
- "server so that it passes the value correctly based on the ".
- "connection type.".
+ "IMPORTANT: A request is identified as HTTP or HTTPS by examining ".
+ "the PHP `%s` variable. If you run Apache/mod_php this will ".
+ "probably be set correctly for you automatically, but if you run ".
+ "as CGI/FCGI (e.g., through nginx or lighttpd), you need to ".
+ "configure your web server so that it passes the value correctly ".
+ "based on the connection type.".
"\n\n".
- "If you configure Phabricator in cluster mode, note that this ".
+ "If you configure clustering, note that this ".
"setting is ignored by intracluster requests.",
"\$_SERVER['HTTPS']"))
->setBoolOptions(
array(
pht('Force HTTPS'),
pht('Allow HTTP'),
)),
$this->newOption('security.require-multi-factor-auth', 'bool', false)
->setLocked(true)
->setSummary(
pht('Require all users to configure multi-factor authentication.'))
->setDescription($require_mfa_description)
->setBoolOptions(
array(
pht('Multi-Factor Required'),
pht('Multi-Factor Optional'),
)),
$this->newOption(
'uri.allowed-protocols',
'set',
array(
'http' => true,
'https' => true,
'mailto' => true,
))
->setSummary(
pht(
'Determines which URI protocols are valid for links and '.
'redirects.'))
->setDescription(
pht(
'When users write comments which have URIs, they will be '.
'automatically turned into clickable links if the URI protocol '.
'appears in this set.'.
"\n\n".
'This set of allowed protocols is primarily intended to prevent '.
'security issues with "javascript:" and other potentially '.
'dangerous URI handlers.'.
"\n\n".
'This set is also used to enforce valid redirect URIs. '.
- 'Phabricator will refuse to issue a HTTP "Location" redirect to a '.
- 'URI with a protocol not on this set.'.
+ 'This service will refuse to issue a HTTP "Location" redirect '.
+ 'to a URI with a protocol not on this set.'.
"\n\n".
'Usually, "http" and "https" should be present in this set. If '.
- 'you remove one or both protocols, some Phabricator features '.
- 'which rely on links or redirects may not work.'))
+ 'you remove one or both protocols, some features which rely on '.
+ 'links or redirects may not work.'))
->addExample("http\nhttps", pht('Valid Setting'))
->setLocked(true),
$this->newOption(
'uri.allowed-editor-protocols',
'set',
array(
'http' => true,
'https' => true,
// This handler is installed by Textmate.
'txmt' => true,
// This handler is for MacVim.
'mvim' => true,
// Unofficial handler for Vim.
'vim' => true,
// Unofficial handler for Sublime.
'subl' => true,
// Unofficial handler for Emacs.
'emacs' => true,
// This isn't a standard handler installed by an application, but
// is a reasonable name for a user-installed handler.
'editor' => true,
// This handler is for Visual Studio Code.
'vscode' => true,
// This is for IntelliJ IDEA.
'idea' => true,
))
->setSummary(pht('Whitelists editor protocols for "Open in Editor".'))
->setDescription(
pht(
'Users can configure a URI pattern to open files in a text '.
'editor. The URI must use a protocol on this whitelist.'))
->setLocked(true),
$this->newOption('remarkup.enable-embedded-youtube', 'bool', false)
->setBoolOptions(
array(
pht('Embed YouTube videos'),
pht("Don't embed YouTube videos"),
))
->setSummary(
pht('Determines whether or not YouTube videos get embedded.'))
->setDescription(
pht(
"If you enable this, linked YouTube videos will be embedded ".
"inline. This has mild security implications (you'll leak ".
"referrers to YouTube) and is pretty silly (but sort of ".
"awesome).")),
$this->newOption(
'security.outbound-blacklist',
'list<string>',
$default_address_blacklist)
->setLocked(true)
->setSummary(
pht(
'Blacklist subnets to prevent user-initiated outbound '.
'requests.'))
->setDescription(
pht(
- 'Phabricator users can make requests to other services from '.
- 'the Phabricator host in some circumstances (for example, by '.
- 'creating a repository with a remote URL or having Phabricator '.
- 'fetch an image from a remote server).'.
+ 'Users can make requests to other services from '.
+ 'service hosts in some circumstances (for example, by '.
+ 'creating a repository with a remote URL).'.
"\n\n".
'This may represent a security vulnerability if services on '.
'the same subnet will accept commands or reveal private '.
'information over unauthenticated HTTP GET, based on the source '.
'IP address. In particular, all hosts in EC2 have access to '.
'such a service.'.
"\n\n".
- 'This option defines a list of netblocks which Phabricator '.
- 'will decline to connect to. Generally, you should list all '.
+ 'This option defines a list of netblocks which requests will '.
+ 'never be issued to. Generally, you should list all '.
'private IP space here.'))
->addExample(array('0.0.0.0/0'), pht('No Outbound Requests')),
$this->newOption('security.strict-transport-security', 'bool', false)
->setLocked(true)
->setBoolOptions(
array(
pht('Use HSTS'),
pht('Do Not Use HSTS'),
))
->setSummary(pht('Enable HTTP Strict Transport Security (HSTS).'))
->setDescription(
pht(
'HTTP Strict Transport Security (HSTS) sends a header which '.
'instructs browsers that the site should only be accessed '.
'over HTTPS, never HTTP. This defuses an attack where an '.
'adversary gains access to your network, then proxies requests '.
'through an unsecured link.'.
"\n\n".
'Do not enable this option if you serve (or plan to ever serve) '.
'unsecured content over plain HTTP. It is very difficult to '.
'undo this change once users\' browsers have accepted the '.
'setting.')),
$this->newOption('keyring', $keyring_type, array())
->setHidden(true)
->setSummary(pht('Configure master encryption keys.'))
->setDescription($keyring_description),
);
}
protected function didValidateOption(
PhabricatorConfigOption $option,
$value) {
$key = $option->getKey();
if ($key == 'security.alternate-file-domain') {
$uri = new PhutilURI($value);
$protocol = $uri->getProtocol();
if ($protocol !== 'http' && $protocol !== 'https') {
throw new PhabricatorConfigValidationException(
pht(
"Config option '%s' is invalid. The URI must start with ".
"'%s' or '%s'.",
$key,
'http://',
'https://'));
}
$domain = $uri->getDomain();
if (strpos($domain, '.') === false) {
throw new PhabricatorConfigValidationException(
pht(
"Config option '%s' is invalid. The URI must contain a dot ('.'), ".
"like '%s', not just a bare name like '%s'. ".
"Some web browsers will not set cookies on domains with no TLD.",
$key,
'http://example.com/',
'http://example/'));
}
$path = $uri->getPath();
if ($path !== '' && $path !== '/') {
throw new PhabricatorConfigValidationException(
pht(
"Config option '%s' is invalid. The URI must NOT have a path, ".
- "e.g. '%s' is OK, but '%s' is not. Phabricator must be installed ".
- "on an entire domain; it can not be installed on a path.",
+ "e.g. '%s' is OK, but '%s' is not. This software must be ".
+ "installed on an entire domain; it can not be installed on a path.",
$key,
- 'http://phabricator.example.com/',
- 'http://example.com/phabricator/'));
+ 'http://devtools.example.com/',
+ 'http://example.com/devtools/'));
}
}
}
}
diff --git a/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php b/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php
index 27596f7f07..05303e2b96 100644
--- a/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php
+++ b/src/applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php
@@ -1,155 +1,154 @@
<?php
final class PhabricatorSyntaxHighlightingConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Syntax Highlighting');
}
public function getDescription() {
return pht('Options relating to syntax highlighting source code.');
}
public function getIcon() {
return 'fa-code';
}
public function getGroup() {
return 'core';
}
public function getOptions() {
$caches_href = PhabricatorEnv::getDoclink('Managing Caches');
return array(
$this->newOption(
'syntax-highlighter.engine',
'class',
'PhutilDefaultSyntaxHighlighterEngine')
->setBaseClass('PhutilSyntaxHighlighterEngine')
->setSummary(pht('Default non-pygments syntax highlighter engine.'))
->setDescription(
pht(
- 'Phabricator can highlight PHP by default and use Pygments for '.
- 'other languages if enabled. You can provide a custom '.
- 'highlighter engine by extending class %s.',
+ 'You can provide a custom highlighter engine by extending '.
+ 'class %s.',
'PhutilSyntaxHighlighterEngine')),
$this->newOption('pygments.enabled', 'bool', false)
->setSummary(
- pht('Should Phabricator use Pygments to highlight code?'))
+ pht('Use Pygments to highlight code?'))
->setBoolOptions(
array(
pht('Use Pygments'),
pht('Do Not Use Pygments'),
))
->setDescription(
pht(
- 'Phabricator supports syntax highlighting a few languages by '.
+ 'Syntax highlighting a supported for a few languages by '.
'default, but you can install Pygments (a third-party syntax '.
'highlighting tool) to provide support for many more languages.'.
"\n\n".
'To install Pygments, visit '.
'[[ http://pygments.org | pygments.org ]] and follow the '.
'download and install instructions.'.
"\n\n".
'Once Pygments is installed, enable this option '.
- '(`pygments.enabled`) to make Phabricator use Pygments when '.
+ '(`pygments.enabled`) to make use of Pygments when '.
'highlighting source code.'.
"\n\n".
'After you install and enable Pygments, newly created source '.
'code (like diffs and pastes) should highlight correctly. '.
- 'You may need to clear Phabricator\'s caches to get previously '.
+ 'You may need to clear caches to get previously '.
'existing source code to highlight. For instructions on '.
'managing caches, see [[ %s | Managing Caches ]].',
$caches_href)),
$this->newOption(
'pygments.dropdown-choices',
'wild',
array(
'apacheconf' => 'Apache Configuration',
'bash' => 'Bash Scripting',
'brainfuck' => 'Brainf*ck',
'c' => 'C',
'coffee-script' => 'CoffeeScript',
'cpp' => 'C++',
'csharp' => 'C#',
'css' => 'CSS',
'd' => 'D',
'diff' => 'Diff',
'django' => 'Django Templating',
'docker' => 'Docker',
'erb' => 'Embedded Ruby/ERB',
'erlang' => 'Erlang',
'go' => 'Golang',
'groovy' => 'Groovy',
'haskell' => 'Haskell',
'html' => 'HTML',
'http' => 'HTTP',
'invisible' => 'Invisible',
'java' => 'Java',
'js' => 'Javascript',
'json' => 'JSON',
'make' => 'Makefile',
'mysql' => 'MySQL',
'nginx' => 'Nginx Configuration',
'objc' => 'Objective-C',
'perl' => 'Perl',
'php' => 'PHP',
'postgresql' => 'PostgreSQL',
'pot' => 'Gettext Catalog',
'puppet' => 'Puppet',
'python' => 'Python',
'rainbow' => 'Rainbow',
'remarkup' => 'Remarkup',
'rst' => 'reStructuredText',
'robotframework' => 'RobotFramework',
'ruby' => 'Ruby',
'sql' => 'SQL',
'tex' => 'LaTeX',
'text' => 'Plain Text',
'twig' => 'Twig',
'xml' => 'XML',
'yaml' => 'YAML',
))
->setSummary(
pht('Set the language list which appears in dropdowns.'))
->setDescription(
pht(
'In places that we display a dropdown to syntax-highlight code, '.
'this is where that list is defined.')),
$this->newOption(
'syntax.filemap',
'custom:PhabricatorConfigRegexOptionType',
array(
'@\.arcconfig$@' => 'json',
'@\.arclint$@' => 'json',
'@\.divinerconfig$@' => 'json',
))
->setSummary(
pht('Override what language files (based on filename) highlight as.'))
->setDescription(
pht(
'This is an override list of regular expressions which allows '.
'you to choose what language files are highlighted as. If your '.
'projects have certain rules about filenames or use unusual or '.
'ambiguous language extensions, you can create a mapping here. '.
'This is an ordered dictionary of regular expressions which will '.
'be tested against the filename. They should map to either an '.
'explicit language as a string value, or a numeric index into '.
'the captured groups as an integer.'))
->addExample(
'{"@\\\.xyz$@": "php"}',
pht('Highlight %s as PHP.', '*.xyz'))
->addExample(
'{"@/httpd\\\.conf@": "apacheconf"}',
pht('Highlight httpd.conf as "apacheconf".'))
->addExample(
'{"@\\\.([^.]+)\\\.bak$@": 1}',
pht(
"Treat all '*.x.bak' file as '.x'. NOTE: We map to capturing group ".
"1 by specifying the mapping as '1'")),
);
}
}
diff --git a/src/applications/config/option/PhabricatorUIConfigOptions.php b/src/applications/config/option/PhabricatorUIConfigOptions.php
index 391ba7e123..6802149083 100644
--- a/src/applications/config/option/PhabricatorUIConfigOptions.php
+++ b/src/applications/config/option/PhabricatorUIConfigOptions.php
@@ -1,90 +1,91 @@
<?php
final class PhabricatorUIConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('User Interface');
}
public function getDescription() {
- return pht('Configure the Phabricator UI, including colors.');
+ return pht('Configure the UI, including colors.');
}
public function getIcon() {
return 'fa-magnet';
}
public function getGroup() {
return 'core';
}
public function getOptions() {
$options = array(
'blindigo' => pht('Blindigo'),
'red' => pht('Red'),
'blue' => pht('Blue'),
'green' => pht('Green'),
'indigo' => pht('Indigo'),
'dark' => pht('Dark'),
);
$example = <<<EOJSON
[
{
"name" : "Copyright 2199 Examplecorp"
},
{
"name" : "Privacy Policy",
"href" : "http://www.example.org/privacy/"
},
{
"name" : "Terms and Conditions",
"href" : "http://www.example.org/terms/"
}
]
EOJSON;
$logo_type = 'custom:PhabricatorCustomLogoConfigType';
$footer_type = 'custom:PhabricatorCustomUIFooterConfigType';
return array(
$this->newOption('ui.header-color', 'enum', 'blindigo')
->setDescription(
- pht('Sets the default color scheme of Phabricator.'))
+ pht('Sets the default color scheme.'))
->setEnumOptions($options),
$this->newOption('ui.logo', $logo_type, array())
->setSummary(
pht('Customize the logo and wordmark text in the header.'))
->setDescription(
pht(
"Customize the logo image and text which appears in the main ".
"site header:\n\n".
" - **Logo Image**: Upload a new 80 x 80px image to replace the ".
- "Phabricator logo in the site header.\n\n".
+ "logo in the site header.\n\n".
" - **Wordmark**: Choose new text to display next to the logo. ".
- "By default, the header displays //Phabricator//.\n\n")),
+ "By default, the header displays //%s//.\n\n",
+ PlatformSymbols::getPlatformServerName())),
$this->newOption('ui.favicons', 'wild', array())
->setSummary(pht('Customize favicons.'))
->setDescription(pht('Customize favicons.'))
->setLocked(true),
$this->newOption('ui.footer-items', $footer_type, array())
->setSummary(
pht(
'Allows you to add footer links on most pages.'))
->setDescription(
pht(
"Allows you to add a footer with links in it to most ".
"pages. You might want to use these links to point at legal ".
"information or an about page.\n\n".
"Specify a list of dictionaries. Each dictionary describes ".
"a footer item. These keys are supported:\n\n".
" - `name` The name of the item.\n".
" - `href` Optionally, the link target of the item. You can ".
" omit this if you just want a piece of text, like a copyright ".
" notice."))
->addExample($example, pht('Basic Example')),
);
}
}
diff --git a/src/applications/config/response/PhabricatorConfigResponse.php b/src/applications/config/response/PhabricatorConfigResponse.php
index 588f60255f..3f9a9c86e9 100644
--- a/src/applications/config/response/PhabricatorConfigResponse.php
+++ b/src/applications/config/response/PhabricatorConfigResponse.php
@@ -1,51 +1,51 @@
<?php
final class PhabricatorConfigResponse extends AphrontStandaloneHTMLResponse {
private $view;
public function setView(PhabricatorSetupIssueView $view) {
$this->view = $view;
return $this;
}
public function getHTTPResponseCode() {
return 500;
}
protected function getResources() {
return array(
'css/application/config/config-template.css',
'css/application/config/setup-issue.css',
);
}
protected function getResponseTitle() {
- return pht('Phabricator Setup Error');
+ return pht('Setup Error');
}
protected function getResponseBodyClass() {
if (PhabricatorSetupCheck::isInFlight()) {
return 'setup-fatal in-flight';
} else {
return 'setup-fatal';
}
}
protected function getResponseBody() {
$view = $this->view;
if (PhabricatorSetupCheck::isInFlight()) {
return $view->renderInFlight();
} else {
return $view->render();
}
}
protected function buildPlainTextResponseString() {
return pht(
'This install has a fatal setup error, access the web interface '.
'to view details and resolve it.');
}
}
diff --git a/src/applications/config/view/PhabricatorSetupIssueView.php b/src/applications/config/view/PhabricatorSetupIssueView.php
index 74bdc2d522..003725d5fc 100644
--- a/src/applications/config/view/PhabricatorSetupIssueView.php
+++ b/src/applications/config/view/PhabricatorSetupIssueView.php
@@ -1,614 +1,614 @@
<?php
final class PhabricatorSetupIssueView extends AphrontView {
private $issue;
public function setIssue(PhabricatorSetupIssue $issue) {
$this->issue = $issue;
return $this;
}
public function getIssue() {
return $this->issue;
}
public function renderInFlight() {
$issue = $this->getIssue();
return id(new PhabricatorInFlightErrorView())
->setMessage($issue->getName())
->render();
}
public function render() {
$issue = $this->getIssue();
$description = array();
$description[] = phutil_tag(
'div',
array(
'class' => 'setup-issue-instructions',
),
phutil_escape_html_newlines($issue->getMessage()));
$configs = $issue->getPHPConfig();
if ($configs) {
$description[] = $this->renderPHPConfig($configs, $issue);
}
$configs = $issue->getMySQLConfig();
if ($configs) {
$description[] = $this->renderMySQLConfig($configs);
}
$configs = $issue->getPhabricatorConfig();
if ($configs) {
$description[] = $this->renderPhabricatorConfig($configs);
}
$related_configs = $issue->getRelatedPhabricatorConfig();
if ($related_configs) {
$description[] = $this->renderPhabricatorConfig($related_configs,
$related = true);
}
$commands = $issue->getCommands();
if ($commands) {
$run_these = pht('Run these %d command(s):', count($commands));
$description[] = phutil_tag(
'div',
array(
'class' => 'setup-issue-config',
),
array(
phutil_tag('p', array(), $run_these),
phutil_tag('pre', array(), phutil_implode_html("\n", $commands)),
));
}
$extensions = $issue->getPHPExtensions();
if ($extensions) {
$install_these = pht(
'Install these %d PHP extension(s):', count($extensions));
$install_info = pht(
'You can usually install a PHP extension using %s or %s. Common '.
'package names are %s or %s. Try commands like these:',
phutil_tag('tt', array(), 'apt-get'),
phutil_tag('tt', array(), 'yum'),
hsprintf('<tt>php-<em>%s</em></tt>', pht('extname')),
hsprintf('<tt>php5-<em>%s</em></tt>', pht('extname')));
// TODO: We should do a better job of detecting how to install extensions
// on the current system.
$install_commands = hsprintf(
"\$ sudo apt-get install php5-<em>extname</em> ".
"# Debian / Ubuntu\n".
"\$ sudo yum install php-<em>extname</em> ".
"# Red Hat / Derivatives");
$fallback_info = pht(
"If those commands don't work, try Google. The process of installing ".
- "PHP extensions is not specific to Phabricator, and any instructions ".
- "you can find for installing them on your system should work. On Mac ".
- "OS X, you might want to try Homebrew.");
+ "PHP extensions is not specific to this software, and any ".
+ "instructions you can find for installing them on your system should ".
+ "work. On Mac OS X, you might want to try Homebrew.");
$restart_info = pht(
- 'After installing new PHP extensions, <strong>restart Phabricator '.
+ 'After installing new PHP extensions, <strong>restart everything '.
'for the changes to take effect</strong>. For help with restarting '.
- 'Phabricator, see %s in the documentation.',
+ 'everything, see %s in the documentation.',
$this->renderRestartLink());
$description[] = phutil_tag(
'div',
array(
'class' => 'setup-issue-config',
),
array(
phutil_tag('p', array(), $install_these),
phutil_tag('pre', array(), implode("\n", $extensions)),
phutil_tag('p', array(), $install_info),
phutil_tag('pre', array(), $install_commands),
phutil_tag('p', array(), $fallback_info),
phutil_tag('p', array(), $restart_info),
));
}
$related_links = $issue->getLinks();
if ($related_links) {
$description[] = $this->renderRelatedLinks($related_links);
}
$actions = array();
if (!$issue->getIsFatal()) {
if ($issue->getIsIgnored()) {
$actions[] = javelin_tag(
'a',
array(
'href' => '/config/unignore/'.$issue->getIssueKey().'/',
'sigil' => 'workflow',
'class' => 'button button-grey',
),
pht('Unignore Setup Issue'));
} else {
$actions[] = javelin_tag(
'a',
array(
'href' => '/config/ignore/'.$issue->getIssueKey().'/',
'sigil' => 'workflow',
'class' => 'button button-grey',
),
pht('Ignore Setup Issue'));
}
$actions[] = javelin_tag(
'a',
array(
'href' => '/config/issue/'.$issue->getIssueKey().'/',
'class' => 'button button-grey',
),
pht('Reload Page'));
}
if ($actions) {
$actions = phutil_tag(
'div',
array(
'class' => 'setup-issue-actions',
),
$actions);
}
if ($issue->getIsIgnored()) {
$status = phutil_tag(
'div',
array(
'class' => 'setup-issue-status',
),
pht(
'This issue is currently ignored, and does not show a global '.
'warning.'));
$next = null;
} else {
$status = null;
$next = phutil_tag(
'div',
array(
'class' => 'setup-issue-next',
),
pht('To continue, resolve this problem and reload the page.'));
}
$name = phutil_tag(
'div',
array(
'class' => 'setup-issue-name',
),
$issue->getName());
$head = phutil_tag(
'div',
array(
'class' => 'setup-issue-head',
),
$name);
$body = phutil_tag(
'div',
array(
'class' => 'setup-issue-body',
),
array(
$status,
$description,
));
$tail = phutil_tag(
'div',
array(
'class' => 'setup-issue-tail',
),
$actions);
$issue = phutil_tag(
'div',
array(
'class' => 'setup-issue',
),
array(
$head,
$body,
$tail,
));
$debug_info = phutil_tag(
'div',
array(
'class' => 'setup-issue-debug',
),
pht('Host: %s', php_uname('n')));
return phutil_tag(
'div',
array(
'class' => 'setup-issue-shell',
),
array(
$issue,
$next,
$debug_info,
));
}
private function renderPhabricatorConfig(array $configs, $related = false) {
$issue = $this->getIssue();
$table_info = phutil_tag(
'p',
array(),
pht(
- 'The current Phabricator configuration has these %d value(s):',
+ 'The current configuration has these %d value(s):',
count($configs)));
$options = PhabricatorApplicationConfigOptions::loadAllOptions();
$hidden = array();
foreach ($options as $key => $option) {
if ($option->getHidden()) {
$hidden[$key] = true;
}
}
$table = null;
$dict = array();
foreach ($configs as $key) {
if (isset($hidden[$key])) {
$dict[$key] = null;
} else {
$dict[$key] = PhabricatorEnv::getUnrepairedEnvConfig($key);
}
}
$table = $this->renderValueTable($dict, $hidden);
if ($this->getIssue()->getIsFatal()) {
$update_info = phutil_tag(
'p',
array(),
pht(
'To update these %d value(s), run these command(s) from the command '.
'line:',
count($configs)));
$update = array();
foreach ($configs as $key) {
$update[] = hsprintf(
- '<tt>phabricator/ $</tt> ./bin/config set %s <em>value</em>',
+ '<tt>$</tt> ./bin/config set %s <em>value</em>',
$key);
}
$update = phutil_tag('pre', array(), phutil_implode_html("\n", $update));
} else {
$update = array();
foreach ($configs as $config) {
if (idx($options, $config) && $options[$config]->getLocked()) {
$name = pht('View "%s"', $config);
} else {
$name = pht('Edit "%s"', $config);
}
$link = phutil_tag(
'a',
array(
'href' => '/config/edit/'.$config.'/?issue='.$issue->getIssueKey(),
),
$name);
$update[] = phutil_tag('li', array(), $link);
}
if ($update) {
$update = phutil_tag('ul', array(), $update);
if (!$related) {
$update_info = phutil_tag(
'p',
array(),
pht('You can update these %d value(s) here:', count($configs)));
} else {
$update_info = phutil_tag(
'p',
array(),
pht('These %d configuration value(s) are related:', count($configs)));
}
} else {
$update = null;
$update_info = null;
}
}
return phutil_tag(
'div',
array(
'class' => 'setup-issue-config',
),
array(
$table_info,
$table,
$update_info,
$update,
));
}
private function renderPHPConfig(array $configs, $issue) {
$table_info = phutil_tag(
'p',
array(),
pht(
'The current PHP configuration has these %d value(s):',
count($configs)));
$dict = array();
foreach ($configs as $key) {
$dict[$key] = $issue->getPHPConfigOriginalValue(
$key,
ini_get($key));
}
$table = $this->renderValueTable($dict);
ob_start();
phpinfo();
$phpinfo = ob_get_clean();
$rex = '@Loaded Configuration File\s*</td><td class="v">(.*?)</td>@i';
$matches = null;
$ini_loc = null;
if (preg_match($rex, $phpinfo, $matches)) {
$ini_loc = trim($matches[1]);
}
$rex = '@Additional \.ini files parsed\s*</td><td class="v">(.*?)</td>@i';
$more_loc = array();
if (preg_match($rex, $phpinfo, $matches)) {
$more_loc = trim($matches[1]);
if ($more_loc == '(none)') {
$more_loc = array();
} else {
$more_loc = preg_split('/\s*,\s*/', $more_loc);
}
}
$info = array();
if (!$ini_loc) {
$info[] = phutil_tag(
'p',
array(),
pht(
'To update these %d value(s), edit your PHP configuration file.',
count($configs)));
} else {
$info[] = phutil_tag(
'p',
array(),
pht(
'To update these %d value(s), edit your PHP configuration file, '.
'located here:',
count($configs)));
$info[] = phutil_tag(
'pre',
array(),
$ini_loc);
}
if ($more_loc) {
$info[] = phutil_tag(
'p',
array(),
pht(
'PHP also loaded these %s configuration file(s):',
phutil_count($more_loc)));
$info[] = phutil_tag(
'pre',
array(),
implode("\n", $more_loc));
}
$show_standard = false;
$show_opcache = false;
foreach ($configs as $key) {
if (preg_match('/^opcache\./', $key)) {
$show_opcache = true;
} else {
$show_standard = true;
}
}
if ($show_standard) {
$info[] = phutil_tag(
'p',
array(),
pht(
'You can find more information about PHP configuration values '.
'in the %s.',
phutil_tag(
'a',
array(
'href' => 'http://php.net/manual/ini.list.php',
'target' => '_blank',
),
pht('PHP Documentation'))));
}
if ($show_opcache) {
$info[] = phutil_tag(
'p',
array(),
pht(
'You can find more information about configuring OPcache in '.
'the %s.',
phutil_tag(
'a',
array(
'href' => 'http://php.net/manual/opcache.configuration.php',
'target' => '_blank',
),
pht('PHP OPcache Documentation'))));
}
$info[] = phutil_tag(
'p',
array(),
pht(
- 'After editing the PHP configuration, <strong>restart Phabricator for '.
+ 'After editing the PHP configuration, <strong>restart everything for '.
'the changes to take effect</strong>. For help with restarting '.
- 'Phabricator, see %s in the documentation.',
+ 'everything, see %s in the documentation.',
$this->renderRestartLink()));
return phutil_tag(
'div',
array(
'class' => 'setup-issue-config',
),
array(
$table_info,
$table,
$info,
));
}
private function renderMySQLConfig(array $config) {
$values = array();
$issue = $this->getIssue();
$ref = $issue->getDatabaseRef();
if ($ref) {
foreach ($config as $key) {
$value = $ref->loadRawMySQLConfigValue($key);
if ($value === null) {
$value = phutil_tag(
'em',
array(),
pht('(Not Supported)'));
}
$values[$key] = $value;
}
}
$table = $this->renderValueTable($values);
$doc_href = PhabricatorEnv::getDoclink('User Guide: Amazon RDS');
$doc_link = phutil_tag(
'a',
array(
'href' => $doc_href,
'target' => '_blank',
),
pht('User Guide: Amazon RDS'));
$info = array();
$info[] = phutil_tag(
'p',
array(),
pht(
'If you are using Amazon RDS, some of the instructions above may '.
'not apply to you. See %s for discussion of Amazon RDS.',
$doc_link));
$table_info = phutil_tag(
'p',
array(),
pht(
'The current MySQL configuration has these %d value(s):',
count($config)));
return phutil_tag(
'div',
array(
'class' => 'setup-issue-config',
),
array(
$table_info,
$table,
$info,
));
}
private function renderValueTable(array $dict, array $hidden = array()) {
$rows = array();
foreach ($dict as $key => $value) {
if (isset($hidden[$key])) {
$value = phutil_tag('em', array(), 'hidden');
} else {
$value = $this->renderValueForDisplay($value);
}
$cols = array(
phutil_tag('th', array(), $key),
phutil_tag('td', array(), $value),
);
$rows[] = phutil_tag('tr', array(), $cols);
}
return phutil_tag('table', array(), $rows);
}
private function renderValueForDisplay($value) {
if ($value === null) {
return phutil_tag('em', array(), 'null');
} else if ($value === false) {
return phutil_tag('em', array(), 'false');
} else if ($value === true) {
return phutil_tag('em', array(), 'true');
} else if ($value === '') {
return phutil_tag('em', array(), 'empty string');
} else if ($value instanceof PhutilSafeHTML) {
return $value;
} else {
return PhabricatorConfigJSON::prettyPrintJSON($value);
}
}
private function renderRelatedLinks(array $links) {
$link_info = phutil_tag(
'p',
array(),
pht(
'%d related link(s):',
count($links)));
$link_list = array();
foreach ($links as $link) {
$link_tag = phutil_tag(
'a',
array(
'target' => '_blank',
'href' => $link['href'],
),
$link['name']);
$link_item = phutil_tag('li', array(), $link_tag);
$link_list[] = $link_item;
}
$link_list = phutil_tag('ul', array(), $link_list);
return phutil_tag(
'div',
array(
'class' => 'setup-issue-config',
),
array(
$link_info,
$link_list,
));
}
private function renderRestartLink() {
$doc_href = PhabricatorEnv::getDoclink('Restarting Phabricator');
return phutil_tag(
'a',
array(
'href' => $doc_href,
'target' => '_blank',
),
pht('Restarting Phabricator'));
}
}
diff --git a/src/applications/console/plugin/DarkConsoleEventPlugin.php b/src/applications/console/plugin/DarkConsoleEventPlugin.php
index 070227c12d..182e220659 100644
--- a/src/applications/console/plugin/DarkConsoleEventPlugin.php
+++ b/src/applications/console/plugin/DarkConsoleEventPlugin.php
@@ -1,95 +1,95 @@
<?php
final class DarkConsoleEventPlugin extends DarkConsolePlugin {
public function getName() {
return pht('Events');
}
public function getDescription() {
- return pht('Information about Phabricator events and event listeners.');
+ return pht('Information about events and event listeners.');
}
public function generateData() {
$listeners = PhutilEventEngine::getInstance()->getAllListeners();
foreach ($listeners as $key => $listener) {
$listeners[$key] = array(
'id' => $listener->getListenerID(),
'class' => get_class($listener),
);
}
$events = DarkConsoleEventPluginAPI::getEvents();
foreach ($events as $key => $event) {
$events[$key] = array(
'type' => $event->getType(),
'stopped' => $event->isStopped(),
);
}
return array(
'listeners' => $listeners,
'events' => $events,
);
}
public function renderPanel() {
$data = $this->getData();
$out = array();
$out[] = phutil_tag(
'div',
array('class' => 'dark-console-panel-header'),
phutil_tag('h1', array(), pht('Registered Event Listeners')));
$rows = array();
foreach ($data['listeners'] as $listener) {
$rows[] = array($listener['id'], $listener['class']);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
pht('Internal ID'),
pht('Listener Class'),
));
$table->setColumnClasses(
array(
'',
'wide',
));
$out[] = $table->render();
$out[] = phutil_tag(
'div',
array('class' => 'dark-console-panel-header'),
phutil_tag('h1', array(), pht('Event Log')));
$rows = array();
foreach ($data['events'] as $event) {
$rows[] = array(
$event['type'],
$event['stopped'] ? pht('STOPPED') : null,
);
}
$table = new AphrontTableView($rows);
$table->setColumnClasses(
array(
'wide',
));
$table->setHeaders(
array(
pht('Event Type'),
pht('Stopped'),
));
$out[] = $table->render();
return phutil_implode_html("\n", $out);
}
}
diff --git a/src/applications/countdown/query/PhabricatorCountdownQuery.php b/src/applications/countdown/query/PhabricatorCountdownQuery.php
index 67a2f3a9e3..4a6df16e8e 100644
--- a/src/applications/countdown/query/PhabricatorCountdownQuery.php
+++ b/src/applications/countdown/query/PhabricatorCountdownQuery.php
@@ -1,107 +1,103 @@
<?php
final class PhabricatorCountdownQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $authorPHIDs;
private $upcoming;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withAuthorPHIDs(array $author_phids) {
$this->authorPHIDs = $author_phids;
return $this;
}
public function withUpcoming() {
$this->upcoming = true;
return $this;
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
public function newResultObject() {
return new PhabricatorCountdown();
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'authorPHID in (%Ls)',
$this->authorPHIDs);
}
if ($this->upcoming !== null) {
$where[] = qsprintf(
$conn,
'epoch >= %d',
PhabricatorTime::getNow());
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorCountdownApplication';
}
public function getBuiltinOrders() {
return array(
'ending' => array(
'vector' => array('-epoch', '-id'),
'name' => pht('End Date (Past to Future)'),
),
'unending' => array(
'vector' => array('epoch', 'id'),
'name' => pht('End Date (Future to Past)'),
),
) + parent::getBuiltinOrders();
}
public function getOrderableColumns() {
return array(
'epoch' => array(
'table' => $this->getPrimaryTableAlias(),
'column' => 'epoch',
'type' => 'int',
),
) + parent::getOrderableColumns();
}
protected function newPagingMapFromPartialObject($object) {
return array(
'id' => (int)$object->getID(),
'epoch' => (int)$object->getEpoch(),
);
}
}
diff --git a/src/applications/daemon/application/PhabricatorDaemonsApplication.php b/src/applications/daemon/application/PhabricatorDaemonsApplication.php
index 08e81d5d7e..3aa6b2f086 100644
--- a/src/applications/daemon/application/PhabricatorDaemonsApplication.php
+++ b/src/applications/daemon/application/PhabricatorDaemonsApplication.php
@@ -1,61 +1,61 @@
<?php
final class PhabricatorDaemonsApplication extends PhabricatorApplication {
public function getName() {
return pht('Daemons');
}
public function getShortDescription() {
- return pht('Manage Phabricator Daemons');
+ return pht('Manage Daemons');
}
public function getBaseURI() {
return '/daemon/';
}
public function getTitleGlyph() {
return "\xE2\x98\xAF";
}
public function getIcon() {
return 'fa-pied-piper-alt';
}
public function getApplicationGroup() {
return self::GROUP_ADMIN;
}
public function canUninstall() {
return false;
}
public function getEventListeners() {
return array(
new PhabricatorDaemonEventListener(),
);
}
public function getRoutes() {
return array(
'/daemon/' => array(
'' => 'PhabricatorDaemonConsoleController',
'task/(?P<id>[1-9]\d*)/' => 'PhabricatorWorkerTaskDetailController',
'log/' => array(
'' => 'PhabricatorDaemonLogListController',
'(?P<id>[1-9]\d*)/' => 'PhabricatorDaemonLogViewController',
),
'bulk/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' =>
'PhabricatorDaemonBulkJobListController',
'monitor/(?P<id>\d+)/' =>
'PhabricatorDaemonBulkJobMonitorController',
'view/(?P<id>\d+)/' =>
'PhabricatorDaemonBulkJobViewController',
),
),
);
}
}
diff --git a/src/applications/daemon/event/PhabricatorDaemonEventListener.php b/src/applications/daemon/event/PhabricatorDaemonEventListener.php
index 41324cc9c9..a17bab77af 100644
--- a/src/applications/daemon/event/PhabricatorDaemonEventListener.php
+++ b/src/applications/daemon/event/PhabricatorDaemonEventListener.php
@@ -1,119 +1,122 @@
<?php
final class PhabricatorDaemonEventListener extends PhabricatorEventListener {
private $daemons = array();
public function register() {
$this->listen(PhutilDaemonHandle::EVENT_DID_LAUNCH);
$this->listen(PhutilDaemonHandle::EVENT_DID_LOG);
$this->listen(PhutilDaemonHandle::EVENT_DID_HEARTBEAT);
$this->listen(PhutilDaemonHandle::EVENT_WILL_GRACEFUL);
$this->listen(PhutilDaemonHandle::EVENT_WILL_EXIT);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhutilDaemonHandle::EVENT_DID_LAUNCH:
$this->handleLaunchEvent($event);
break;
case PhutilDaemonHandle::EVENT_DID_HEARTBEAT:
$this->handleHeartbeatEvent($event);
break;
case PhutilDaemonHandle::EVENT_DID_LOG:
$this->handleLogEvent($event);
break;
case PhutilDaemonHandle::EVENT_WILL_GRACEFUL:
$this->handleGracefulEvent($event);
break;
case PhutilDaemonHandle::EVENT_WILL_EXIT:
$this->handleExitEvent($event);
break;
}
}
private function handleLaunchEvent(PhutilEvent $event) {
$id = $event->getValue('id');
$current_user = posix_getpwuid(posix_geteuid());
$daemon = id(new PhabricatorDaemonLog())
->setDaemonID($id)
->setDaemon($event->getValue('daemonClass'))
->setHost(php_uname('n'))
->setPID(getmypid())
->setRunningAsUser($current_user['name'])
->setStatus(PhabricatorDaemonLog::STATUS_RUNNING)
->setArgv($event->getValue('argv'))
->setExplicitArgv($event->getValue('explicitArgv'))
->save();
$this->daemons[$id] = $daemon;
}
private function handleHeartbeatEvent(PhutilEvent $event) {
$daemon = $this->getDaemon($event->getValue('id'));
// Just update the timestamp.
$daemon->save();
}
private function handleLogEvent(PhutilEvent $event) {
$daemon = $this->getDaemon($event->getValue('id'));
// TODO: This is a bit awkward for historical reasons, clean it up after
// removing Conduit.
$message = $event->getValue('message');
+
$context = $event->getValue('context');
- if (strlen($context) && $context !== $message) {
- $message = "({$context}) {$message}";
+ if (phutil_nonempty_scalar($context)) {
+ if ($context !== $message) {
+ $message = "({$context}) {$message}";
+ }
}
$type = $event->getValue('type');
$message = phutil_utf8ize($message);
id(new PhabricatorDaemonLogEvent())
->setLogID($daemon->getID())
->setLogType($type)
->setMessage((string)$message)
->setEpoch(time())
->save();
switch ($type) {
case 'WAIT':
$current_status = PhabricatorDaemonLog::STATUS_WAIT;
break;
default:
$current_status = PhabricatorDaemonLog::STATUS_RUNNING;
break;
}
if ($current_status !== $daemon->getStatus()) {
$daemon->setStatus($current_status)->save();
}
}
private function handleGracefulEvent(PhutilEvent $event) {
$id = $event->getValue('id');
$daemon = $this->getDaemon($id);
$daemon->setStatus(PhabricatorDaemonLog::STATUS_EXITING)->save();
}
private function handleExitEvent(PhutilEvent $event) {
$id = $event->getValue('id');
$daemon = $this->getDaemon($id);
$daemon->setStatus(PhabricatorDaemonLog::STATUS_EXITED)->save();
unset($this->daemons[$id]);
}
private function getDaemon($id) {
if (isset($this->daemons[$id])) {
return $this->daemons[$id];
}
throw new Exception(pht('No such daemon "%s"!', $id));
}
}
diff --git a/src/applications/daemon/management/PhabricatorDaemonManagementRestartWorkflow.php b/src/applications/daemon/management/PhabricatorDaemonManagementRestartWorkflow.php
index eb2d5d229c..a9778724af 100644
--- a/src/applications/daemon/management/PhabricatorDaemonManagementRestartWorkflow.php
+++ b/src/applications/daemon/management/PhabricatorDaemonManagementRestartWorkflow.php
@@ -1,54 +1,54 @@
<?php
final class PhabricatorDaemonManagementRestartWorkflow
extends PhabricatorDaemonManagementWorkflow {
protected function didConstruct() {
$this
->setName('restart')
->setSynopsis(
pht(
'Stop daemon processes on this host, then start the standard '.
'daemon loadout.'))
->setArguments(
array(
array(
'name' => 'graceful',
'param' => 'seconds',
'help' => pht(
'Grace period for daemons to attempt a clean shutdown, in '.
'seconds. Defaults to __15__ seconds.'),
'default' => 15,
),
array(
'name' => 'force',
'help' => pht(
'Stop all daemon processes on this host, even if they belong '.
- 'to another Phabricator instance.'),
+ 'to another instance.'),
),
array(
'name' => 'gently',
'help' => pht('Deprecated. Has no effect.'),
),
$this->getAutoscaleReserveArgument(),
));
}
public function execute(PhutilArgumentParser $args) {
$err = $this->executeStopCommand(
array(
'graceful' => $args->getArg('graceful'),
'force' => $args->getArg('force'),
));
if ($err) {
return $err;
}
return $this->executeStartCommand(
array(
'reserve' => (float)$args->getArg('autoscale-reserve'),
));
}
}
diff --git a/src/applications/daemon/management/PhabricatorDaemonManagementStartWorkflow.php b/src/applications/daemon/management/PhabricatorDaemonManagementStartWorkflow.php
index bd23d3bc7d..0aca9e5c71 100644
--- a/src/applications/daemon/management/PhabricatorDaemonManagementStartWorkflow.php
+++ b/src/applications/daemon/management/PhabricatorDaemonManagementStartWorkflow.php
@@ -1,41 +1,41 @@
<?php
final class PhabricatorDaemonManagementStartWorkflow
extends PhabricatorDaemonManagementWorkflow {
protected function didConstruct() {
$this
->setName('start')
->setSynopsis(
pht(
- 'Start the standard configured collection of Phabricator daemons. '.
+ 'Start the standard configured collection of daemons. '.
'This is appropriate for most installs. Use **%s** to '.
'customize which daemons are launched.',
'phd launch'))
->setArguments(
array(
array(
'name' => 'keep-leases',
'help' => pht(
'By default, **%s** will free all task leases held by '.
'the daemons. With this flag, this step will be skipped.',
'phd start'),
),
array(
'name' => 'force',
'help' => pht('Start daemons even if daemons are already running.'),
),
$this->getAutoscaleReserveArgument(),
));
}
public function execute(PhutilArgumentParser $args) {
return $this->executeStartCommand(
array(
'keep-leases' => $args->getArg('keep-leases'),
'force' => $args->getArg('force'),
'reserve' => (float)$args->getArg('autoscale-reserve'),
));
}
}
diff --git a/src/applications/daemon/management/PhabricatorDaemonManagementStopWorkflow.php b/src/applications/daemon/management/PhabricatorDaemonManagementStopWorkflow.php
index 19b9fc44fb..f944b8b171 100644
--- a/src/applications/daemon/management/PhabricatorDaemonManagementStopWorkflow.php
+++ b/src/applications/daemon/management/PhabricatorDaemonManagementStopWorkflow.php
@@ -1,41 +1,41 @@
<?php
final class PhabricatorDaemonManagementStopWorkflow
extends PhabricatorDaemonManagementWorkflow {
protected function didConstruct() {
$this
->setName('stop')
->setSynopsis(pht('Stop daemon processes on this host.'))
->setArguments(
array(
array(
'name' => 'graceful',
'param' => 'seconds',
'help' => pht(
'Grace period for daemons to attempt a clean shutdown, in '.
'seconds. Defaults to __15__ seconds.'),
'default' => 15,
),
array(
'name' => 'force',
'help' => pht(
'Stop all daemon processes on this host, even if they belong '.
- 'to another Phabricator instance.'),
+ 'to another instance.'),
),
array(
'name' => 'gently',
'help' => pht('Deprecated. Has no effect.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
return $this->executeStopCommand(
array(
'graceful' => $args->getArg('graceful'),
'force' => $args->getArg('force'),
));
}
}
diff --git a/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php b/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php
index b9645323c2..853a797448 100644
--- a/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php
+++ b/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php
@@ -1,611 +1,611 @@
<?php
abstract class PhabricatorDaemonManagementWorkflow
extends PhabricatorManagementWorkflow {
private $runDaemonsAsUser = null;
final protected function loadAvailableDaemonClasses() {
return id(new PhutilSymbolLoader())
->setAncestorClass('PhutilDaemon')
->setConcreteOnly(true)
->selectSymbolsWithoutLoading();
}
final protected function getLogDirectory() {
$path = PhabricatorEnv::getEnvConfig('phd.log-directory');
return $this->getControlDirectory($path);
}
private function getControlDirectory($path) {
if (!Filesystem::pathExists($path)) {
list($err) = exec_manual('mkdir -p %s', $path);
if ($err) {
throw new Exception(
pht(
"%s requires the directory '%s' to exist, but it does not exist ".
"and could not be created. Create this directory or update ".
"'%s' in your configuration to point to an existing ".
"directory.",
'phd',
$path,
'phd.log-directory'));
}
}
return $path;
}
private function findDaemonClass($substring) {
$symbols = $this->loadAvailableDaemonClasses();
$symbols = ipull($symbols, 'name');
$match = array();
foreach ($symbols as $symbol) {
if (stripos($symbol, $substring) !== false) {
if (strtolower($symbol) == strtolower($substring)) {
$match = array($symbol);
break;
} else {
$match[] = $symbol;
}
}
}
if (count($match) == 0) {
throw new PhutilArgumentUsageException(
pht(
"No daemons match '%s'! Use '%s' for a list of available daemons.",
$substring,
'phd list'));
} else if (count($match) > 1) {
throw new PhutilArgumentUsageException(
pht(
"Specify a daemon unambiguously. Multiple daemons match '%s': %s.",
$substring,
implode(', ', $match)));
}
return head($match);
}
final protected function launchDaemons(
array $daemons,
$debug,
$run_as_current_user = false) {
// Convert any shorthand classnames like "taskmaster" into proper class
// names.
foreach ($daemons as $key => $daemon) {
$class = $this->findDaemonClass($daemon['class']);
$daemons[$key]['class'] = $class;
}
$console = PhutilConsole::getConsole();
if (!$run_as_current_user) {
// Check if the script is started as the correct user
$phd_user = PhabricatorEnv::getEnvConfig('phd.user');
$current_user = posix_getpwuid(posix_geteuid());
$current_user = $current_user['name'];
if ($phd_user && $phd_user != $current_user) {
if ($debug) {
throw new PhutilArgumentUsageException(
pht(
"You are trying to run a daemon as a nonstandard user, ".
"and `%s` was not able to `%s` to the correct user. \n".
- 'Phabricator is configured to run daemons as "%s", '.
+ 'The daemons are configured to run as "%s", '.
'but the current user is "%s". '."\n".
'Use `%s` to run as a different user, pass `%s` to ignore this '.
'warning, or edit `%s` to change the configuration.',
'phd',
'sudo',
$phd_user,
$current_user,
'sudo',
'--as-current-user',
'phd.user'));
} else {
$this->runDaemonsAsUser = $phd_user;
$console->writeOut(pht('Starting daemons as %s', $phd_user)."\n");
}
}
}
$this->printLaunchingDaemons($daemons, $debug);
$trace = PhutilArgumentParser::isTraceModeEnabled();
$flags = array();
if ($trace) {
$flags[] = '--trace';
}
if ($debug) {
$flags[] = '--verbose';
}
$instance = $this->getInstance();
if ($instance) {
$flags[] = '-l';
$flags[] = $instance;
}
$config = array();
if (!$debug) {
$config['daemonize'] = true;
}
if (!$debug) {
$config['log'] = $this->getLogDirectory().'/daemons.log';
}
$config['daemons'] = $daemons;
$command = csprintf('./phd-daemon %Ls', $flags);
$phabricator_root = dirname(phutil_get_library_root('phabricator'));
$daemon_script_dir = $phabricator_root.'/scripts/daemon/';
if ($debug) {
// Don't terminate when the user sends ^C; it will be sent to the
// subprocess which will terminate normally.
pcntl_signal(
SIGINT,
array(__CLASS__, 'ignoreSignal'));
- echo "\n phabricator/scripts/daemon/ \$ {$command}\n\n";
+ echo "\n scripts/daemon/ \$ {$command}\n\n";
$tempfile = new TempFile('daemon.config');
Filesystem::writeFile($tempfile, json_encode($config));
phutil_passthru(
'(cd %s && exec %C < %s)',
$daemon_script_dir,
$command,
$tempfile);
} else {
try {
$this->executeDaemonLaunchCommand(
$command,
$daemon_script_dir,
$config,
$this->runDaemonsAsUser);
} catch (Exception $ex) {
throw new PhutilArgumentUsageException(
pht(
'Daemons are configured to run as user "%s" in configuration '.
'option `%s`, but the current user is "%s" and `phd` was unable '.
'to switch to the correct user with `sudo`. Command output:'.
"\n\n".
'%s',
$phd_user,
'phd.user',
$current_user,
$ex->getMessage()));
}
}
}
private function executeDaemonLaunchCommand(
$command,
$daemon_script_dir,
array $config,
$run_as_user = null) {
$is_sudo = false;
if ($run_as_user) {
// If anything else besides sudo should be
// supported then insert it here (runuser, su, ...)
$command = csprintf(
'sudo -En -u %s -- %C',
$run_as_user,
$command);
$is_sudo = true;
}
$future = new ExecFuture('exec %C', $command);
// Play games to keep 'ps' looking reasonable.
$future->setCWD($daemon_script_dir);
$future->write(json_encode($config));
list($stdout, $stderr) = $future->resolvex();
if ($is_sudo) {
// On OSX, `sudo -n` exits 0 when the user does not have permission to
// switch accounts without a password. This is not consistent with
// sudo on Linux, and seems buggy/broken. Check for this by string
// matching the output.
if (preg_match('/sudo: a password is required/', $stderr)) {
throw new Exception(
pht(
'%s exited with a zero exit code, but emitted output '.
'consistent with failure under OSX.',
'sudo'));
}
}
}
public static function ignoreSignal($signo) {
return;
}
public static function requireExtensions() {
self::mustHaveExtension('pcntl');
self::mustHaveExtension('posix');
}
private static function mustHaveExtension($ext) {
if (!extension_loaded($ext)) {
echo pht(
"ERROR: The PHP extension '%s' is not installed. You must ".
"install it to run daemons on this machine.\n",
$ext);
exit(1);
}
$extension = new ReflectionExtension($ext);
foreach ($extension->getFunctions() as $function) {
$function = $function->name;
if (!function_exists($function)) {
echo pht(
"ERROR: The PHP function %s is disabled. You must ".
"enable it to run daemons on this machine.\n",
$function.'()');
exit(1);
}
}
}
/* -( Commands )----------------------------------------------------------- */
final protected function executeStartCommand(array $options) {
PhutilTypeSpec::checkMap(
$options,
array(
'keep-leases' => 'optional bool',
'force' => 'optional bool',
'reserve' => 'optional float',
));
$console = PhutilConsole::getConsole();
if (!idx($options, 'force')) {
$process_refs = $this->getOverseerProcessRefs();
if ($process_refs) {
$this->logWarn(
pht('RUNNING DAEMONS'),
pht('Daemons are already running:'));
fprintf(STDERR, '%s', "\n");
foreach ($process_refs as $process_ref) {
fprintf(
STDERR,
'%s',
tsprintf(
" %s %s\n",
$process_ref->getPID(),
$process_ref->getCommand()));
}
fprintf(STDERR, '%s', "\n");
$this->logFail(
pht('RUNNING DAEMONS'),
pht(
'Use "phd stop" to stop daemons, "phd restart" to restart '.
'daemons, or "phd start --force" to ignore running processes.'));
exit(1);
}
}
if (idx($options, 'keep-leases')) {
$console->writeErr("%s\n", pht('Not touching active task queue leases.'));
} else {
$console->writeErr("%s\n", pht('Freeing active task leases...'));
$count = $this->freeActiveLeases();
$console->writeErr(
"%s\n",
pht('Freed %s task lease(s).', new PhutilNumber($count)));
}
$daemons = array(
array(
'class' => 'PhabricatorRepositoryPullLocalDaemon',
'label' => 'pull',
),
array(
'class' => 'PhabricatorTriggerDaemon',
'label' => 'trigger',
),
array(
'class' => 'PhabricatorFactDaemon',
'label' => 'fact',
),
array(
'class' => 'PhabricatorTaskmasterDaemon',
'label' => 'task',
'pool' => PhabricatorEnv::getEnvConfig('phd.taskmasters'),
'reserve' => idx($options, 'reserve', 0),
),
);
$this->launchDaemons($daemons, $is_debug = false);
$console->writeErr("%s\n", pht('Done.'));
return 0;
}
final protected function executeStopCommand(array $options) {
$grace_period = idx($options, 'graceful', 15);
$force = idx($options, 'force');
$query = id(new PhutilProcessQuery())
->withIsOverseer(true);
$instance = $this->getInstance();
if ($instance !== null && !$force) {
$query->withInstances(array($instance));
}
try {
$process_refs = $query->execute();
} catch (Exception $ex) {
// See T13321. If this fails for some reason, just continue for now so
// that daemon management still works. In the long run, we don't expect
// this to fail, but I don't want to break this workflow while we iron
// bugs out.
// See T12827. Particularly, this is likely to fail on Solaris.
phlog($ex);
$process_refs = array();
}
if (!$process_refs) {
if ($instance !== null && !$force) {
$this->logInfo(
pht('NO DAEMONS'),
pht(
'There are no running daemons for the current instance ("%s"). '.
'Use "--force" to stop daemons for all instances.',
$instance));
} else {
$this->logInfo(
pht('NO DAEMONS'),
pht('There are no running daemons.'));
}
return 0;
}
$process_refs = mpull($process_refs, null, 'getPID');
$stop_pids = array_keys($process_refs);
$live_pids = $this->sendStopSignals($stop_pids, $grace_period);
$stop_pids = array_fuse($stop_pids);
$live_pids = array_fuse($live_pids);
$dead_pids = array_diff_key($stop_pids, $live_pids);
foreach ($dead_pids as $dead_pid) {
$dead_ref = $process_refs[$dead_pid];
$this->logOkay(
pht('STOP'),
pht(
'Stopped PID %d ("%s")',
$dead_pid,
$dead_ref->getCommand()));
}
foreach ($live_pids as $live_pid) {
$live_ref = $process_refs[$live_pid];
$this->logFail(
pht('SURVIVED'),
pht(
'Unable to stop PID %d ("%s").',
$live_pid,
$live_ref->getCommand()));
}
if ($live_pids) {
$this->logWarn(
pht('SURVIVORS'),
pht(
'Unable to stop all daemon processes. You may need to run this '.
'command as root with "sudo".'));
}
return 0;
}
final protected function executeReloadCommand(array $pids) {
$process_refs = $this->getOverseerProcessRefs();
if (!$process_refs) {
$this->logInfo(
pht('NO DAEMONS'),
pht('There are no running daemon processes to reload.'));
return 0;
}
foreach ($process_refs as $process_ref) {
$pid = $process_ref->getPID();
$this->logInfo(
pht('RELOAD'),
pht('Reloading process %d...', $pid));
posix_kill($pid, SIGHUP);
}
return 0;
}
private function sendStopSignals($pids, $grace_period) {
// If we're doing a graceful shutdown, try SIGINT first.
if ($grace_period) {
$pids = $this->sendSignal($pids, SIGINT, $grace_period);
}
// If we still have daemons, SIGTERM them.
if ($pids) {
$pids = $this->sendSignal($pids, SIGTERM, 15);
}
// If the overseer is still alive, SIGKILL it.
if ($pids) {
$pids = $this->sendSignal($pids, SIGKILL, 0);
}
return $pids;
}
private function sendSignal(array $pids, $signo, $wait) {
$console = PhutilConsole::getConsole();
$pids = array_fuse($pids);
foreach ($pids as $key => $pid) {
if (!$pid) {
// NOTE: We must have a PID to signal a daemon, since sending a signal
// to PID 0 kills this process.
unset($pids[$key]);
continue;
}
switch ($signo) {
case SIGINT:
$message = pht('Interrupting process %d...', $pid);
break;
case SIGTERM:
$message = pht('Terminating process %d...', $pid);
break;
case SIGKILL:
$message = pht('Killing process %d...', $pid);
break;
}
$console->writeOut("%s\n", $message);
posix_kill($pid, $signo);
}
if ($wait) {
$start = PhabricatorTime::getNow();
do {
foreach ($pids as $key => $pid) {
if (!PhabricatorDaemonReference::isProcessRunning($pid)) {
$console->writeOut(pht('Process %d exited.', $pid)."\n");
unset($pids[$key]);
}
}
if (empty($pids)) {
break;
}
usleep(100000);
} while (PhabricatorTime::getNow() < $start + $wait);
}
return $pids;
}
private function freeActiveLeases() {
$task_table = id(new PhabricatorWorkerActiveTask());
$conn_w = $task_table->establishConnection('w');
queryfx(
$conn_w,
'UPDATE %T SET leaseExpires = UNIX_TIMESTAMP()
WHERE leaseExpires > UNIX_TIMESTAMP()',
$task_table->getTableName());
return $conn_w->getAffectedRows();
}
private function printLaunchingDaemons(array $daemons, $debug) {
$console = PhutilConsole::getConsole();
if ($debug) {
$console->writeOut(pht('Launching daemons (in debug mode):'));
} else {
$console->writeOut(pht('Launching daemons:'));
}
$log_dir = $this->getLogDirectory().'/daemons.log';
$console->writeOut(
"\n%s\n\n",
pht('(Logs will appear in "%s".)', $log_dir));
foreach ($daemons as $daemon) {
$pool_size = pht('(Pool: %s)', idx($daemon, 'pool', 1));
$console->writeOut(
" %s %s\n",
$pool_size,
$daemon['class'],
implode(' ', idx($daemon, 'argv', array())));
}
$console->writeOut("\n");
}
protected function getAutoscaleReserveArgument() {
return array(
'name' => 'autoscale-reserve',
'param' => 'ratio',
'help' => pht(
'Specify a proportion of machine memory which must be free '.
'before autoscale pools will grow. For example, a value of 0.25 '.
'means that pools will not grow unless the machine has at least '.
'25%%%% of its RAM free.'),
);
}
private function selectDaemonPIDs(array $daemons, array $pids) {
$console = PhutilConsole::getConsole();
$running_pids = array_fuse(mpull($daemons, 'getPID'));
if (!$pids) {
$select_pids = $running_pids;
} else {
// We were given a PID or set of PIDs to kill.
$select_pids = array();
foreach ($pids as $key => $pid) {
if (!preg_match('/^\d+$/', $pid)) {
$console->writeErr(pht("PID '%s' is not a valid PID.", $pid)."\n");
continue;
} else if (empty($running_pids[$pid])) {
$console->writeErr(
"%s\n",
pht(
- 'PID "%d" is not a known Phabricator daemon PID.',
+ 'PID "%d" is not a known daemon PID.',
$pid));
continue;
} else {
$select_pids[$pid] = $pid;
}
}
}
return $select_pids;
}
protected function getOverseerProcessRefs() {
$query = id(new PhutilProcessQuery())
->withIsOverseer(true);
$instance = PhabricatorEnv::getEnvConfig('cluster.instance');
if ($instance !== null) {
$query->withInstances(array($instance));
}
return $query->execute();
}
protected function getInstance() {
return PhabricatorEnv::getEnvConfig('cluster.instance');
}
}
diff --git a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php
index 8969151c06..52e8cc70d5 100644
--- a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php
+++ b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php
@@ -1,452 +1,452 @@
<?php
final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
const HEADER_MODE_NORMAL = 'normal';
const HEADER_MODE_NONE = 'none';
const HEADER_MODE_EDIT = 'edit';
private $panel;
private $panelPHID;
private $viewer;
private $enableAsyncRendering;
private $parentPanelPHIDs;
private $headerMode = self::HEADER_MODE_NORMAL;
private $movable;
private $panelHandle;
private $editMode;
private $contextObject;
private $panelKey;
public function setContextObject($object) {
$this->contextObject = $object;
return $this;
}
public function getContextObject() {
return $this->contextObject;
}
public function setPanelKey($panel_key) {
$this->panelKey = $panel_key;
return $this;
}
public function getPanelKey() {
return $this->panelKey;
}
public function setHeaderMode($header_mode) {
$this->headerMode = $header_mode;
return $this;
}
public function getHeaderMode() {
return $this->headerMode;
}
public function setPanelHandle(PhabricatorObjectHandle $panel_handle) {
$this->panelHandle = $panel_handle;
return $this;
}
public function getPanelHandle() {
return $this->panelHandle;
}
public function isEditMode() {
return $this->editMode;
}
public function setEditMode($mode) {
$this->editMode = $mode;
return $this;
}
/**
* Allow the engine to render the panel via Ajax.
*/
public function setEnableAsyncRendering($enable) {
$this->enableAsyncRendering = $enable;
return $this;
}
public function setParentPanelPHIDs(array $parents) {
$this->parentPanelPHIDs = $parents;
return $this;
}
public function getParentPanelPHIDs() {
return $this->parentPanelPHIDs;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setPanel(PhabricatorDashboardPanel $panel) {
$this->panel = $panel;
return $this;
}
public function setMovable($movable) {
$this->movable = $movable;
return $this;
}
public function getMovable() {
return $this->movable;
}
public function getPanel() {
return $this->panel;
}
public function setPanelPHID($panel_phid) {
$this->panelPHID = $panel_phid;
return $this;
}
public function getPanelPHID() {
return $this->panelPHID;
}
public function renderPanel() {
$panel = $this->getPanel();
if (!$panel) {
$handle = $this->getPanelHandle();
if ($handle->getPolicyFiltered()) {
return $this->renderErrorPanel(
pht('Restricted Panel'),
pht(
'You do not have permission to see this panel.'));
} else {
return $this->renderErrorPanel(
pht('Invalid Panel'),
pht(
'This panel is invalid or does not exist. It may have been '.
'deleted.'));
}
}
$panel_type = $panel->getImplementation();
if (!$panel_type) {
return $this->renderErrorPanel(
$panel->getName(),
pht(
- 'This panel has type "%s", but that panel type is not known to '.
- 'Phabricator.',
+ 'This panel has type "%s", but that panel type is unknown.',
$panel->getPanelType()));
}
try {
$this->detectRenderingCycle($panel);
if ($this->enableAsyncRendering) {
if ($panel_type->shouldRenderAsync()) {
return $this->renderAsyncPanel();
}
}
return $this->renderNormalPanel();
} catch (Exception $ex) {
return $this->renderErrorPanel(
$panel->getName(),
pht(
'%s: %s',
phutil_tag('strong', array(), get_class($ex)),
$ex->getMessage()));
}
}
private function renderNormalPanel() {
$panel = $this->getPanel();
$panel_type = $panel->getImplementation();
$content = $panel_type->renderPanelContent(
$this->getViewer(),
$panel,
$this);
$header = $this->renderPanelHeader();
return $this->renderPanelDiv(
$content,
$header);
}
private function renderAsyncPanel() {
$context_phid = $this->getContextPHID();
$panel = $this->getPanel();
$panel_id = celerity_generate_unique_node_id();
Javelin::initBehavior(
'dashboard-async-panel',
array(
'panelID' => $panel_id,
'parentPanelPHIDs' => $this->getParentPanelPHIDs(),
'headerMode' => $this->getHeaderMode(),
'contextPHID' => $context_phid,
'panelKey' => $this->getPanelKey(),
'movable' => $this->getMovable(),
'uri' => '/dashboard/panel/render/'.$panel->getID().'/',
));
$header = $this->renderPanelHeader();
$content = id(new PHUIPropertyListView())
->addTextContent(pht('Loading...'));
return $this->renderPanelDiv(
$content,
$header,
$panel_id);
}
private function renderErrorPanel($title, $body) {
switch ($this->getHeaderMode()) {
case self::HEADER_MODE_NONE:
$header = null;
break;
case self::HEADER_MODE_EDIT:
$header = id(new PHUIHeaderView())
->setHeader($title);
$header = $this->addPanelHeaderActions($header);
break;
case self::HEADER_MODE_NORMAL:
default:
$header = id(new PHUIHeaderView())
->setHeader($title);
break;
}
$icon = id(new PHUIIconView())
->setIcon('fa-warning red msr');
$content = id(new PHUIBoxView())
->addClass('dashboard-box')
->addMargin(PHUI::MARGIN_LARGE)
->appendChild($icon)
->appendChild($body);
return $this->renderPanelDiv(
$content,
$header);
}
private function renderPanelDiv(
$content,
$header = null,
$id = null) {
require_celerity_resource('phabricator-dashboard-css');
$panel = $this->getPanel();
if (!$id) {
$id = celerity_generate_unique_node_id();
}
$box = new PHUIObjectBoxView();
$interface = 'PhabricatorApplicationSearchResultView';
if ($content instanceof $interface) {
if ($content->getObjectList()) {
$box->setObjectList($content->getObjectList());
}
if ($content->getTable()) {
$box->setTable($content->getTable());
}
if ($content->getContent()) {
$box->appendChild($content->getContent());
}
} else {
$box->appendChild($content);
}
$box
->setHeader($header)
->setID($id)
->addClass('dashboard-box')
->addSigil('dashboard-panel');
if ($this->getMovable()) {
$box->addSigil('panel-movable');
}
if ($panel) {
$box->setMetadata(
array(
'panelKey' => $this->getPanelKey(),
));
}
return $box;
}
private function renderPanelHeader() {
$panel = $this->getPanel();
switch ($this->getHeaderMode()) {
case self::HEADER_MODE_NONE:
$header = null;
break;
case self::HEADER_MODE_EDIT:
// In edit mode, include the panel monogram to make managing boards
// a little easier.
$header_text = pht('%s %s', $panel->getMonogram(), $panel->getName());
$header = id(new PHUIHeaderView())
->setHeader($header_text);
$header = $this->addPanelHeaderActions($header);
break;
case self::HEADER_MODE_NORMAL:
default:
$header = id(new PHUIHeaderView())
->setHeader($panel->getName());
$panel_type = $panel->getImplementation();
$header = $panel_type->adjustPanelHeader(
$this->getViewer(),
$panel,
$this,
$header);
break;
}
return $header;
}
private function addPanelHeaderActions(
PHUIHeaderView $header) {
$viewer = $this->getViewer();
$panel = $this->getPanel();
$context_phid = $this->getContextPHID();
$actions = array();
if ($panel) {
try {
$panel_actions = $panel->newHeaderEditActions(
$viewer,
$context_phid);
} catch (Exception $ex) {
$error_action = id(new PhabricatorActionView())
->setIcon('fa-exclamation-triangle red')
->setName(pht('<Rendering Exception>'));
$panel_actions[] = $error_action;
}
if ($panel_actions) {
foreach ($panel_actions as $panel_action) {
$actions[] = $panel_action;
}
$actions[] = id(new PhabricatorActionView())
->setType(PhabricatorActionView::TYPE_DIVIDER);
}
$panel_id = $panel->getID();
$edit_uri = "/dashboard/panel/edit/{$panel_id}/";
$params = array(
'contextPHID' => $context_phid,
);
$edit_uri = new PhutilURI($edit_uri, $params);
$actions[] = id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setName(pht('Edit Panel'))
->setHref($edit_uri);
$actions[] = id(new PhabricatorActionView())
->setIcon('fa-window-maximize')
->setName(pht('View Panel Details'))
->setHref($panel->getURI());
}
if ($context_phid) {
$panel_phid = $this->getPanelPHID();
$remove_uri = urisprintf('/dashboard/adjust/remove/');
$params = array(
'contextPHID' => $context_phid,
'panelKey' => $this->getPanelKey(),
);
$remove_uri = new PhutilURI($remove_uri, $params);
$actions[] = id(new PhabricatorActionView())
->setIcon('fa-times')
->setHref($remove_uri)
->setName(pht('Remove Panel'))
->setWorkflow(true);
}
$dropdown_menu = id(new PhabricatorActionListView())
->setViewer($viewer);
foreach ($actions as $action) {
$dropdown_menu->addAction($action);
}
$action_menu = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-cog')
->setText(pht('Manage Panel'))
->setDropdownMenu($dropdown_menu);
$header->addActionLink($action_menu);
return $header;
}
/**
* Detect graph cycles in panels, and deeply nested panels.
*
* This method throws if the current rendering stack is too deep or contains
* a cycle. This can happen if you embed layout panels inside each other,
* build a big stack of panels, or embed a panel in remarkup inside another
* panel. Generally, all of this stuff is ridiculous and we just want to
* shut it down.
*
* @param PhabricatorDashboardPanel Panel being rendered.
* @return void
*/
private function detectRenderingCycle(PhabricatorDashboardPanel $panel) {
if ($this->parentPanelPHIDs === null) {
throw new PhutilInvalidStateException('setParentPanelPHIDs');
}
$max_depth = 4;
if (count($this->parentPanelPHIDs) >= $max_depth) {
throw new Exception(
pht(
'To render more than %s levels of panels nested inside other '.
- 'panels, purchase a subscription to Phabricator Gold.',
- new PhutilNumber($max_depth)));
+ 'panels, purchase a subscription to %s Gold.',
+ new PhutilNumber($max_depth),
+ PlatformSymbols::getPlatformServerName()));
}
if (in_array($panel->getPHID(), $this->parentPanelPHIDs)) {
throw new Exception(
pht(
'You awake in a twisting maze of mirrors, all alike. '.
'You are likely to be eaten by a graph cycle. '.
'Should you escape alive, you resolve to be more careful about '.
'putting dashboard panels inside themselves.'));
}
}
private function getContextPHID() {
$context = $this->getContextObject();
if ($context) {
return $context->getPHID();
}
return null;
}
}
diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php b/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php
index 2174f793a4..c8cfe4fd2e 100644
--- a/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php
+++ b/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php
@@ -1,234 +1,234 @@
<?php
final class PhabricatorDashboardQueryPanelType
extends PhabricatorDashboardPanelType {
public function getPanelTypeKey() {
return 'query';
}
public function getPanelTypeName() {
return pht('Query Panel');
}
public function getIcon() {
return 'fa-search';
}
public function getPanelTypeDescription() {
return pht(
'Show results of a search query, like the most recently filed tasks or '.
'revisions you need to review.');
}
protected function newEditEngineFields(PhabricatorDashboardPanel $panel) {
$application_field =
id(new PhabricatorDashboardQueryPanelApplicationEditField())
->setKey('class')
->setLabel(pht('Search For'))
->setTransactionType(
PhabricatorDashboardQueryPanelApplicationTransaction::TRANSACTIONTYPE)
->setValue($panel->getProperty('class', ''));
$application_id = $application_field->getControlID();
$query_field =
id(new PhabricatorDashboardQueryPanelQueryEditField())
->setKey('key')
->setLabel(pht('Query'))
->setApplicationControlID($application_id)
->setTransactionType(
PhabricatorDashboardQueryPanelQueryTransaction::TRANSACTIONTYPE)
->setValue($panel->getProperty('key', ''));
$limit_field = id(new PhabricatorIntEditField())
->setKey('limit')
->setLabel(pht('Limit'))
->setTransactionType(
PhabricatorDashboardQueryPanelLimitTransaction::TRANSACTIONTYPE)
->setValue($panel->getProperty('limit'));
return array(
$application_field,
$query_field,
$limit_field,
);
}
public function renderPanelContent(
PhabricatorUser $viewer,
PhabricatorDashboardPanel $panel,
PhabricatorDashboardPanelRenderingEngine $engine) {
$engine = $this->getSearchEngine($panel);
$engine->setViewer($viewer);
$engine->setContext(PhabricatorApplicationSearchEngine::CONTEXT_PANEL);
$key = $panel->getProperty('key');
if ($engine->isBuiltinQuery($key)) {
$saved = $engine->buildSavedQueryFromBuiltin($key);
} else {
$saved = id(new PhabricatorSavedQueryQuery())
->setViewer($viewer)
->withEngineClassNames(array(get_class($engine)))
->withQueryKeys(array($key))
->executeOne();
}
if (!$saved) {
throw new Exception(
pht(
'Query "%s" is unknown to application search engine "%s"!',
$key,
get_class($engine)));
}
$query = $engine->buildQueryFromSavedQuery($saved);
$pager = $engine->newPagerForSavedQuery($saved);
if ($panel->getProperty('limit')) {
$limit = (int)$panel->getProperty('limit');
if ($pager->getPageSize() !== 0xFFFF) {
$pager->setPageSize($limit);
}
}
$query->setReturnPartialResultsOnOverheat(true);
$results = $engine->executeQuery($query, $pager);
$results_view = $engine->renderResults($results, $saved);
$is_overheated = $query->getIsOverheated();
$overheated_view = null;
if ($is_overheated) {
$content = $results_view->getContent();
$overheated_message =
PhabricatorApplicationSearchController::newOverheatedError(
(bool)$results);
$overheated_warning = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setTitle(pht('Query Overheated'))
->setErrors(
array(
$overheated_message,
));
$overheated_box = id(new PHUIBoxView())
->addClass('mmt mmb')
->appendChild($overheated_warning);
$content = array($content, $overheated_box);
$results_view->setContent($content);
}
// TODO: A small number of queries, including "Notifications" and "Search",
// use an offset pager which has a slightly different API. Some day, we
// should unify these.
if ($pager instanceof PHUIPagerView) {
$has_more = $pager->getHasMorePages();
} else {
$has_more = $pager->getHasMoreResults();
}
if ($has_more) {
$item_list = $results_view->getObjectList();
$more_href = $engine->getQueryResultsPageURI($key);
if ($item_list) {
$item_list->newTailButton()
->setHref($more_href);
} else {
// For search engines that do not return an object list, add a fake
// one to the end so we can render a "View All Results" button that
// looks like it does in normal applications. At time of writing,
// several major applications like Maniphest (which has group headers)
// and Feed (which uses custom rendering) don't return simple lists.
$content = $results_view->getContent();
$more_list = id(new PHUIObjectItemListView())
->setAllowEmptyList(true);
$more_list->newTailButton()
->setHref($more_href);
$content = array($content, $more_list);
$results_view->setContent($content);
}
}
return $results_view;
}
public function adjustPanelHeader(
PhabricatorUser $viewer,
PhabricatorDashboardPanel $panel,
PhabricatorDashboardPanelRenderingEngine $engine,
PHUIHeaderView $header) {
$search_engine = $this->getSearchEngine($panel);
$key = $panel->getProperty('key');
$href = $search_engine->getQueryResultsPageURI($key);
$icon = id(new PHUIIconView())
->setIcon('fa-search');
$button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('View All'))
->setIcon($icon)
->setHref($href)
->setColor(PHUIButtonView::GREY);
$header->addActionLink($button);
return $header;
}
private function getSearchEngine(PhabricatorDashboardPanel $panel) {
$class = $panel->getProperty('class');
$engine = PhabricatorApplicationSearchEngine::getEngineByClassName($class);
if (!$engine) {
throw new Exception(
pht(
- 'The application search engine "%s" is not known to Phabricator!',
+ 'The application search engine "%s" is unknown.',
$class));
}
if (!$engine->canUseInPanelContext()) {
throw new Exception(
pht(
'Application search engines of class "%s" can not be used to build '.
'dashboard panels.',
$class));
}
return $engine;
}
public function newHeaderEditActions(
PhabricatorDashboardPanel $panel,
PhabricatorUser $viewer,
$context_phid) {
$actions = array();
$engine = $this->getSearchEngine($panel);
$customize_uri = $engine->getCustomizeURI(
$panel->getProperty('key'),
$panel->getPHID(),
$context_phid);
$actions[] = id(new PhabricatorActionView())
->setIcon('fa-pencil-square-o')
->setName(pht('Customize Query'))
->setWorkflow(true)
->setHref($customize_uri);
return $actions;
}
}
diff --git a/src/applications/dashboard/query/PhabricatorDashboardPanelQuery.php b/src/applications/dashboard/query/PhabricatorDashboardPanelQuery.php
index dd32d1c9dc..c67b756262 100644
--- a/src/applications/dashboard/query/PhabricatorDashboardPanelQuery.php
+++ b/src/applications/dashboard/query/PhabricatorDashboardPanelQuery.php
@@ -1,102 +1,98 @@
<?php
final class PhabricatorDashboardPanelQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $archived;
private $panelTypes;
private $authorPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withArchived($archived) {
$this->archived = $archived;
return $this;
}
public function withPanelTypes(array $types) {
$this->panelTypes = $types;
return $this;
}
public function withAuthorPHIDs(array $authors) {
$this->authorPHIDs = $authors;
return $this;
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
public function newResultObject() {
// TODO: If we don't do this, SearchEngine explodes when trying to
// enumerate custom fields. For now, just give the panel a default panel
// type so custom fields work. In the long run, we may want to find a
// cleaner or more general approach for this.
$text_type = id(new PhabricatorDashboardTextPanelType())
->getPanelTypeKey();
return id(new PhabricatorDashboardPanel())
->setPanelType($text_type);
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'panel.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'panel.phid IN (%Ls)',
$this->phids);
}
if ($this->archived !== null) {
$where[] = qsprintf(
$conn,
'panel.isArchived = %d',
(int)$this->archived);
}
if ($this->panelTypes !== null) {
$where[] = qsprintf(
$conn,
'panel.panelType IN (%Ls)',
$this->panelTypes);
}
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'panel.authorPHID IN (%Ls)',
$this->authorPHIDs);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorDashboardApplication';
}
protected function getPrimaryTableAlias() {
return 'panel';
}
}
diff --git a/src/applications/dashboard/query/PhabricatorDashboardPortalQuery.php b/src/applications/dashboard/query/PhabricatorDashboardPortalQuery.php
index 857c4dc215..418262c745 100644
--- a/src/applications/dashboard/query/PhabricatorDashboardPortalQuery.php
+++ b/src/applications/dashboard/query/PhabricatorDashboardPortalQuery.php
@@ -1,68 +1,64 @@
<?php
final class PhabricatorDashboardPortalQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $statuses;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function newResultObject() {
return new PhabricatorDashboardPortal();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'portal.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'portal.phid IN (%Ls)',
$this->phids);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'portal.status IN (%Ls)',
$this->statuses);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorDashboardApplication';
}
protected function getPrimaryTableAlias() {
return 'portal';
}
}
diff --git a/src/applications/dashboard/query/PhabricatorDashboardQuery.php b/src/applications/dashboard/query/PhabricatorDashboardQuery.php
index 76854bffc1..954a565ac6 100644
--- a/src/applications/dashboard/query/PhabricatorDashboardQuery.php
+++ b/src/applications/dashboard/query/PhabricatorDashboardQuery.php
@@ -1,103 +1,99 @@
<?php
final class PhabricatorDashboardQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $statuses;
private $authorPHIDs;
private $canEdit;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function withAuthorPHIDs(array $authors) {
$this->authorPHIDs = $authors;
return $this;
}
public function withCanEdit($can_edit) {
$this->canEdit = $can_edit;
return $this;
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
public function newResultObject() {
return new PhabricatorDashboard();
}
protected function didFilterPage(array $dashboards) {
$phids = mpull($dashboards, 'getPHID');
if ($this->canEdit) {
$dashboards = id(new PhabricatorPolicyFilter())
->setViewer($this->getViewer())
->requireCapabilities(array(
PhabricatorPolicyCapability::CAN_EDIT,
))
->apply($dashboards);
}
return $dashboards;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'dashboard.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'dashboard.phid IN (%Ls)',
$this->phids);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'dashboard.status IN (%Ls)',
$this->statuses);
}
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'dashboard.authorPHID IN (%Ls)',
$this->authorPHIDs);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorDashboardApplication';
}
protected function getPrimaryTableAlias() {
return 'dashboard';
}
}
diff --git a/src/applications/dashboard/storage/PhabricatorDashboardPanel.php b/src/applications/dashboard/storage/PhabricatorDashboardPanel.php
index 4173b208ba..61cc2bbdf2 100644
--- a/src/applications/dashboard/storage/PhabricatorDashboardPanel.php
+++ b/src/applications/dashboard/storage/PhabricatorDashboardPanel.php
@@ -1,185 +1,184 @@
<?php
/**
* An individual dashboard panel.
*/
final class PhabricatorDashboardPanel
extends PhabricatorDashboardDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface,
PhabricatorFlaggableInterface,
PhabricatorDestructibleInterface,
PhabricatorFulltextInterface,
PhabricatorFerretInterface,
PhabricatorDashboardPanelContainerInterface {
protected $name;
protected $panelType;
protected $viewPolicy;
protected $editPolicy;
protected $authorPHID;
protected $isArchived = 0;
protected $properties = array();
public static function initializeNewPanel(PhabricatorUser $actor) {
return id(new PhabricatorDashboardPanel())
->setName('')
->setAuthorPHID($actor->getPHID())
->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy())
->setEditPolicy($actor->getPHID());
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'properties' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'sort255',
'panelType' => 'text64',
'authorPHID' => 'phid',
'isArchived' => 'bool',
),
) + parent::getConfiguration();
}
public function getPHIDType() {
return PhabricatorDashboardPanelPHIDType::TYPECONST;
}
public function getProperty($key, $default = null) {
return idx($this->properties, $key, $default);
}
public function setProperty($key, $value) {
$this->properties[$key] = $value;
return $this;
}
public function getMonogram() {
return 'W'.$this->getID();
}
public function getURI() {
return '/'.$this->getMonogram();
}
public function getPanelTypes() {
$panel_types = PhabricatorDashboardPanelType::getAllPanelTypes();
$panel_types = mpull($panel_types, 'getPanelTypeName', 'getPanelTypeKey');
asort($panel_types);
$panel_types = (array('' => pht('(All Types)')) + $panel_types);
return $panel_types;
}
public function getStatuses() {
$statuses =
array(
'' => pht('(All Panels)'),
'active' => pht('Active Panels'),
'archived' => pht('Archived Panels'),
);
return $statuses;
}
public function getImplementation() {
return idx(
PhabricatorDashboardPanelType::getAllPanelTypes(),
$this->getPanelType());
}
public function requireImplementation() {
$impl = $this->getImplementation();
if (!$impl) {
throw new Exception(
pht(
'Attempting to use a panel in a way that requires an '.
- 'implementation, but the panel implementation ("%s") is unknown to '.
- 'Phabricator.',
+ 'implementation, but the panel implementation ("%s") is unknown.',
$this->getPanelType()));
}
return $impl;
}
public function getEditEngineFields() {
return $this->requireImplementation()->getEditEngineFields($this);
}
public function newHeaderEditActions(
PhabricatorUser $viewer,
$context_phid) {
return $this->requireImplementation()->newHeaderEditActions(
$this,
$viewer,
$context_phid);
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorDashboardPanelTransactionEditor();
}
public function getApplicationTransactionTemplate() {
return new PhabricatorDashboardPanelTransaction();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$this->delete();
$this->saveTransaction();
}
/* -( PhabricatorDashboardPanelContainerInterface )------------------------ */
public function getDashboardPanelContainerPanelPHIDs() {
return $this->requireImplementation()->getSubpanelPHIDs($this);
}
/* -( PhabricatorFulltextInterface )--------------------------------------- */
public function newFulltextEngine() {
return new PhabricatorDashboardPanelFulltextEngine();
}
/* -( PhabricatorFerretInterface )----------------------------------------- */
public function newFerretEngine() {
return new PhabricatorDashboardPanelFerretEngine();
}
}
diff --git a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php
index 9634756dac..8c33bd5fad 100644
--- a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php
+++ b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php
@@ -1,254 +1,254 @@
<?php
final class PhabricatorDifferentialConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Differential');
}
public function getDescription() {
return pht('Configure Differential code review.');
}
public function getIcon() {
return 'fa-cog';
}
public function getGroup() {
return 'apps';
}
public function getOptions() {
$caches_href = PhabricatorEnv::getDoclink('Managing Caches');
$custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType';
$fields = array(
new DifferentialSummaryField(),
new DifferentialTestPlanField(),
new DifferentialReviewersField(),
new DifferentialProjectReviewersField(),
new DifferentialRepositoryField(),
new DifferentialManiphestTasksField(),
new DifferentialCommitsField(),
new DifferentialJIRAIssuesField(),
new DifferentialAsanaRepresentationField(),
new DifferentialChangesSinceLastUpdateField(),
new DifferentialBranchField(),
new DifferentialBlameRevisionField(),
new DifferentialPathField(),
new DifferentialHostField(),
new DifferentialLintField(),
new DifferentialUnitField(),
new DifferentialRevertPlanField(),
);
$default_fields = array();
foreach ($fields as $field) {
$default_fields[$field->getFieldKey()] = array(
'disabled' => $field->shouldDisableByDefault(),
);
}
$inline_description = $this->deformat(
pht(<<<EOHELP
To include patches inline in email bodies, set this option to a positive
integer. Patches will be inlined if they are at most that many lines and at
most 256 times that many bytes.
For example, a value of 100 means "inline patches if they are at not more than
100 lines long and not more than 25,600 bytes large".
By default, patches are not inlined.
EOHELP
));
return array(
$this->newOption(
'differential.fields',
$custom_field_type,
$default_fields)
->setCustomData(
id(new DifferentialRevision())->getCustomFieldBaseClass())
->setDescription(
pht(
"Select and reorder revision fields.\n\n".
"NOTE: This feature is under active development and subject ".
"to change.")),
$this->newOption('differential.require-test-plan-field', 'bool', true)
->setBoolOptions(
array(
pht("Require 'Test Plan' field"),
pht("Make 'Test Plan' field optional"),
))
->setSummary(pht('Require "Test Plan" field?'))
->setDescription(
pht(
"Differential has a required 'Test Plan' field by default. You ".
"can make it optional by setting this to false. You can also ".
"completely remove it above, if you prefer.")),
$this->newOption('differential.enable-email-accept', 'bool', false)
->setBoolOptions(
array(
pht('Enable Email "!accept" Action'),
pht('Disable Email "!accept" Action'),
))
->setSummary(pht('Enable or disable "!accept" action via email.'))
->setDescription(
pht(
'If inbound email is configured, users can interact with '.
'revisions by using "!actions" in email replies (for example, '.
'"!resign" or "!rethink"). However, by default, users may not '.
'"!accept" revisions via email: email authentication can be '.
'configured to be very weak, and email "!accept" is kind of '.
'sketchy and implies the revision may not actually be receiving '.
'thorough review. You can enable "!accept" by setting this '.
'option to true.')),
$this->newOption('differential.generated-paths', 'list<regex>', array())
->setSummary(pht('File regexps to treat as automatically generated.'))
->setDescription(
pht(
'List of file regexps that should be treated as if they are '.
'generated by an automatic process, and thus be hidden by '.
'default in Differential.'.
"\n\n".
'NOTE: This property is cached, so you will need to purge the '.
'cache after making changes if you want the new configuration '.
'to affect existing revisions. For instructions, see '.
'**[[ %s | Managing Caches ]]** in the documentation.',
$caches_href))
->addExample("/config\.h$/\n#(^|/)autobuilt/#", pht('Valid Setting')),
$this->newOption('differential.sticky-accept', 'bool', true)
->setBoolOptions(
array(
pht('Accepts persist across updates'),
pht('Accepts are reset by updates'),
))
->setSummary(
pht('Should "Accepted" revisions remain "Accepted" after updates?'))
->setDescription(
pht(
'Normally, when revisions that have been "Accepted" are updated, '.
'they remain "Accepted". This allows reviewers to suggest minor '.
'alterations when accepting, and encourages authors to update '.
'if they make minor changes in response to this feedback.'.
"\n\n".
'If you want updates to always require re-review, you can disable '.
'the "stickiness" of the "Accepted" status with this option. '.
'This may make the process for minor changes much more burdensome '.
'to both authors and reviewers.')),
$this->newOption('differential.allow-self-accept', 'bool', false)
->setBoolOptions(
array(
pht('Allow self-accept'),
pht('Disallow self-accept'),
))
->setSummary(pht('Allows users to accept their own revisions.'))
->setDescription(
pht(
"If you set this to true, users can accept their own revisions. ".
"This action is disabled by default because it's most likely not ".
"a behavior you want, but it proves useful if you are working ".
"alone on a project and want to make use of all of ".
"differential's features.")),
$this->newOption('differential.always-allow-close', 'bool', false)
->setBoolOptions(
array(
pht('Allow any user'),
pht('Restrict to submitter'),
))
->setSummary(pht('Allows any user to close accepted revisions.'))
->setDescription(
pht(
'If you set this to true, any user can close any revision so '.
'long as it has been accepted. This can be useful depending on '.
'your development model. For example, github-style pull requests '.
'where the reviewer is often the actual committer can benefit '.
'from turning this option to true. If false, only the submitter '.
'can close a revision.')),
$this->newOption('differential.always-allow-abandon', 'bool', false)
->setBoolOptions(
array(
pht('Allow any user'),
pht('Restrict to submitter'),
))
->setSummary(pht('Allows any user to abandon revisions.'))
->setDescription(
pht(
'If you set this to true, any user can abandon any revision. If '.
'false, only the submitter can abandon a revision.')),
$this->newOption('differential.allow-reopen', 'bool', false)
->setBoolOptions(
array(
pht('Enable reopen'),
pht('Disable reopen'),
))
->setSummary(pht('Allows any user to reopen a closed revision.'))
->setDescription(
pht(
'If you set this to true, any user can reopen a revision so '.
'long as it has been closed. This can be useful if a revision '.
'is accidentally closed or if a developer changes his or her '.
'mind after closing a revision. If it is false, reopening '.
'is not allowed.')),
$this->newOption('differential.close-on-accept', 'bool', false)
->setBoolOptions(
array(
pht('Treat Accepted Revisions as "Closed"'),
pht('Treat Accepted Revisions as "Open"'),
))
->setSummary(pht('Allows "Accepted" to act as a closed status.'))
->setDescription(
pht(
'Normally, Differential revisions remain on the dashboard when '.
'they are "Accepted", and the author then commits the changes '.
'to "Close" the revision and move it off the dashboard.'.
"\n\n".
'If you have an unusual workflow where Differential is used for '.
- 'post-commit review (normally called "Audit", elsewhere in '.
- 'Phabricator), you can set this flag to treat the "Accepted" '.
+ 'post-commit review (normally called "Audit", elsewhere), you '.
+ 'can set this flag to treat the "Accepted" '.
'state as a "Closed" state and end the review workflow early.'.
"\n\n".
'This sort of workflow is very unusual. Very few installs should '.
'need to change this option.')),
$this->newOption(
'metamta.differential.attach-patches',
'bool',
false)
->setBoolOptions(
array(
pht('Attach Patches'),
pht('Do Not Attach Patches'),
))
->setSummary(pht('Attach patches to email, as text attachments.'))
->setDescription(
pht(
- 'If you set this to true, Phabricator will attach patches to '.
+ 'If you set this to true, patches will be attached to '.
'Differential mail (as text attachments). This will not work if '.
'you are using SendGrid as your mail adapter.')),
$this->newOption(
'metamta.differential.inline-patches',
'int',
0)
->setSummary(pht('Inline patches in email, as body text.'))
->setDescription($inline_description),
$this->newOption(
'metamta.differential.patch-format',
'enum',
'unified')
->setDescription(
pht('Format for inlined or attached patches.'))
->setEnumOptions(
array(
'unified' => pht('Unified'),
'git' => pht('Git'),
)),
);
}
}
diff --git a/src/applications/differential/controller/DifferentialDiffCreateController.php b/src/applications/differential/controller/DifferentialDiffCreateController.php
index f9d1006555..c3bfc01743 100644
--- a/src/applications/differential/controller/DifferentialDiffCreateController.php
+++ b/src/applications/differential/controller/DifferentialDiffCreateController.php
@@ -1,218 +1,219 @@
<?php
final class DifferentialDiffCreateController extends DifferentialController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
// If we're on the "Update Diff" workflow, load the revision we're going
// to update.
$revision = null;
$revision_id = $request->getURIData('revisionID');
if ($revision_id) {
$revision = id(new DifferentialRevisionQuery())
->setViewer($viewer)
->withIDs(array($revision_id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$revision) {
return new Aphront404Response();
}
}
$diff = null;
// This object is just for policy stuff
$diff_object = DifferentialDiff::initializeNewDiff($viewer);
if ($revision) {
$repository_phid = $revision->getRepositoryPHID();
} else {
$repository_phid = null;
}
$errors = array();
$e_diff = null;
$e_file = null;
$validation_exception = null;
if ($request->isFormPost()) {
$repository_tokenizer = $request->getArr(
id(new DifferentialRepositoryField())->getFieldKey());
if ($repository_tokenizer) {
$repository_phid = reset($repository_tokenizer);
}
if ($request->getFileExists('diff-file')) {
$diff = PhabricatorFile::readUploadedFileData($_FILES['diff-file']);
} else {
$diff = $request->getStr('diff');
}
if (!strlen($diff)) {
$errors[] = pht(
'You can not create an empty diff. Paste a diff or upload a '.
'file containing a diff.');
$e_diff = pht('Required');
$e_file = pht('Required');
}
if (!$errors) {
try {
$call = new ConduitCall(
'differential.createrawdiff',
array(
'diff' => $diff,
'repositoryPHID' => $repository_phid,
'viewPolicy' => $request->getStr('viewPolicy'),
));
$call->setUser($viewer);
$result = $call->execute();
$diff_id = $result['id'];
$uri = $this->getApplicationURI("diff/{$diff_id}/");
$uri = new PhutilURI($uri);
if ($revision) {
$uri->replaceQueryParam('revisionID', $revision->getID());
}
return id(new AphrontRedirectResponse())->setURI($uri);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
}
}
}
$form = new AphrontFormView();
$arcanist_href = PhabricatorEnv::getDoclink('Arcanist User Guide');
$arcanist_link = phutil_tag(
'a',
array(
'href' => $arcanist_href,
'target' => '_blank',
),
pht('Learn More'));
$cancel_uri = $this->getApplicationURI();
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($diff_object)
->execute();
$info_view = null;
if (!$request->isFormPost()) {
$info_view = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->setErrors(
array(
array(
pht(
- 'The best way to create a diff is to use the Arcanist '.
- 'command-line tool.'),
+ 'The best way to create a diff is to use the %s '.
+ 'command-line tool.',
+ PlatformSymbols::getPlatformClientName()),
' ',
$arcanist_link,
),
pht(
'You can also paste a diff above, or upload a file '.
'containing a diff (for example, from %s, %s or %s).',
phutil_tag('tt', array(), 'svn diff'),
phutil_tag('tt', array(), 'git diff'),
phutil_tag('tt', array(), 'hg diff --git')),
));
}
if ($revision) {
$title = pht('Update Diff');
$header = pht('Update Diff');
$button = pht('Continue');
$header_icon = 'fa-upload';
} else {
$title = pht('Create Diff');
$header = pht('Create New Diff');
$button = pht('Create Diff');
$header_icon = 'fa-plus-square';
}
$form
->setEncType('multipart/form-data')
->setUser($viewer);
if ($revision) {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Updating Revision'))
->setValue($viewer->renderHandle($revision->getPHID())));
}
if ($repository_phid) {
$repository_value = array($repository_phid);
} else {
$repository_value = array();
}
$form
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Raw Diff'))
->setName('diff')
->setValue($diff)
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setError($e_diff))
->appendChild(
id(new AphrontFormFileControl())
->setLabel(pht('Raw Diff From File'))
->setName('diff-file')
->setError($e_file))
->appendControl(
id(new AphrontFormTokenizerControl())
->setName(id(new DifferentialRepositoryField())->getFieldKey())
->setLabel(pht('Repository'))
->setDatasource(new DiffusionRepositoryDatasource())
->setValue($repository_value)
->setLimit(1))
->appendChild(
id(new AphrontFormPolicyControl())
->setUser($viewer)
->setName('viewPolicy')
->setPolicyObject($diff_object)
->setPolicies($policies)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue($button));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setValidationException($validation_exception)
->setForm($form)
->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->setFormErrors($errors);
$crumbs = $this->buildApplicationCrumbs();
if ($revision) {
$crumbs->addTextCrumb(
$revision->getMonogram(),
'/'.$revision->getMonogram());
}
$crumbs->addTextCrumb($title);
$crumbs->setBorder(true);
$view = id(new PHUITwoColumnView())
->setFooter(array(
$form_box,
$info_view,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
}
diff --git a/src/applications/differential/customfield/DifferentialJIRAIssuesField.php b/src/applications/differential/customfield/DifferentialJIRAIssuesField.php
index e97efd6ad5..8bd0b052dd 100644
--- a/src/applications/differential/customfield/DifferentialJIRAIssuesField.php
+++ b/src/applications/differential/customfield/DifferentialJIRAIssuesField.php
@@ -1,285 +1,286 @@
<?php
final class DifferentialJIRAIssuesField
extends DifferentialStoredCustomField {
private $error;
public function getFieldKey() {
return 'phabricator:jira-issues';
}
public function getFieldKeyForConduit() {
return 'jira.issues';
}
public function isFieldEnabled() {
return (bool)PhabricatorJIRAAuthProvider::getJIRAProvider();
}
public function canDisableField() {
return false;
}
public function getValueForStorage() {
return json_encode($this->getValue());
}
public function setValueFromStorage($value) {
try {
$this->setValue(phutil_json_decode($value));
} catch (PhutilJSONParserException $ex) {
$this->setValue(array());
}
return $this;
}
public function getFieldName() {
return pht('JIRA Issues');
}
public function getFieldDescription() {
return pht('Lists associated JIRA issues.');
}
public function shouldAppearInPropertyView() {
return true;
}
public function renderPropertyViewLabel() {
return $this->getFieldName();
}
public function renderPropertyViewValue(array $handles) {
$xobjs = $this->loadDoorkeeperExternalObjects($this->getValue());
if (!$xobjs) {
return null;
}
$links = array();
foreach ($xobjs as $xobj) {
$links[] = id(new DoorkeeperTagView())
->setExternalObject($xobj);
}
return phutil_implode_html(phutil_tag('br'), $links);
}
private function buildDoorkeeperRefs($value) {
$provider = PhabricatorJIRAAuthProvider::getJIRAProvider();
$refs = array();
if ($value) {
foreach ($value as $jira_key) {
$refs[] = id(new DoorkeeperObjectRef())
->setApplicationType(DoorkeeperBridgeJIRA::APPTYPE_JIRA)
->setApplicationDomain($provider->getProviderDomain())
->setObjectType(DoorkeeperBridgeJIRA::OBJTYPE_ISSUE)
->setObjectID($jira_key);
}
}
return $refs;
}
private function loadDoorkeeperExternalObjects($value) {
$refs = $this->buildDoorkeeperRefs($value);
if (!$refs) {
return array();
}
$xobjs = id(new DoorkeeperExternalObjectQuery())
->setViewer($this->getViewer())
->withObjectKeys(mpull($refs, 'getObjectKey'))
->execute();
return $xobjs;
}
public function shouldAppearInEditView() {
return PhabricatorJIRAAuthProvider::getJIRAProvider();
}
public function shouldAppearInApplicationTransactions() {
return PhabricatorJIRAAuthProvider::getJIRAProvider();
}
public function readValueFromRequest(AphrontRequest $request) {
$this->setValue($request->getStrList($this->getFieldKey()));
return $this;
}
public function renderEditControl(array $handles) {
return id(new AphrontFormTextControl())
->setLabel(pht('JIRA Issues'))
->setCaption(
pht('Example: %s', phutil_tag('tt', array(), 'JIS-3, JIS-9')))
->setName($this->getFieldKey())
->setValue(implode(', ', nonempty($this->getValue(), array())))
->setError($this->error);
}
public function getOldValueForApplicationTransactions() {
return array_unique(nonempty($this->getValue(), array()));
}
public function getNewValueForApplicationTransactions() {
return array_unique(nonempty($this->getValue(), array()));
}
public function validateApplicationTransactions(
PhabricatorApplicationTransactionEditor $editor,
$type,
array $xactions) {
$this->error = null;
$errors = parent::validateApplicationTransactions(
$editor,
$type,
$xactions);
$transaction = null;
foreach ($xactions as $xaction) {
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$add = array_diff($new, $old);
if (!$add) {
continue;
}
// Only check that the actor can see newly added JIRA refs. You're
// allowed to remove refs or make no-op changes even if you aren't
// linked to JIRA.
try {
$refs = id(new DoorkeeperImportEngine())
->setViewer($this->getViewer())
->setRefs($this->buildDoorkeeperRefs($add))
->setThrowOnMissingLink(true)
->execute();
} catch (DoorkeeperMissingLinkException $ex) {
$this->error = pht('Not Linked');
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Not Linked'),
pht(
'You can not add JIRA issues (%s) to this revision because your '.
- 'Phabricator account is not linked to a JIRA account.',
- implode(', ', $add)),
+ '%s account is not linked to a JIRA account.',
+ implode(', ', $add),
+ PlatformSymbols::getPlatformServerName()),
$xaction);
continue;
}
$bad = array();
foreach ($refs as $ref) {
if (!$ref->getIsVisible()) {
$bad[] = $ref->getObjectID();
}
}
if ($bad) {
$bad = implode(', ', $bad);
$this->error = pht('Invalid');
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
'Some JIRA issues could not be loaded. They may not exist, or '.
'you may not have permission to view them: %s',
$bad),
$xaction);
}
}
return $errors;
}
public function getApplicationTransactionTitle(
PhabricatorApplicationTransaction $xaction) {
$old = $xaction->getOldValue();
if (!is_array($old)) {
$old = array();
}
$new = $xaction->getNewValue();
if (!is_array($new)) {
$new = array();
}
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
$author_phid = $xaction->getAuthorPHID();
if ($add && $rem) {
return pht(
'%s updated JIRA issue(s): added %d %s; removed %d %s.',
$xaction->renderHandleLink($author_phid),
phutil_count($add),
implode(', ', $add),
phutil_count($rem),
implode(', ', $rem));
} else if ($add) {
return pht(
'%s added %d JIRA issue(s): %s.',
$xaction->renderHandleLink($author_phid),
phutil_count($add),
implode(', ', $add));
} else if ($rem) {
return pht(
'%s removed %d JIRA issue(s): %s.',
$xaction->renderHandleLink($author_phid),
phutil_count($rem),
implode(', ', $rem));
}
return parent::getApplicationTransactionTitle($xaction);
}
public function applyApplicationTransactionExternalEffects(
PhabricatorApplicationTransaction $xaction) {
// Update the CustomField storage.
parent::applyApplicationTransactionExternalEffects($xaction);
// Now, synchronize the Doorkeeper edges.
$revision = $this->getObject();
$revision_phid = $revision->getPHID();
$edge_type = PhabricatorJiraIssueHasObjectEdgeType::EDGECONST;
$xobjs = $this->loadDoorkeeperExternalObjects($xaction->getNewValue());
$edge_dsts = mpull($xobjs, 'getPHID');
$edges = PhabricatorEdgeQuery::loadDestinationPHIDs(
$revision_phid,
$edge_type);
$editor = new PhabricatorEdgeEditor();
foreach (array_diff($edges, $edge_dsts) as $rem_edge) {
$editor->removeEdge($revision_phid, $edge_type, $rem_edge);
}
foreach (array_diff($edge_dsts, $edges) as $add_edge) {
$editor->addEdge($revision_phid, $edge_type, $add_edge);
}
$editor->save();
}
public function shouldAppearInConduitDictionary() {
return true;
}
public function shouldAppearInConduitTransactions() {
return true;
}
protected function newConduitEditParameterType() {
return new ConduitStringListParameterType();
}
}
diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php
index ad62b2a1ef..5b39269bdd 100644
--- a/src/applications/differential/parser/DifferentialChangesetParser.php
+++ b/src/applications/differential/parser/DifferentialChangesetParser.php
@@ -1,1975 +1,1975 @@
<?php
final class DifferentialChangesetParser extends Phobject {
const HIGHLIGHT_BYTE_LIMIT = 262144;
protected $visible = array();
protected $new = array();
protected $old = array();
protected $intra = array();
protected $depthOnlyLines = array();
protected $newRender = null;
protected $oldRender = null;
protected $filename = null;
protected $hunkStartLines = array();
protected $comments = array();
protected $specialAttributes = array();
protected $changeset;
protected $renderCacheKey = null;
private $handles = array();
private $user;
private $leftSideChangesetID;
private $leftSideAttachesToNewFile;
private $rightSideChangesetID;
private $rightSideAttachesToNewFile;
private $originalLeft;
private $originalRight;
private $renderingReference;
private $isSubparser;
private $isTopLevel;
private $coverage;
private $markupEngine;
private $highlightErrors;
private $disableCache;
private $renderer;
private $highlightingDisabled;
private $showEditAndReplyLinks = true;
private $canMarkDone;
private $objectOwnerPHID;
private $offsetMode;
private $rangeStart;
private $rangeEnd;
private $mask;
private $linesOfContext = 8;
private $highlightEngine;
private $viewer;
private $viewState;
private $availableDocumentEngines;
public function setRange($start, $end) {
$this->rangeStart = $start;
$this->rangeEnd = $end;
return $this;
}
public function setMask(array $mask) {
$this->mask = $mask;
return $this;
}
public function renderChangeset() {
return $this->render($this->rangeStart, $this->rangeEnd, $this->mask);
}
public function setShowEditAndReplyLinks($bool) {
$this->showEditAndReplyLinks = $bool;
return $this;
}
public function getShowEditAndReplyLinks() {
return $this->showEditAndReplyLinks;
}
public function setViewState(PhabricatorChangesetViewState $view_state) {
$this->viewState = $view_state;
return $this;
}
public function getViewState() {
return $this->viewState;
}
public function setRenderer(DifferentialChangesetRenderer $renderer) {
$this->renderer = $renderer;
return $this;
}
public function getRenderer() {
return $this->renderer;
}
public function setDisableCache($disable_cache) {
$this->disableCache = $disable_cache;
return $this;
}
public function getDisableCache() {
return $this->disableCache;
}
public function setCanMarkDone($can_mark_done) {
$this->canMarkDone = $can_mark_done;
return $this;
}
public function getCanMarkDone() {
return $this->canMarkDone;
}
public function setObjectOwnerPHID($phid) {
$this->objectOwnerPHID = $phid;
return $this;
}
public function getObjectOwnerPHID() {
return $this->objectOwnerPHID;
}
public function setOffsetMode($offset_mode) {
$this->offsetMode = $offset_mode;
return $this;
}
public function getOffsetMode() {
return $this->offsetMode;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
private function newRenderer() {
$viewer = $this->getViewer();
$viewstate = $this->getViewstate();
$renderer_key = $viewstate->getRendererKey();
if ($renderer_key === null) {
$is_unified = $viewer->compareUserSetting(
PhabricatorUnifiedDiffsSetting::SETTINGKEY,
PhabricatorUnifiedDiffsSetting::VALUE_ALWAYS_UNIFIED);
if ($is_unified) {
$renderer_key = '1up';
} else {
$renderer_key = $viewstate->getDefaultDeviceRendererKey();
}
}
switch ($renderer_key) {
case '1up':
$renderer = new DifferentialChangesetOneUpRenderer();
break;
default:
$renderer = new DifferentialChangesetTwoUpRenderer();
break;
}
return $renderer;
}
const CACHE_VERSION = 14;
const CACHE_MAX_SIZE = 8e6;
const ATTR_GENERATED = 'attr:generated';
const ATTR_DELETED = 'attr:deleted';
const ATTR_UNCHANGED = 'attr:unchanged';
const ATTR_MOVEAWAY = 'attr:moveaway';
public function setOldLines(array $lines) {
$this->old = $lines;
return $this;
}
public function setNewLines(array $lines) {
$this->new = $lines;
return $this;
}
public function setSpecialAttributes(array $attributes) {
$this->specialAttributes = $attributes;
return $this;
}
public function setIntraLineDiffs(array $diffs) {
$this->intra = $diffs;
return $this;
}
public function setDepthOnlyLines(array $lines) {
$this->depthOnlyLines = $lines;
return $this;
}
public function getDepthOnlyLines() {
return $this->depthOnlyLines;
}
public function setVisibleLinesMask(array $mask) {
$this->visible = $mask;
return $this;
}
public function setLinesOfContext($lines_of_context) {
$this->linesOfContext = $lines_of_context;
return $this;
}
public function getLinesOfContext() {
return $this->linesOfContext;
}
/**
* Configure which Changeset comments added to the right side of the visible
* diff will be attached to. The ID must be the ID of a real Differential
* Changeset.
*
* The complexity here is that we may show an arbitrary side of an arbitrary
* changeset as either the left or right part of a diff. This method allows
* the left and right halves of the displayed diff to be correctly mapped to
* storage changesets.
*
* @param id The Differential Changeset ID that comments added to the right
* side of the visible diff should be attached to.
* @param bool If true, attach new comments to the right side of the storage
* changeset. Note that this may be false, if the left side of
* some storage changeset is being shown as the right side of
* a display diff.
* @return this
*/
public function setRightSideCommentMapping($id, $is_new) {
$this->rightSideChangesetID = $id;
$this->rightSideAttachesToNewFile = $is_new;
return $this;
}
/**
* See setRightSideCommentMapping(), but this sets information for the left
* side of the display diff.
*/
public function setLeftSideCommentMapping($id, $is_new) {
$this->leftSideChangesetID = $id;
$this->leftSideAttachesToNewFile = $is_new;
return $this;
}
public function setOriginals(
DifferentialChangeset $left,
DifferentialChangeset $right) {
$this->originalLeft = $left;
$this->originalRight = $right;
return $this;
}
public function diffOriginals() {
$engine = new PhabricatorDifferenceEngine();
$changeset = $engine->generateChangesetFromFileContent(
implode('', mpull($this->originalLeft->getHunks(), 'getChanges')),
implode('', mpull($this->originalRight->getHunks(), 'getChanges')));
$parser = new DifferentialHunkParser();
return $parser->parseHunksForHighlightMasks(
$changeset->getHunks(),
$this->originalLeft->getHunks(),
$this->originalRight->getHunks());
}
/**
* Set a key for identifying this changeset in the render cache. If set, the
* parser will attempt to use the changeset render cache, which can improve
* performance for frequently-viewed changesets.
*
* By default, there is no render cache key and parsers do not use the cache.
* This is appropriate for rarely-viewed changesets.
*
* @param string Key for identifying this changeset in the render cache.
* @return this
*/
public function setRenderCacheKey($key) {
$this->renderCacheKey = $key;
return $this;
}
private function getRenderCacheKey() {
return $this->renderCacheKey;
}
public function setChangeset(DifferentialChangeset $changeset) {
$this->changeset = $changeset;
$this->setFilename($changeset->getFilename());
return $this;
}
public function setRenderingReference($ref) {
$this->renderingReference = $ref;
return $this;
}
private function getRenderingReference() {
return $this->renderingReference;
}
public function getChangeset() {
return $this->changeset;
}
public function setFilename($filename) {
$this->filename = $filename;
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function setMarkupEngine(PhabricatorMarkupEngine $engine) {
$this->markupEngine = $engine;
return $this;
}
public function setCoverage($coverage) {
$this->coverage = $coverage;
return $this;
}
private function getCoverage() {
return $this->coverage;
}
public function parseInlineComment(
PhabricatorInlineComment $comment) {
// Parse only comments which are actually visible.
if ($this->isCommentVisibleOnRenderedDiff($comment)) {
$this->comments[] = $comment;
}
return $this;
}
private function loadCache() {
$render_cache_key = $this->getRenderCacheKey();
if (!$render_cache_key) {
return false;
}
$data = null;
$changeset = new DifferentialChangeset();
$conn_r = $changeset->establishConnection('r');
$data = queryfx_one(
$conn_r,
'SELECT * FROM %T WHERE cacheIndex = %s',
DifferentialChangeset::TABLE_CACHE,
PhabricatorHash::digestForIndex($render_cache_key));
if (!$data) {
return false;
}
if ($data['cache'][0] == '{') {
// This is likely an old-style JSON cache which we will not be able to
// deserialize.
return false;
}
$data = unserialize($data['cache']);
if (!is_array($data) || !$data) {
return false;
}
foreach (self::getCacheableProperties() as $cache_key) {
if (!array_key_exists($cache_key, $data)) {
// If we're missing a cache key, assume we're looking at an old cache
// and ignore it.
return false;
}
}
if ($data['cacheVersion'] !== self::CACHE_VERSION) {
return false;
}
// Someone displays contents of a partially cached shielded file.
if (!isset($data['newRender']) && (!$this->isTopLevel || $this->comments)) {
return false;
}
unset($data['cacheVersion'], $data['cacheHost']);
$cache_prop = array_select_keys($data, self::getCacheableProperties());
foreach ($cache_prop as $cache_key => $v) {
$this->$cache_key = $v;
}
return true;
}
protected static function getCacheableProperties() {
return array(
'visible',
'new',
'old',
'intra',
'depthOnlyLines',
'newRender',
'oldRender',
'specialAttributes',
'hunkStartLines',
'cacheVersion',
'cacheHost',
'highlightingDisabled',
);
}
public function saveCache() {
if (PhabricatorEnv::isReadOnly()) {
return false;
}
if ($this->highlightErrors) {
return false;
}
$render_cache_key = $this->getRenderCacheKey();
if (!$render_cache_key) {
return false;
}
$cache = array();
foreach (self::getCacheableProperties() as $cache_key) {
switch ($cache_key) {
case 'cacheVersion':
$cache[$cache_key] = self::CACHE_VERSION;
break;
case 'cacheHost':
$cache[$cache_key] = php_uname('n');
break;
default:
$cache[$cache_key] = $this->$cache_key;
break;
}
}
$cache = serialize($cache);
// We don't want to waste too much space by a single changeset.
if (strlen($cache) > self::CACHE_MAX_SIZE) {
return;
}
$changeset = new DifferentialChangeset();
$conn_w = $changeset->establishConnection('w');
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
try {
queryfx(
$conn_w,
'INSERT INTO %T (cacheIndex, cache, dateCreated) VALUES (%s, %B, %d)
ON DUPLICATE KEY UPDATE cache = VALUES(cache)',
DifferentialChangeset::TABLE_CACHE,
PhabricatorHash::digestForIndex($render_cache_key),
$cache,
PhabricatorTime::getNow());
} catch (AphrontQueryException $ex) {
// Ignore these exceptions. A common cause is that the cache is
// larger than 'max_allowed_packet', in which case we're better off
// not writing it.
// TODO: It would be nice to tailor this more narrowly.
}
unset($unguarded);
}
private function markGenerated($new_corpus_block = '') {
$generated_guess = (strpos($new_corpus_block, '@'.'generated') !== false);
if (!$generated_guess) {
$generated_path_regexps = PhabricatorEnv::getEnvConfig(
'differential.generated-paths');
foreach ($generated_path_regexps as $regexp) {
if (preg_match($regexp, $this->changeset->getFilename())) {
$generated_guess = true;
break;
}
}
}
$event = new PhabricatorEvent(
PhabricatorEventType::TYPE_DIFFERENTIAL_WILLMARKGENERATED,
array(
'corpus' => $new_corpus_block,
'is_generated' => $generated_guess,
)
);
PhutilEventEngine::dispatchEvent($event);
$generated = $event->getValue('is_generated');
$attribute = $this->changeset->isGeneratedChangeset();
if ($attribute) {
$generated = true;
}
$this->specialAttributes[self::ATTR_GENERATED] = $generated;
}
public function isGenerated() {
return idx($this->specialAttributes, self::ATTR_GENERATED, false);
}
public function isDeleted() {
return idx($this->specialAttributes, self::ATTR_DELETED, false);
}
public function isUnchanged() {
return idx($this->specialAttributes, self::ATTR_UNCHANGED, false);
}
public function isMoveAway() {
return idx($this->specialAttributes, self::ATTR_MOVEAWAY, false);
}
private function applyIntraline(&$render, $intra, $corpus) {
foreach ($render as $key => $text) {
$result = $text;
if (isset($intra[$key])) {
$result = PhabricatorDifferenceEngine::applyIntralineDiff(
$result,
$intra[$key]);
}
$result = $this->adjustRenderedLineForDisplay($result);
$render[$key] = $result;
}
}
private function getHighlightFuture($corpus) {
$language = $this->getViewState()->getHighlightLanguage();
if (!$language) {
$language = $this->highlightEngine->getLanguageFromFilename(
$this->filename);
if (($language != 'txt') &&
(strlen($corpus) > self::HIGHLIGHT_BYTE_LIMIT)) {
$this->highlightingDisabled = true;
$language = 'txt';
}
}
return $this->highlightEngine->getHighlightFuture(
$language,
$corpus);
}
protected function processHighlightedSource($data, $result) {
$result_lines = phutil_split_lines($result);
foreach ($data as $key => $info) {
if (!$info) {
unset($result_lines[$key]);
}
}
return $result_lines;
}
private function tryCacheStuff() {
$changeset = $this->getChangeset();
if (!$changeset->hasSourceTextBody()) {
// TODO: This isn't really correct (the change is not "generated"), the
// intent is just to not render a text body for Subversion directory
// changes, etc.
$this->markGenerated();
return;
}
$viewstate = $this->getViewState();
$skip_cache = false;
if ($this->disableCache) {
$skip_cache = true;
}
$character_encoding = $viewstate->getCharacterEncoding();
if ($character_encoding !== null) {
$skip_cache = true;
}
$highlight_language = $viewstate->getHighlightLanguage();
if ($highlight_language !== null) {
$skip_cache = true;
}
if ($skip_cache || !$this->loadCache()) {
$this->process();
if (!$skip_cache) {
$this->saveCache();
}
}
}
private function process() {
$changeset = $this->changeset;
$hunk_parser = new DifferentialHunkParser();
$hunk_parser->parseHunksForLineData($changeset->getHunks());
$this->realignDiff($changeset, $hunk_parser);
$hunk_parser->reparseHunksForSpecialAttributes();
$unchanged = false;
if (!$hunk_parser->getHasAnyChanges()) {
$filetype = $this->changeset->getFileType();
if ($filetype == DifferentialChangeType::FILE_TEXT ||
$filetype == DifferentialChangeType::FILE_SYMLINK) {
$unchanged = true;
}
}
$moveaway = false;
$changetype = $this->changeset->getChangeType();
if ($changetype == DifferentialChangeType::TYPE_MOVE_AWAY) {
$moveaway = true;
}
$this->setSpecialAttributes(array(
self::ATTR_UNCHANGED => $unchanged,
self::ATTR_DELETED => $hunk_parser->getIsDeleted(),
self::ATTR_MOVEAWAY => $moveaway,
));
$lines_context = $this->getLinesOfContext();
$hunk_parser->generateIntraLineDiffs();
$hunk_parser->generateVisibleLinesMask($lines_context);
$this->setOldLines($hunk_parser->getOldLines());
$this->setNewLines($hunk_parser->getNewLines());
$this->setIntraLineDiffs($hunk_parser->getIntraLineDiffs());
$this->setDepthOnlyLines($hunk_parser->getDepthOnlyLines());
$this->setVisibleLinesMask($hunk_parser->getVisibleLinesMask());
$this->hunkStartLines = $hunk_parser->getHunkStartLines(
$changeset->getHunks());
$new_corpus = $hunk_parser->getNewCorpus();
$new_corpus_block = implode('', $new_corpus);
$this->markGenerated($new_corpus_block);
if ($this->isTopLevel &&
!$this->comments &&
($this->isGenerated() ||
$this->isUnchanged() ||
$this->isDeleted())) {
return;
}
$old_corpus = $hunk_parser->getOldCorpus();
$old_corpus_block = implode('', $old_corpus);
$old_future = $this->getHighlightFuture($old_corpus_block);
$new_future = $this->getHighlightFuture($new_corpus_block);
$futures = array(
'old' => $old_future,
'new' => $new_future,
);
$corpus_blocks = array(
'old' => $old_corpus_block,
'new' => $new_corpus_block,
);
$this->highlightErrors = false;
foreach (new FutureIterator($futures) as $key => $future) {
try {
try {
$highlighted = $future->resolve();
} catch (PhutilSyntaxHighlighterException $ex) {
$this->highlightErrors = true;
$highlighted = id(new PhutilDefaultSyntaxHighlighter())
->getHighlightFuture($corpus_blocks[$key])
->resolve();
}
switch ($key) {
case 'old':
$this->oldRender = $this->processHighlightedSource(
$this->old,
$highlighted);
break;
case 'new':
$this->newRender = $this->processHighlightedSource(
$this->new,
$highlighted);
break;
}
} catch (Exception $ex) {
phlog($ex);
throw $ex;
}
}
$this->applyIntraline(
$this->oldRender,
ipull($this->intra, 0),
$old_corpus);
$this->applyIntraline(
$this->newRender,
ipull($this->intra, 1),
$new_corpus);
}
private function shouldRenderPropertyChangeHeader($changeset) {
if (!$this->isTopLevel) {
// We render properties only at top level; otherwise we get multiple
// copies of them when a user clicks "Show More".
return false;
}
return true;
}
public function render(
$range_start = null,
$range_len = null,
$mask_force = array()) {
$viewer = $this->getViewer();
$renderer = $this->getRenderer();
if (!$renderer) {
$renderer = $this->newRenderer();
$this->setRenderer($renderer);
}
// "Top level" renders are initial requests for the whole file, versus
// requests for a specific range generated by clicking "show more". We
// generate property changes and "shield" UI elements only for toplevel
// requests.
$this->isTopLevel = (($range_start === null) && ($range_len === null));
$this->highlightEngine = PhabricatorSyntaxHighlighter::newEngine();
$viewstate = $this->getViewState();
$encoding = null;
$character_encoding = $viewstate->getCharacterEncoding();
if ($character_encoding) {
// We are forcing this changeset to be interpreted with a specific
// character encoding, so force all the hunks into that encoding and
// propagate it to the renderer.
$encoding = $character_encoding;
foreach ($this->changeset->getHunks() as $hunk) {
$hunk->forceEncoding($character_encoding);
}
} else {
// We're just using the default, so tell the renderer what that is
// (by reading the encoding from the first hunk).
foreach ($this->changeset->getHunks() as $hunk) {
$encoding = $hunk->getDataEncoding();
break;
}
}
$this->tryCacheStuff();
// If we're rendering in an offset mode, treat the range numbers as line
// numbers instead of rendering offsets.
$offset_mode = $this->getOffsetMode();
if ($offset_mode) {
if ($offset_mode == 'new') {
$offset_map = $this->new;
} else {
$offset_map = $this->old;
}
// NOTE: Inline comments use zero-based lengths. For example, a comment
// that starts and ends on line 123 has length 0. Rendering considers
// this range to have length 1. Probably both should agree, but that
// ship likely sailed long ago. Tweak things here to get the two systems
// to agree. See PHI985, where this affected mail rendering of inline
// comments left on the final line of a file.
$range_end = $this->getOffset($offset_map, $range_start + $range_len);
$range_start = $this->getOffset($offset_map, $range_start);
$range_len = ($range_end - $range_start) + 1;
}
$render_pch = $this->shouldRenderPropertyChangeHeader($this->changeset);
$rows = max(
count($this->old),
count($this->new));
$renderer = $this->getRenderer()
->setUser($this->getViewer())
->setChangeset($this->changeset)
->setRenderPropertyChangeHeader($render_pch)
->setIsTopLevel($this->isTopLevel)
->setOldRender($this->oldRender)
->setNewRender($this->newRender)
->setHunkStartLines($this->hunkStartLines)
->setOldChangesetID($this->leftSideChangesetID)
->setNewChangesetID($this->rightSideChangesetID)
->setOldAttachesToNewFile($this->leftSideAttachesToNewFile)
->setNewAttachesToNewFile($this->rightSideAttachesToNewFile)
->setCodeCoverage($this->getCoverage())
->setRenderingReference($this->getRenderingReference())
->setHandles($this->handles)
->setOldLines($this->old)
->setNewLines($this->new)
->setOriginalCharacterEncoding($encoding)
->setShowEditAndReplyLinks($this->getShowEditAndReplyLinks())
->setCanMarkDone($this->getCanMarkDone())
->setObjectOwnerPHID($this->getObjectOwnerPHID())
->setHighlightingDisabled($this->highlightingDisabled)
->setDepthOnlyLines($this->getDepthOnlyLines());
if ($this->markupEngine) {
$renderer->setMarkupEngine($this->markupEngine);
}
list($engine, $old_ref, $new_ref) = $this->newDocumentEngine();
if ($engine) {
$engine_blocks = $engine->newEngineBlocks(
$old_ref,
$new_ref);
} else {
$engine_blocks = null;
}
$has_document_engine = ($engine_blocks !== null);
// Remove empty comments that don't have any unsaved draft data.
PhabricatorInlineComment::loadAndAttachVersionedDrafts(
$viewer,
$this->comments);
foreach ($this->comments as $key => $comment) {
if ($comment->isVoidComment($viewer)) {
unset($this->comments[$key]);
}
}
// See T13515. Sometimes, we collapse file content by default: for
// example, if the file is marked as containing generated code.
// If a file has inline comments, that normally means we never collapse
// it. However, if the viewer has already collapsed all of the inlines,
// it's fine to collapse the file.
$expanded_comments = array();
foreach ($this->comments as $comment) {
if ($comment->isHidden()) {
continue;
}
$expanded_comments[] = $comment;
}
$collapsed_count = (count($this->comments) - count($expanded_comments));
$shield_raw = null;
$shield_text = null;
$shield_type = null;
if ($this->isTopLevel && !$expanded_comments && !$has_document_engine) {
if ($this->isGenerated()) {
$shield_text = pht(
'This file contains generated code, which does not normally '.
'need to be reviewed.');
} else if ($this->isMoveAway()) {
// We put an empty shield on these files. Normally, they do not have
// any diff content anyway. However, if they come through `arc`, they
// may have content. We don't want to show it (it's not useful) and
// we bailed out of fully processing it earlier anyway.
// We could show a message like "this file was moved", but we show
// that as a change header anyway, so it would be redundant. Instead,
// just render an empty shield to skip rendering the diff body.
$shield_raw = '';
} else if ($this->isUnchanged()) {
$type = 'text';
if (!$rows) {
// NOTE: Normally, diffs which don't change files do not include
// file content (for example, if you "chmod +x" a file and then
// run "git show", the file content is not available). Similarly,
// if you move a file from A to B without changing it, diffs normally
// do not show the file content. In some cases `arc` is able to
// synthetically generate content for these diffs, but for raw diffs
// we'll never have it so we need to be prepared to not render a link.
$type = 'none';
}
$shield_type = $type;
$type_add = DifferentialChangeType::TYPE_ADD;
if ($this->changeset->getChangeType() == $type_add) {
// Although the generic message is sort of accurate in a technical
// sense, this more-tailored message is less confusing.
$shield_text = pht('This is an empty file.');
} else {
$shield_text = pht('The contents of this file were not changed.');
}
} else if ($this->isDeleted()) {
$shield_text = pht('This file was completely deleted.');
} else if ($this->changeset->getAffectedLineCount() > 2500) {
$shield_text = pht(
'This file has a very large number of changes (%s lines).',
new PhutilNumber($this->changeset->getAffectedLineCount()));
}
}
$shield = null;
if ($shield_raw !== null) {
$shield = $shield_raw;
} else if ($shield_text !== null) {
if ($shield_type === null) {
$shield_type = 'default';
}
// If we have inlines and the shield would normally show the whole file,
// downgrade it to show only text around the inlines.
if ($collapsed_count) {
if ($shield_type === 'text') {
$shield_type = 'default';
}
$shield_text = array(
$shield_text,
' ',
pht(
'This file has %d collapsed inline comment(s).',
new PhutilNumber($collapsed_count)),
);
}
$shield = $renderer->renderShield($shield_text, $shield_type);
}
if ($shield !== null) {
return $renderer->renderChangesetTable($shield);
}
// This request should render the "undershield" headers if it's a top-level
// request which made it this far (indicating the changeset has no shield)
// or it's a request with no mask information (indicating it's the request
// that removes the rendering shield). Possibly, this second class of
// request might need to be made more explicit.
$is_undershield = (empty($mask_force) || $this->isTopLevel);
$renderer->setIsUndershield($is_undershield);
$old_comments = array();
$new_comments = array();
$old_mask = array();
$new_mask = array();
$feedback_mask = array();
$lines_context = $this->getLinesOfContext();
if ($this->comments) {
// If there are any comments which appear in sections of the file which
// we don't have, we're going to move them backwards to the closest
// earlier line. Two cases where this may happen are:
//
// - Porting ghost comments forward into a file which was mostly
// deleted.
// - Porting ghost comments forward from a full-context diff to a
// partial-context diff.
list($old_backmap, $new_backmap) = $this->buildLineBackmaps();
foreach ($this->comments as $comment) {
$new_side = $this->isCommentOnRightSideWhenDisplayed($comment);
$line = $comment->getLineNumber();
// See T13524. Lint inlines from Harbormaster may not have a line
// number.
if ($line === null) {
$back_line = null;
} else if ($new_side) {
$back_line = idx($new_backmap, $line);
} else {
$back_line = idx($old_backmap, $line);
}
if ($back_line != $line) {
// TODO: This should probably be cleaner, but just be simple and
// obvious for now.
$ghost = $comment->getIsGhost();
if ($ghost) {
$moved = pht(
'This comment originally appeared on line %s, but that line '.
'does not exist in this version of the diff. It has been '.
'moved backward to the nearest line.',
new PhutilNumber($line));
$ghost['reason'] = $ghost['reason']."\n\n".$moved;
$comment->setIsGhost($ghost);
}
$comment->setLineNumber($back_line);
$comment->setLineLength(0);
}
$start = max($comment->getLineNumber() - $lines_context, 0);
$end = $comment->getLineNumber() +
$comment->getLineLength() +
$lines_context;
for ($ii = $start; $ii <= $end; $ii++) {
if ($new_side) {
$new_mask[$ii] = true;
} else {
$old_mask[$ii] = true;
}
}
}
foreach ($this->old as $ii => $old) {
if (isset($old['line']) && isset($old_mask[$old['line']])) {
$feedback_mask[$ii] = true;
}
}
foreach ($this->new as $ii => $new) {
if (isset($new['line']) && isset($new_mask[$new['line']])) {
$feedback_mask[$ii] = true;
}
}
$this->comments = id(new PHUIDiffInlineThreader())
->reorderAndThreadCommments($this->comments);
$old_max_display = 1;
foreach ($this->old as $old) {
if (isset($old['line'])) {
$old_max_display = $old['line'];
}
}
$new_max_display = 1;
foreach ($this->new as $new) {
if (isset($new['line'])) {
$new_max_display = $new['line'];
}
}
foreach ($this->comments as $comment) {
$display_line = $comment->getLineNumber() + $comment->getLineLength();
$display_line = max(1, $display_line);
if ($this->isCommentOnRightSideWhenDisplayed($comment)) {
$display_line = min($new_max_display, $display_line);
$new_comments[$display_line][] = $comment;
} else {
$display_line = min($old_max_display, $display_line);
$old_comments[$display_line][] = $comment;
}
}
}
$renderer
->setOldComments($old_comments)
->setNewComments($new_comments);
if ($engine_blocks !== null) {
$reference = $this->getRenderingReference();
$parts = explode('/', $reference);
if (count($parts) == 2) {
list($id, $vs) = $parts;
} else {
$id = $parts[0];
$vs = 0;
}
// If we don't have an explicit "vs" changeset, it's the left side of
// the "id" changeset.
if (!$vs) {
$vs = $id;
}
if ($mask_force) {
$engine_blocks->setRevealedIndexes(array_keys($mask_force));
}
if ($range_start !== null || $range_len !== null) {
$range_min = $range_start;
if ($range_len === null) {
$range_max = null;
} else {
$range_max = (int)$range_start + (int)$range_len;
}
$engine_blocks->setRange($range_min, $range_max);
}
$renderer
->setDocumentEngine($engine)
->setDocumentEngineBlocks($engine_blocks);
return $renderer->renderDocumentEngineBlocks(
$engine_blocks,
(string)$id,
(string)$vs);
}
// If we've made it here with a type of file we don't know how to render,
// bail out with a default empty rendering. Normally, we'd expect a
// document engine to catch these changes before we make it this far.
switch ($this->changeset->getFileType()) {
case DifferentialChangeType::FILE_DIRECTORY:
case DifferentialChangeType::FILE_BINARY:
case DifferentialChangeType::FILE_IMAGE:
$output = $renderer->renderChangesetTable(null);
return $output;
}
if ($this->originalLeft && $this->originalRight) {
list($highlight_old, $highlight_new) = $this->diffOriginals();
$highlight_old = array_flip($highlight_old);
$highlight_new = array_flip($highlight_new);
$renderer
->setHighlightOld($highlight_old)
->setHighlightNew($highlight_new);
}
$renderer
->setOriginalOld($this->originalLeft)
->setOriginalNew($this->originalRight);
if ($range_start === null) {
$range_start = 0;
}
if ($range_len === null) {
$range_len = $rows;
}
$range_len = min($range_len, $rows - $range_start);
list($gaps, $mask) = $this->calculateGapsAndMask(
$mask_force,
$feedback_mask,
$range_start,
$range_len);
$renderer
->setGaps($gaps)
->setMask($mask);
$html = $renderer->renderTextChange(
$range_start,
$range_len,
$rows);
return $renderer->renderChangesetTable($html);
}
/**
* This function calculates a lot of stuff we need to know to display
* the diff:
*
* Gaps - compute gaps in the visible display diff, where we will render
* "Show more context" spacers. If a gap is smaller than the context size,
* we just display it. Otherwise, we record it into $gaps and will render a
* "show more context" element instead of diff text below. A given $gap
* is a tuple of $gap_line_number_start and $gap_length.
*
* Mask - compute the actual lines that need to be shown (because they
* are near changes lines, near inline comments, or the request has
* explicitly asked for them, i.e. resulting from the user clicking
* "show more"). The $mask returned is a sparsely populated dictionary
* of $visible_line_number => true.
*
* @return array($gaps, $mask)
*/
private function calculateGapsAndMask(
$mask_force,
$feedback_mask,
$range_start,
$range_len) {
$lines_context = $this->getLinesOfContext();
$gaps = array();
$gap_start = 0;
$in_gap = false;
$base_mask = $this->visible + $mask_force + $feedback_mask;
$base_mask[$range_start + $range_len] = true;
for ($ii = $range_start; $ii <= $range_start + $range_len; $ii++) {
if (isset($base_mask[$ii])) {
if ($in_gap) {
$gap_length = $ii - $gap_start;
if ($gap_length <= $lines_context) {
for ($jj = $gap_start; $jj <= $gap_start + $gap_length; $jj++) {
$base_mask[$jj] = true;
}
} else {
$gaps[] = array($gap_start, $gap_length);
}
$in_gap = false;
}
} else {
if (!$in_gap) {
$gap_start = $ii;
$in_gap = true;
}
}
}
$gaps = array_reverse($gaps);
$mask = $base_mask;
return array($gaps, $mask);
}
/**
* Determine if an inline comment will appear on the rendered diff,
* taking into consideration which halves of which changesets will actually
* be shown.
*
* @param PhabricatorInlineComment Comment to test for visibility.
* @return bool True if the comment is visible on the rendered diff.
*/
private function isCommentVisibleOnRenderedDiff(
PhabricatorInlineComment $comment) {
$changeset_id = $comment->getChangesetID();
$is_new = $comment->getIsNewFile();
if ($changeset_id == $this->rightSideChangesetID &&
$is_new == $this->rightSideAttachesToNewFile) {
return true;
}
if ($changeset_id == $this->leftSideChangesetID &&
$is_new == $this->leftSideAttachesToNewFile) {
return true;
}
return false;
}
/**
* Determine if a comment will appear on the right side of the display diff.
* Note that the comment must appear somewhere on the rendered changeset, as
* per isCommentVisibleOnRenderedDiff().
*
* @param PhabricatorInlineComment Comment to test for display
* location.
* @return bool True for right, false for left.
*/
private function isCommentOnRightSideWhenDisplayed(
PhabricatorInlineComment $comment) {
if (!$this->isCommentVisibleOnRenderedDiff($comment)) {
throw new Exception(pht('Comment is not visible on changeset!'));
}
$changeset_id = $comment->getChangesetID();
$is_new = $comment->getIsNewFile();
if ($changeset_id == $this->rightSideChangesetID &&
$is_new == $this->rightSideAttachesToNewFile) {
return true;
}
return false;
}
/**
* Parse the 'range' specification that this class and the client-side JS
* emit to indicate that a user clicked "Show more..." on a diff. Generally,
* use is something like this:
*
* $spec = $request->getStr('range');
* $parsed = DifferentialChangesetParser::parseRangeSpecification($spec);
* list($start, $end, $mask) = $parsed;
* $parser->render($start, $end, $mask);
*
* @param string Range specification, indicating the range of the diff that
* should be rendered.
* @return tuple List of <start, end, mask> suitable for passing to
* @{method:render}.
*/
public static function parseRangeSpecification($spec) {
$range_s = null;
$range_e = null;
$mask = array();
if ($spec) {
$match = null;
if (preg_match('@^(\d+)-(\d+)(?:/(\d+)-(\d+))?$@', $spec, $match)) {
$range_s = (int)$match[1];
$range_e = (int)$match[2];
if (count($match) > 3) {
$start = (int)$match[3];
$len = (int)$match[4];
for ($ii = $start; $ii < $start + $len; $ii++) {
$mask[$ii] = true;
}
}
}
}
return array($range_s, $range_e, $mask);
}
/**
* Render "modified coverage" information; test coverage on modified lines.
* This synthesizes diff information with unit test information into a useful
* indicator of how well tested a change is.
*/
public function renderModifiedCoverage() {
$na = phutil_tag('em', array(), '-');
$coverage = $this->getCoverage();
if (!$coverage) {
return $na;
}
$covered = 0;
$not_covered = 0;
foreach ($this->new as $k => $new) {
if ($new === null) {
continue;
}
if (!$new['line']) {
continue;
}
if (!$new['type']) {
continue;
}
if (empty($coverage[$new['line'] - 1])) {
continue;
}
switch ($coverage[$new['line'] - 1]) {
case 'C':
$covered++;
break;
case 'U':
$not_covered++;
break;
}
}
if (!$covered && !$not_covered) {
return $na;
}
return sprintf('%d%%', 100 * ($covered / ($covered + $not_covered)));
}
/**
* Build maps from lines comments appear on to actual lines.
*/
private function buildLineBackmaps() {
$old_back = array();
$new_back = array();
foreach ($this->old as $ii => $old) {
if ($old === null) {
continue;
}
$old_back[$old['line']] = $old['line'];
}
foreach ($this->new as $ii => $new) {
if ($new === null) {
continue;
}
$new_back[$new['line']] = $new['line'];
}
$max_old_line = 0;
$max_new_line = 0;
foreach ($this->comments as $comment) {
if ($this->isCommentOnRightSideWhenDisplayed($comment)) {
$max_new_line = max($max_new_line, $comment->getLineNumber());
} else {
$max_old_line = max($max_old_line, $comment->getLineNumber());
}
}
$cursor = 1;
for ($ii = 1; $ii <= $max_old_line; $ii++) {
if (empty($old_back[$ii])) {
$old_back[$ii] = $cursor;
} else {
$cursor = $old_back[$ii];
}
}
$cursor = 1;
for ($ii = 1; $ii <= $max_new_line; $ii++) {
if (empty($new_back[$ii])) {
$new_back[$ii] = $cursor;
} else {
$cursor = $new_back[$ii];
}
}
return array($old_back, $new_back);
}
private function getOffset(array $map, $line) {
if (!$map) {
return null;
}
$line = (int)$line;
foreach ($map as $key => $spec) {
if ($spec && isset($spec['line'])) {
if ((int)$spec['line'] >= $line) {
return $key;
}
}
}
return $key;
}
private function realignDiff(
DifferentialChangeset $changeset,
DifferentialHunkParser $hunk_parser) {
// Normalizing and realigning the diff depends on rediffing the files, and
// we currently need complete representations of both files to do anything
// reasonable. If we only have parts of the files, skip realignment.
// We have more than one hunk, so we're definitely missing part of the file.
$hunks = $changeset->getHunks();
if (count($hunks) !== 1) {
return null;
}
// The first hunk doesn't start at the beginning of the file, so we're
// missing some context.
$first_hunk = head($hunks);
if ($first_hunk->getOldOffset() != 1 || $first_hunk->getNewOffset() != 1) {
return null;
}
$old_file = $changeset->makeOldFile();
$new_file = $changeset->makeNewFile();
if ($old_file === $new_file) {
// If the old and new files are exactly identical, the synthetic
// diff below will give us nonsense and whitespace modes are
// irrelevant anyway. This occurs when you, e.g., copy a file onto
// itself in Subversion (see T271).
return null;
}
$engine = id(new PhabricatorDifferenceEngine())
->setNormalize(true);
$normalized_changeset = $engine->generateChangesetFromFileContent(
$old_file,
$new_file);
$type_parser = new DifferentialHunkParser();
$type_parser->parseHunksForLineData($normalized_changeset->getHunks());
$hunk_parser->setNormalized(true);
$hunk_parser->setOldLineTypeMap($type_parser->getOldLineTypeMap());
$hunk_parser->setNewLineTypeMap($type_parser->getNewLineTypeMap());
}
private function adjustRenderedLineForDisplay($line) {
// IMPORTANT: We're using "str_replace()" against raw HTML here, which can
// easily become unsafe. The input HTML has already had syntax highlighting
// and intraline diff highlighting applied, so it's full of "<span />" tags.
static $search;
static $replace;
if ($search === null) {
$rules = $this->newSuspiciousCharacterRules();
$map = array();
foreach ($rules as $key => $spec) {
$tag = phutil_tag(
'span',
array(
'data-copy-text' => $key,
'class' => $spec['class'],
'title' => $spec['title'],
),
$spec['replacement']);
$map[$key] = phutil_string_cast($tag);
}
$search = array_keys($map);
$replace = array_values($map);
}
$is_html = false;
if ($line instanceof PhutilSafeHTML) {
$is_html = true;
$line = hsprintf('%s', $line);
}
$line = phutil_string_cast($line);
// TODO: This should be flexible, eventually.
$tab_width = 2;
$line = self::replaceTabsWithSpaces($line, $tab_width);
$line = str_replace($search, $replace, $line);
if ($is_html) {
$line = phutil_safe_html($line);
}
return $line;
}
private function newSuspiciousCharacterRules() {
// The "title" attributes are cached in the database, so they're
// intentionally not wrapped in "pht(...)".
$rules = array(
"\xE2\x80\x8B" => array(
'title' => 'ZWS',
'class' => 'suspicious-character',
'replacement' => '!',
),
"\xC2\xA0" => array(
'title' => 'NBSP',
'class' => 'suspicious-character',
'replacement' => '!',
),
"\x7F" => array(
'title' => 'DEL (0x7F)',
'class' => 'suspicious-character',
'replacement' => "\xE2\x90\xA1",
),
);
// Unicode defines special pictures for the control characters in the
// range between "0x00" and "0x1F".
$control = array(
'NULL',
'SOH',
'STX',
'ETX',
'EOT',
'ENQ',
'ACK',
'BEL',
'BS',
null, // "\t" Tab
null, // "\n" New Line
'VT',
'FF',
null, // "\r" Carriage Return,
'SO',
'SI',
'DLE',
'DC1',
'DC2',
'DC3',
'DC4',
'NAK',
'SYN',
'ETB',
'CAN',
'EM',
'SUB',
'ESC',
'FS',
'GS',
'RS',
'US',
);
foreach ($control as $idx => $label) {
if ($label === null) {
continue;
}
$rules[chr($idx)] = array(
'title' => sprintf('%s (0x%02X)', $label, $idx),
'class' => 'suspicious-character',
'replacement' => "\xE2\x90".chr(0x80 + $idx),
);
}
return $rules;
}
public static function replaceTabsWithSpaces($line, $tab_width) {
static $tags = array();
if (empty($tags[$tab_width])) {
for ($ii = 1; $ii <= $tab_width; $ii++) {
$tag = phutil_tag(
'span',
array(
'data-copy-text' => "\t",
),
str_repeat(' ', $ii));
$tag = phutil_string_cast($tag);
$tags[$ii] = $tag;
}
}
// Expand all prefix tabs until we encounter any non-tab character. This
// is cheap and often immediately produces the correct result with no
// further work (and, particularly, no need to handle any unicode cases).
$len = strlen($line);
$head = 0;
for ($head = 0; $head < $len; $head++) {
$char = $line[$head];
if ($char !== "\t") {
break;
}
}
if ($head) {
if (empty($tags[$tab_width * $head])) {
$tags[$tab_width * $head] = str_repeat($tags[$tab_width], $head);
}
$prefix = $tags[$tab_width * $head];
$line = substr($line, $head);
} else {
$prefix = '';
}
// If we have no remaining tabs elsewhere in the string after taking care
// of all the prefix tabs, we're done.
if (strpos($line, "\t") === false) {
return $prefix.$line;
}
$len = strlen($line);
// If the line is particularly long, don't try to do anything special with
// it. Use a faster approximation of the correct tabstop expansion instead.
// This usually still arrives at the right result.
if ($len > 256) {
return $prefix.str_replace("\t", $tags[$tab_width], $line);
}
$in_tag = false;
$pos = 0;
// See PHI1210. If the line only has single-byte characters, we don't need
// to vectorize it and can avoid an expensive UTF8 call.
$fast_path = preg_match('/^[\x01-\x7F]*\z/', $line);
if ($fast_path) {
$replace = array();
for ($ii = 0; $ii < $len; $ii++) {
$char = $line[$ii];
if ($char === '>') {
$in_tag = false;
continue;
}
if ($in_tag) {
continue;
}
if ($char === '<') {
$in_tag = true;
continue;
}
if ($char === "\t") {
$count = $tab_width - ($pos % $tab_width);
$pos += $count;
$replace[$ii] = $tags[$count];
continue;
}
$pos++;
}
if ($replace) {
// Apply replacements starting at the end of the string so they
// don't mess up the offsets for following replacements.
$replace = array_reverse($replace, true);
foreach ($replace as $replace_pos => $replacement) {
$line = substr_replace($line, $replacement, $replace_pos, 1);
}
}
} else {
$line = phutil_utf8v_combined($line);
foreach ($line as $key => $char) {
if ($char === '>') {
$in_tag = false;
continue;
}
if ($in_tag) {
continue;
}
if ($char === '<') {
$in_tag = true;
continue;
}
if ($char === "\t") {
$count = $tab_width - ($pos % $tab_width);
$pos += $count;
$line[$key] = $tags[$count];
continue;
}
$pos++;
}
$line = implode('', $line);
}
return $prefix.$line;
}
private function newDocumentEngine() {
$changeset = $this->changeset;
$viewer = $this->getViewer();
list($old_file, $new_file) = $this->loadFileObjectsForChangeset();
$no_old = !$changeset->hasOldState();
$no_new = !$changeset->hasNewState();
if ($no_old) {
$old_ref = null;
} else {
$old_ref = id(new PhabricatorDocumentRef())
->setName($changeset->getOldFile());
if ($old_file) {
$old_ref->setFile($old_file);
} else {
$old_data = $this->getRawDocumentEngineData($this->old);
$old_ref->setData($old_data);
}
}
if ($no_new) {
$new_ref = null;
} else {
$new_ref = id(new PhabricatorDocumentRef())
->setName($changeset->getFilename());
if ($new_file) {
$new_ref->setFile($new_file);
} else {
$new_data = $this->getRawDocumentEngineData($this->new);
$new_ref->setData($new_data);
}
}
$old_engines = null;
if ($old_ref) {
$old_engines = PhabricatorDocumentEngine::getEnginesForRef(
$viewer,
$old_ref);
}
$new_engines = null;
if ($new_ref) {
$new_engines = PhabricatorDocumentEngine::getEnginesForRef(
$viewer,
$new_ref);
}
if ($new_engines !== null && $old_engines !== null) {
$shared_engines = array_intersect_key($new_engines, $old_engines);
$default_engine = head_key($new_engines);
} else if ($new_engines !== null) {
$shared_engines = $new_engines;
$default_engine = head_key($shared_engines);
} else if ($old_engines !== null) {
$shared_engines = $old_engines;
$default_engine = head_key($shared_engines);
} else {
return null;
}
foreach ($shared_engines as $key => $shared_engine) {
if (!$shared_engine->canDiffDocuments($old_ref, $new_ref)) {
unset($shared_engines[$key]);
}
}
$this->availableDocumentEngines = $shared_engines;
$viewstate = $this->getViewState();
$engine_key = $viewstate->getDocumentEngineKey();
- if (strlen($engine_key)) {
+ if (phutil_nonempty_string($engine_key)) {
if (isset($shared_engines[$engine_key])) {
$document_engine = $shared_engines[$engine_key];
} else {
$document_engine = null;
}
} else {
// If we aren't rendering with a specific engine, only use a default
// engine if the best engine for the new file is a shared engine which
// can diff files. If we're less picky (for example, by accepting any
// shared engine) we can end up with silly behavior (like ".json" files
// rendering as Jupyter documents).
if (isset($shared_engines[$default_engine])) {
$document_engine = $shared_engines[$default_engine];
} else {
$document_engine = null;
}
}
if ($document_engine) {
return array(
$document_engine,
$old_ref,
$new_ref);
}
return null;
}
private function loadFileObjectsForChangeset() {
$changeset = $this->changeset;
$viewer = $this->getViewer();
$old_phid = $changeset->getOldFileObjectPHID();
$new_phid = $changeset->getNewFileObjectPHID();
$old_file = null;
$new_file = null;
if ($old_phid || $new_phid) {
$file_phids = array();
if ($old_phid) {
$file_phids[] = $old_phid;
}
if ($new_phid) {
$file_phids[] = $new_phid;
}
$files = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs($file_phids)
->execute();
$files = mpull($files, null, 'getPHID');
if ($old_phid) {
$old_file = idx($files, $old_phid);
if (!$old_file) {
throw new Exception(
pht(
'Failed to load file data for changeset ("%s").',
$old_phid));
}
$changeset->attachOldFileObject($old_file);
}
if ($new_phid) {
$new_file = idx($files, $new_phid);
if (!$new_file) {
throw new Exception(
pht(
'Failed to load file data for changeset ("%s").',
$new_phid));
}
$changeset->attachNewFileObject($new_file);
}
}
return array($old_file, $new_file);
}
public function newChangesetResponse() {
// NOTE: This has to happen first because it has side effects. Yuck.
$rendered_changeset = $this->renderChangeset();
$renderer = $this->getRenderer();
$renderer_key = $renderer->getRendererKey();
$viewstate = $this->getViewState();
$undo_templates = $renderer->renderUndoTemplates();
foreach ($undo_templates as $key => $undo_template) {
$undo_templates[$key] = hsprintf('%s', $undo_template);
}
$document_engine = $renderer->getDocumentEngine();
if ($document_engine) {
$document_engine_key = $document_engine->getDocumentEngineKey();
} else {
$document_engine_key = null;
}
$available_keys = array();
$engines = $this->availableDocumentEngines;
if (!$engines) {
$engines = array();
}
$available_keys = mpull($engines, 'getDocumentEngineKey');
// TODO: Always include "source" as a usable engine to default to
// the buitin rendering. This is kind of a hack and does not actually
// use the source engine. The source engine isn't a diff engine, so
// selecting it causes us to fall through and render with builtin
// behavior. For now, overall behavir is reasonable.
$available_keys[] = PhabricatorSourceDocumentEngine::ENGINEKEY;
$available_keys = array_fuse($available_keys);
$available_keys = array_values($available_keys);
$state = array(
'undoTemplates' => $undo_templates,
'rendererKey' => $renderer_key,
'highlight' => $viewstate->getHighlightLanguage(),
'characterEncoding' => $viewstate->getCharacterEncoding(),
'requestDocumentEngineKey' => $viewstate->getDocumentEngineKey(),
'responseDocumentEngineKey' => $document_engine_key,
'availableDocumentEngineKeys' => $available_keys,
'isHidden' => $viewstate->getHidden(),
);
return id(new PhabricatorChangesetResponse())
->setRenderedChangeset($rendered_changeset)
->setChangesetState($state);
}
private function getRawDocumentEngineData(array $lines) {
$text = array();
foreach ($lines as $line) {
if ($line === null) {
continue;
}
// If this is a "No newline at end of file." annotation, don't hand it
// off to the DocumentEngine.
if ($line['type'] === '\\') {
continue;
}
$text[] = $line['text'];
}
return implode('', $text);
}
}
diff --git a/src/applications/differential/query/DifferentialChangesetQuery.php b/src/applications/differential/query/DifferentialChangesetQuery.php
index e2357f9278..ee29aa1cf8 100644
--- a/src/applications/differential/query/DifferentialChangesetQuery.php
+++ b/src/applications/differential/query/DifferentialChangesetQuery.php
@@ -1,182 +1,178 @@
<?php
final class DifferentialChangesetQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $diffPHIDs;
private $diffs;
private $needAttachToDiffs;
private $needHunks;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withDiffs(array $diffs) {
assert_instances_of($diffs, 'DifferentialDiff');
$this->diffs = $diffs;
return $this;
}
public function withDiffPHIDs(array $phids) {
$this->diffPHIDs = $phids;
return $this;
}
public function needAttachToDiffs($attach) {
$this->needAttachToDiffs = $attach;
return $this;
}
public function needHunks($need) {
$this->needHunks = $need;
return $this;
}
protected function willExecute() {
// If we fail to load any changesets (which is possible in the case of an
// empty commit) we'll never call didFilterPage(). Attach empty changeset
// lists now so that we end up with the right result.
if ($this->needAttachToDiffs) {
foreach ($this->diffs as $diff) {
$diff->attachChangesets(array());
}
}
}
public function newResultObject() {
return new DifferentialChangeset();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $changesets) {
// First, attach all the diffs we already have. We can just do this
// directly without worrying about querying for them. When we don't have
// a diff, record that we need to load it.
if ($this->diffs) {
$have_diffs = mpull($this->diffs, null, 'getID');
} else {
$have_diffs = array();
}
$must_load = array();
foreach ($changesets as $key => $changeset) {
$diff_id = $changeset->getDiffID();
if (isset($have_diffs[$diff_id])) {
$changeset->attachDiff($have_diffs[$diff_id]);
} else {
$must_load[$key] = $changeset;
}
}
// Load all the diffs we don't have.
$need_diff_ids = mpull($must_load, 'getDiffID');
$more_diffs = array();
if ($need_diff_ids) {
$more_diffs = id(new DifferentialDiffQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withIDs($need_diff_ids)
->execute();
$more_diffs = mpull($more_diffs, null, 'getID');
}
// Attach the diffs we loaded.
foreach ($must_load as $key => $changeset) {
$diff_id = $changeset->getDiffID();
if (isset($more_diffs[$diff_id])) {
$changeset->attachDiff($more_diffs[$diff_id]);
} else {
// We didn't have the diff, and could not load it (it does not exist,
// or we can't see it), so filter this result out.
unset($changesets[$key]);
}
}
return $changesets;
}
protected function didFilterPage(array $changesets) {
if ($this->needAttachToDiffs) {
$changeset_groups = mgroup($changesets, 'getDiffID');
foreach ($this->diffs as $diff) {
$diff_changesets = idx($changeset_groups, $diff->getID(), array());
$diff->attachChangesets($diff_changesets);
}
}
if ($this->needHunks) {
id(new DifferentialHunkQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withChangesets($changesets)
->needAttachToChangesets(true)
->execute();
}
return $changesets;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->diffs !== null) {
$where[] = qsprintf(
$conn,
'diffID IN (%Ld)',
mpull($this->diffs, 'getID'));
}
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->diffPHIDs !== null) {
$diff_ids = queryfx_all(
$conn,
'SELECT id FROM %R WHERE phid IN (%Ls)',
new DifferentialDiff(),
$this->diffPHIDs);
$diff_ids = ipull($diff_ids, 'id', null);
if (!$diff_ids) {
throw new PhabricatorEmptyQueryException();
}
$where[] = qsprintf(
$conn,
'diffID IN (%Ld)',
$diff_ids);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorDifferentialApplication';
}
}
diff --git a/src/applications/differential/query/DifferentialDiffQuery.php b/src/applications/differential/query/DifferentialDiffQuery.php
index 3b1dd3ae62..04019df1e0 100644
--- a/src/applications/differential/query/DifferentialDiffQuery.php
+++ b/src/applications/differential/query/DifferentialDiffQuery.php
@@ -1,195 +1,191 @@
<?php
final class DifferentialDiffQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $revisionIDs;
private $revisionPHIDs;
private $commitPHIDs;
private $hasRevision;
private $needChangesets = false;
private $needProperties;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withRevisionIDs(array $revision_ids) {
$this->revisionIDs = $revision_ids;
return $this;
}
public function withRevisionPHIDs(array $revision_phids) {
$this->revisionPHIDs = $revision_phids;
return $this;
}
public function withCommitPHIDs(array $phids) {
$this->commitPHIDs = $phids;
return $this;
}
public function withHasRevision($has_revision) {
$this->hasRevision = $has_revision;
return $this;
}
public function needChangesets($bool) {
$this->needChangesets = $bool;
return $this;
}
public function needProperties($need_properties) {
$this->needProperties = $need_properties;
return $this;
}
public function newResultObject() {
return new DifferentialDiff();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $diffs) {
$revision_ids = array_filter(mpull($diffs, 'getRevisionID'));
$revisions = array();
if ($revision_ids) {
$revisions = id(new DifferentialRevisionQuery())
->setViewer($this->getViewer())
->withIDs($revision_ids)
->execute();
}
foreach ($diffs as $key => $diff) {
if (!$diff->getRevisionID()) {
continue;
}
$revision = idx($revisions, $diff->getRevisionID());
if ($revision) {
$diff->attachRevision($revision);
continue;
}
unset($diffs[$key]);
}
if ($diffs && $this->needChangesets) {
$diffs = $this->loadChangesets($diffs);
}
return $diffs;
}
protected function didFilterPage(array $diffs) {
if ($this->needProperties) {
$properties = id(new DifferentialDiffProperty())->loadAllWhere(
'diffID IN (%Ld)',
mpull($diffs, 'getID'));
$properties = mgroup($properties, 'getDiffID');
foreach ($diffs as $diff) {
$map = idx($properties, $diff->getID(), array());
$map = mpull($map, 'getData', 'getName');
$diff->attachDiffProperties($map);
}
}
return $diffs;
}
private function loadChangesets(array $diffs) {
id(new DifferentialChangesetQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withDiffs($diffs)
->needAttachToDiffs(true)
->needHunks(true)
->execute();
return $diffs;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->revisionIDs !== null) {
$where[] = qsprintf(
$conn,
'revisionID IN (%Ld)',
$this->revisionIDs);
}
if ($this->commitPHIDs !== null) {
$where[] = qsprintf(
$conn,
'commitPHID IN (%Ls)',
$this->commitPHIDs);
}
if ($this->hasRevision !== null) {
if ($this->hasRevision) {
$where[] = qsprintf(
$conn,
'revisionID IS NOT NULL');
} else {
$where[] = qsprintf(
$conn,
'revisionID IS NULL');
}
}
if ($this->revisionPHIDs !== null) {
$viewer = $this->getViewer();
$revisions = id(new DifferentialRevisionQuery())
->setViewer($viewer)
->setParentQuery($this)
->withPHIDs($this->revisionPHIDs)
->execute();
$revision_ids = mpull($revisions, 'getID');
if (!$revision_ids) {
throw new PhabricatorEmptyQueryException();
}
$where[] = qsprintf(
$conn,
'revisionID IN (%Ls)',
$revision_ids);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorDifferentialApplication';
}
}
diff --git a/src/applications/differential/query/DifferentialHunkQuery.php b/src/applications/differential/query/DifferentialHunkQuery.php
index 981c2b4782..21787f04e5 100644
--- a/src/applications/differential/query/DifferentialHunkQuery.php
+++ b/src/applications/differential/query/DifferentialHunkQuery.php
@@ -1,93 +1,89 @@
<?php
final class DifferentialHunkQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $changesets;
private $shouldAttachToChangesets;
public function withChangesets(array $changesets) {
assert_instances_of($changesets, 'DifferentialChangeset');
$this->changesets = $changesets;
return $this;
}
public function needAttachToChangesets($attach) {
$this->shouldAttachToChangesets = $attach;
return $this;
}
protected function willExecute() {
// If we fail to load any hunks at all (for example, because all of
// the requested changesets are directories or empty files and have no
// hunks) we'll never call didFilterPage(), and thus never have an
// opportunity to attach hunks. Attach empty hunk lists now so that we
// end up with the right result.
if ($this->shouldAttachToChangesets) {
foreach ($this->changesets as $changeset) {
$changeset->attachHunks(array());
}
}
}
public function newResultObject() {
return new DifferentialHunk();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $hunks) {
$changesets = mpull($this->changesets, null, 'getID');
foreach ($hunks as $key => $hunk) {
$changeset = idx($changesets, $hunk->getChangesetID());
if (!$changeset) {
unset($hunks[$key]);
}
$hunk->attachChangeset($changeset);
}
return $hunks;
}
protected function didFilterPage(array $hunks) {
if ($this->shouldAttachToChangesets) {
$hunk_groups = mgroup($hunks, 'getChangesetID');
foreach ($this->changesets as $changeset) {
$hunks = idx($hunk_groups, $changeset->getID(), array());
$changeset->attachHunks($hunks);
}
}
return $hunks;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if (!$this->changesets) {
throw new Exception(
pht(
'You must load hunks via changesets, with %s!',
'withChangesets()'));
}
$where[] = qsprintf(
$conn,
'changesetID IN (%Ld)',
mpull($this->changesets, 'getID'));
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorDifferentialApplication';
}
protected function getDefaultOrderVector() {
// TODO: Do we need this?
return array('-id');
}
}
diff --git a/src/applications/differential/query/DifferentialViewStateQuery.php b/src/applications/differential/query/DifferentialViewStateQuery.php
index 6ef63721e4..604a6de1db 100644
--- a/src/applications/differential/query/DifferentialViewStateQuery.php
+++ b/src/applications/differential/query/DifferentialViewStateQuery.php
@@ -1,64 +1,60 @@
<?php
final class DifferentialViewStateQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $viewerPHIDs;
private $objectPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withViewerPHIDs(array $phids) {
$this->viewerPHIDs = $phids;
return $this;
}
public function withObjectPHIDs(array $phids) {
$this->objectPHIDs = $phids;
return $this;
}
public function newResultObject() {
return new DifferentialViewState();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->viewerPHIDs !== null) {
$where[] = qsprintf(
$conn,
'viewerPHID IN (%Ls)',
$this->viewerPHIDs);
}
if ($this->objectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'objectPHID IN (%Ls)',
$this->objectPHIDs);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorDifferentialApplication';
}
}
diff --git a/src/applications/differential/render/DifferentialChangesetTestRenderer.php b/src/applications/differential/render/DifferentialChangesetTestRenderer.php
index 56b96dcbca..b4b2122e59 100644
--- a/src/applications/differential/render/DifferentialChangesetTestRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetTestRenderer.php
@@ -1,137 +1,137 @@
<?php
abstract class DifferentialChangesetTestRenderer
extends DifferentialChangesetRenderer {
protected function renderChangeTypeHeader($force) {
$changeset = $this->getChangeset();
$old = nonempty($changeset->getOldFile(), '-');
$current = nonempty($changeset->getFilename(), '-');
$away = nonempty(implode(', ', $changeset->getAwayPaths()), '-');
$ctype = $changeset->getChangeType();
$ftype = $changeset->getFileType();
$force = ($force ? '(forced)' : '(unforced)');
return "CTYPE {$ctype} {$ftype} {$force}\n".
"{$old}\n".
"{$current}\n".
"{$away}\n";
}
protected function renderUndershieldHeader() {
return null;
}
public function renderShield($message, $force = 'default') {
return "SHIELD ({$force}) {$message}\n";
}
protected function renderPropertyChangeHeader() {
$changeset = $this->getChangeset();
list($old, $new) = $this->getChangesetProperties($changeset);
foreach (array_keys($old) as $key) {
if ($old[$key] === idx($new, $key)) {
unset($old[$key]);
unset($new[$key]);
}
}
if (!$old && !$new) {
return null;
}
$props = '';
foreach ($old as $key => $value) {
$props .= "P - {$key} {$value}~\n";
}
foreach ($new as $key => $value) {
$props .= "P + {$key} {$value}~\n";
}
return "PROPERTIES\n".$props;
}
public function renderTextChange(
$range_start,
$range_len,
$rows) {
$out = array();
$any_old = false;
$any_new = false;
$primitives = $this->buildPrimitives($range_start, $range_len);
foreach ($primitives as $p) {
$type = $p['type'];
switch ($type) {
case 'old':
case 'new':
if ($type == 'old') {
$any_old = true;
}
if ($type == 'new') {
$any_new = true;
}
$num = nonempty($p['line'], '-');
- $render = $p['render'];
+ $render = (string)$p['render'];
$htype = nonempty($p['htype'], '.');
// TODO: This should probably happen earlier, whenever we deal with
// \r and \t normalization?
$render = str_replace(
array(
"\r",
"\n",
),
array(
'\\r',
'\\n',
),
$render);
$render = str_replace(
array(
'<span class="bright">',
'</span>',
'<span class="depth-out">',
'<span class="depth-in">',
),
array(
'{(',
')}',
'{<',
'{>',
),
$render);
$render = html_entity_decode($render, ENT_QUOTES);
$t = ($type == 'old') ? 'O' : 'N';
$out[] = "{$t} {$num} {$htype} {$render}~";
break;
case 'no-context':
$out[] = 'X <MISSING-CONTEXT>';
break;
default:
$out[] = $type;
break;
}
}
if (!$any_old) {
$out[] = 'O X <EMPTY>';
}
if (!$any_new) {
$out[] = 'N X <EMPTY>';
}
$out = implode("\n", $out)."\n";
return phutil_safe_html($out);
}
}
diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php
index 78025e44ec..dfa6d1f791 100644
--- a/src/applications/differential/storage/DifferentialDiff.php
+++ b/src/applications/differential/storage/DifferentialDiff.php
@@ -1,834 +1,834 @@
<?php
final class DifferentialDiff
extends DifferentialDAO
implements
PhabricatorPolicyInterface,
PhabricatorExtendedPolicyInterface,
HarbormasterBuildableInterface,
HarbormasterCircleCIBuildableInterface,
HarbormasterBuildkiteBuildableInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorDestructibleInterface,
PhabricatorConduitResultInterface {
protected $revisionID;
protected $authorPHID;
protected $repositoryPHID;
protected $commitPHID;
protected $sourceMachine;
protected $sourcePath;
protected $sourceControlSystem;
protected $sourceControlBaseRevision;
protected $sourceControlPath;
protected $lintStatus;
protected $unitStatus;
protected $lineCount;
protected $branch;
protected $bookmark;
protected $creationMethod;
protected $repositoryUUID;
protected $description;
protected $viewPolicy;
private $unsavedChangesets = array();
private $changesets = self::ATTACHABLE;
private $revision = self::ATTACHABLE;
private $properties = self::ATTACHABLE;
private $buildable = self::ATTACHABLE;
private $unitMessages = self::ATTACHABLE;
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'revisionID' => 'id?',
'authorPHID' => 'phid?',
'repositoryPHID' => 'phid?',
'sourceMachine' => 'text255?',
'sourcePath' => 'text255?',
'sourceControlSystem' => 'text64?',
'sourceControlBaseRevision' => 'text255?',
'sourceControlPath' => 'text255?',
'lintStatus' => 'uint32',
'unitStatus' => 'uint32',
'lineCount' => 'uint32',
'branch' => 'text255?',
'bookmark' => 'text255?',
'repositoryUUID' => 'text64?',
'commitPHID' => 'phid?',
// T6203/NULLABILITY
// These should be non-null; all diffs should have a creation method
// and the description should just be empty.
'creationMethod' => 'text255?',
'description' => 'text255?',
),
self::CONFIG_KEY_SCHEMA => array(
'revisionID' => array(
'columns' => array('revisionID'),
),
'key_commit' => array(
'columns' => array('commitPHID'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
DifferentialDiffPHIDType::TYPECONST);
}
public function addUnsavedChangeset(DifferentialChangeset $changeset) {
if ($this->changesets === null) {
$this->changesets = array();
}
$this->unsavedChangesets[] = $changeset;
$this->changesets[] = $changeset;
return $this;
}
public function attachChangesets(array $changesets) {
assert_instances_of($changesets, 'DifferentialChangeset');
$this->changesets = $changesets;
return $this;
}
public function getChangesets() {
return $this->assertAttached($this->changesets);
}
public function loadChangesets() {
if (!$this->getID()) {
return array();
}
$changesets = id(new DifferentialChangeset())->loadAllWhere(
'diffID = %d',
$this->getID());
foreach ($changesets as $changeset) {
$changeset->attachDiff($this);
}
return $changesets;
}
public function save() {
$this->openTransaction();
$ret = parent::save();
foreach ($this->unsavedChangesets as $changeset) {
$changeset->setDiffID($this->getID());
$changeset->save();
}
$this->saveTransaction();
return $ret;
}
public static function initializeNewDiff(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
->withClasses(array('PhabricatorDifferentialApplication'))
->executeOne();
$view_policy = $app->getPolicy(
DifferentialDefaultViewCapability::CAPABILITY);
$diff = id(new DifferentialDiff())
->setViewPolicy($view_policy);
return $diff;
}
public static function newFromRawChanges(
PhabricatorUser $actor,
array $changes) {
assert_instances_of($changes, 'ArcanistDiffChange');
$diff = self::initializeNewDiff($actor);
return self::buildChangesetsFromRawChanges($diff, $changes);
}
public static function newEphemeralFromRawChanges(array $changes) {
assert_instances_of($changes, 'ArcanistDiffChange');
$diff = id(new DifferentialDiff())->makeEphemeral();
return self::buildChangesetsFromRawChanges($diff, $changes);
}
private static function buildChangesetsFromRawChanges(
DifferentialDiff $diff,
array $changes) {
// There may not be any changes; initialize the changesets list so that
// we don't throw later when accessing it.
$diff->attachChangesets(array());
$lines = 0;
foreach ($changes as $change) {
if ($change->getType() == ArcanistDiffChangeType::TYPE_MESSAGE) {
// If a user pastes a diff into Differential which includes a commit
// message (e.g., they ran `git show` to generate it), discard that
// change when constructing a DifferentialDiff.
continue;
}
$changeset = new DifferentialChangeset();
$add_lines = 0;
$del_lines = 0;
$first_line = PHP_INT_MAX;
$hunks = $change->getHunks();
if ($hunks) {
foreach ($hunks as $hunk) {
$dhunk = new DifferentialHunk();
$dhunk->setOldOffset($hunk->getOldOffset());
$dhunk->setOldLen($hunk->getOldLength());
$dhunk->setNewOffset($hunk->getNewOffset());
$dhunk->setNewLen($hunk->getNewLength());
$dhunk->setChanges($hunk->getCorpus());
$changeset->addUnsavedHunk($dhunk);
$add_lines += $hunk->getAddLines();
$del_lines += $hunk->getDelLines();
$added_lines = $hunk->getChangedLines('new');
if ($added_lines) {
$first_line = min($first_line, head_key($added_lines));
}
}
$lines += $add_lines + $del_lines;
} else {
// This happens when you add empty files.
$changeset->attachHunks(array());
}
$metadata = $change->getAllMetadata();
if ($first_line != PHP_INT_MAX) {
$metadata['line:first'] = $first_line;
}
$changeset->setOldFile($change->getOldPath());
$changeset->setFilename($change->getCurrentPath());
$changeset->setChangeType($change->getType());
$changeset->setFileType($change->getFileType());
$changeset->setMetadata($metadata);
$changeset->setOldProperties($change->getOldProperties());
$changeset->setNewProperties($change->getNewProperties());
$changeset->setAwayPaths($change->getAwayPaths());
$changeset->setAddLines($add_lines);
$changeset->setDelLines($del_lines);
$diff->addUnsavedChangeset($changeset);
}
$diff->setLineCount($lines);
$changesets = $diff->getChangesets();
// TODO: This is "safe", but it would be better to propagate a real user
// down the stack.
$viewer = PhabricatorUser::getOmnipotentUser();
id(new DifferentialChangesetEngine())
->setViewer($viewer)
->rebuildChangesets($changesets);
return $diff;
}
public function getDiffDict() {
$dict = array(
'id' => $this->getID(),
'revisionID' => $this->getRevisionID(),
'dateCreated' => $this->getDateCreated(),
'dateModified' => $this->getDateModified(),
'sourceControlBaseRevision' => $this->getSourceControlBaseRevision(),
'sourceControlPath' => $this->getSourceControlPath(),
'sourceControlSystem' => $this->getSourceControlSystem(),
'branch' => $this->getBranch(),
'bookmark' => $this->getBookmark(),
'creationMethod' => $this->getCreationMethod(),
'description' => $this->getDescription(),
'unitStatus' => $this->getUnitStatus(),
'lintStatus' => $this->getLintStatus(),
'changes' => array(),
);
$dict['changes'] = $this->buildChangesList();
return $dict + $this->getDiffAuthorshipDict();
}
public function getDiffAuthorshipDict() {
$dict = array('properties' => array());
$properties = id(new DifferentialDiffProperty())->loadAllWhere(
'diffID = %d',
$this->getID());
foreach ($properties as $property) {
$dict['properties'][$property->getName()] = $property->getData();
if ($property->getName() == 'local:commits') {
foreach ($property->getData() as $commit) {
$dict['authorName'] = $commit['author'];
$dict['authorEmail'] = idx($commit, 'authorEmail');
break;
}
}
}
return $dict;
}
public function buildChangesList() {
$changes = array();
foreach ($this->getChangesets() as $changeset) {
$hunks = array();
foreach ($changeset->getHunks() as $hunk) {
$hunks[] = array(
'oldOffset' => $hunk->getOldOffset(),
'newOffset' => $hunk->getNewOffset(),
'oldLength' => $hunk->getOldLen(),
'newLength' => $hunk->getNewLen(),
'addLines' => null,
'delLines' => null,
'isMissingOldNewline' => null,
'isMissingNewNewline' => null,
'corpus' => $hunk->getChanges(),
);
}
$change = array(
'id' => $changeset->getID(),
'metadata' => $changeset->getMetadata(),
'oldPath' => $changeset->getOldFile(),
'currentPath' => $changeset->getFilename(),
'awayPaths' => $changeset->getAwayPaths(),
'oldProperties' => $changeset->getOldProperties(),
'newProperties' => $changeset->getNewProperties(),
'type' => $changeset->getChangeType(),
'fileType' => $changeset->getFileType(),
'commitHash' => null,
'addLines' => $changeset->getAddLines(),
'delLines' => $changeset->getDelLines(),
'hunks' => $hunks,
);
$changes[] = $change;
}
return $changes;
}
public function hasRevision() {
return $this->revision !== self::ATTACHABLE;
}
public function getRevision() {
return $this->assertAttached($this->revision);
}
public function attachRevision(DifferentialRevision $revision = null) {
$this->revision = $revision;
return $this;
}
public function attachProperty($key, $value) {
if (!is_array($this->properties)) {
$this->properties = array();
}
$this->properties[$key] = $value;
return $this;
}
public function getProperty($key) {
return $this->assertAttachedKey($this->properties, $key);
}
public function hasDiffProperty($key) {
$properties = $this->getDiffProperties();
return array_key_exists($key, $properties);
}
public function attachDiffProperties(array $properties) {
$this->properties = $properties;
return $this;
}
public function getDiffProperties() {
return $this->assertAttached($this->properties);
}
public function attachBuildable(HarbormasterBuildable $buildable = null) {
$this->buildable = $buildable;
return $this;
}
public function getBuildable() {
return $this->assertAttached($this->buildable);
}
public function getBuildTargetPHIDs() {
$buildable = $this->getBuildable();
if (!$buildable) {
return array();
}
$target_phids = array();
foreach ($buildable->getBuilds() as $build) {
foreach ($build->getBuildTargets() as $target) {
$target_phids[] = $target->getPHID();
}
}
return $target_phids;
}
public function loadCoverageMap(PhabricatorUser $viewer) {
$target_phids = $this->getBuildTargetPHIDs();
if (!$target_phids) {
return array();
}
$unit = id(new HarbormasterBuildUnitMessageQuery())
->setViewer($viewer)
->withBuildTargetPHIDs($target_phids)
->execute();
$map = array();
foreach ($unit as $message) {
$coverage = $message->getProperty('coverage', array());
foreach ($coverage as $path => $coverage_data) {
$map[$path][] = $coverage_data;
}
}
foreach ($map as $path => $coverage_items) {
$map[$path] = ArcanistUnitTestResult::mergeCoverage($coverage_items);
}
return $map;
}
public function getURI() {
$id = $this->getID();
return "/differential/diff/{$id}/";
}
public function attachUnitMessages(array $unit_messages) {
$this->unitMessages = $unit_messages;
return $this;
}
public function getUnitMessages() {
return $this->assertAttached($this->unitMessages);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
if ($this->hasRevision()) {
return PhabricatorPolicies::getMostOpenPolicy();
}
return $this->viewPolicy;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
if ($this->hasRevision()) {
return $this->getRevision()->hasAutomaticCapability($capability, $viewer);
}
return ($this->getAuthorPHID() == $viewer->getPHID());
}
public function describeAutomaticCapability($capability) {
if ($this->hasRevision()) {
return pht(
'This diff is attached to a revision, and inherits its policies.');
}
return pht('The author of a diff can see it.');
}
/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
$extended = array();
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
if ($this->hasRevision()) {
$extended[] = array(
$this->getRevision(),
PhabricatorPolicyCapability::CAN_VIEW,
);
} else if ($this->getRepositoryPHID()) {
$extended[] = array(
$this->getRepositoryPHID(),
PhabricatorPolicyCapability::CAN_VIEW,
);
}
break;
}
return $extended;
}
/* -( HarbormasterBuildableInterface )------------------------------------- */
public function getHarbormasterBuildableDisplayPHID() {
$container_phid = $this->getHarbormasterContainerPHID();
if ($container_phid) {
return $container_phid;
}
return $this->getHarbormasterBuildablePHID();
}
public function getHarbormasterBuildablePHID() {
return $this->getPHID();
}
public function getHarbormasterContainerPHID() {
if ($this->getRevisionID()) {
$revision = id(new DifferentialRevision())->load($this->getRevisionID());
if ($revision) {
return $revision->getPHID();
}
}
return null;
}
public function getBuildVariables() {
$results = array();
$results['buildable.diff'] = $this->getID();
if ($this->revisionID) {
$revision = $this->getRevision();
$results['buildable.revision'] = $revision->getID();
$repo = $revision->getRepository();
if ($repo) {
$results['repository.callsign'] = $repo->getCallsign();
$results['repository.phid'] = $repo->getPHID();
$results['repository.vcs'] = $repo->getVersionControlSystem();
$results['repository.uri'] = $repo->getPublicCloneURI();
$results['repository.staging.uri'] = $repo->getStagingURI();
$results['repository.staging.ref'] = $this->getStagingRef();
}
}
return $results;
}
public function getAvailableBuildVariables() {
return array(
'buildable.diff' =>
pht('The differential diff ID, if applicable.'),
'buildable.revision' =>
pht('The differential revision ID, if applicable.'),
'repository.callsign' =>
- pht('The callsign of the repository in Phabricator.'),
+ pht('The callsign of the repository.'),
'repository.phid' =>
- pht('The PHID of the repository in Phabricator.'),
+ pht('The PHID of the repository.'),
'repository.vcs' =>
pht('The version control system, either "svn", "hg" or "git".'),
'repository.uri' =>
pht('The URI to clone or checkout the repository from.'),
'repository.staging.uri' =>
pht('The URI of the staging repository.'),
'repository.staging.ref' =>
pht('The ref name for this change in the staging repository.'),
);
}
public function newBuildableEngine() {
return new DifferentialBuildableEngine();
}
/* -( HarbormasterCircleCIBuildableInterface )----------------------------- */
public function getCircleCIGitHubRepositoryURI() {
$diff_phid = $this->getPHID();
$repository_phid = $this->getRepositoryPHID();
if (!$repository_phid) {
throw new Exception(
pht(
'This diff ("%s") is not associated with a repository. A diff '.
'must belong to a tracked repository to be built by CircleCI.',
$diff_phid));
}
$repository = id(new PhabricatorRepositoryQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($repository_phid))
->executeOne();
if (!$repository) {
throw new Exception(
pht(
'This diff ("%s") is associated with a repository ("%s") which '.
'could not be loaded.',
$diff_phid,
$repository_phid));
}
$staging_uri = $repository->getStagingURI();
if (!$staging_uri) {
throw new Exception(
pht(
'This diff ("%s") is associated with a repository ("%s") that '.
'does not have a Staging Area configured. You must configure a '.
'Staging Area to use CircleCI integration.',
$diff_phid,
$repository_phid));
}
$path = HarbormasterCircleCIBuildStepImplementation::getGitHubPath(
$staging_uri);
if (!$path) {
throw new Exception(
pht(
'This diff ("%s") is associated with a repository ("%s") that '.
'does not have a Staging Area ("%s") that is hosted on GitHub. '.
'CircleCI can only build from GitHub, so the Staging Area for '.
'the repository must be hosted there.',
$diff_phid,
$repository_phid,
$staging_uri));
}
return $staging_uri;
}
public function getCircleCIBuildIdentifierType() {
return 'tag';
}
public function getCircleCIBuildIdentifier() {
$ref = $this->getStagingRef();
$ref = preg_replace('(^refs/tags/)', '', $ref);
return $ref;
}
/* -( HarbormasterBuildkiteBuildableInterface )---------------------------- */
public function getBuildkiteBranch() {
$ref = $this->getStagingRef();
// NOTE: Circa late January 2017, Buildkite fails with the error message
// "Tags have been disabled for this project" if we pass the "refs/tags/"
// prefix via the API and the project doesn't have GitHub tag builds
// enabled, even if GitHub builds are disabled. The tag builds fine
// without this prefix.
$ref = preg_replace('(^refs/tags/)', '', $ref);
return $ref;
}
public function getBuildkiteCommit() {
return 'HEAD';
}
public function getStagingRef() {
// TODO: We're just hoping to get lucky. Instead, `arc` should store
// where it sent changes and we should only provide staging details
// if we reasonably believe they are accurate.
return 'refs/tags/phabricator/diff/'.$this->getID();
}
public function loadTargetBranch() {
// TODO: This is sketchy, but just eat the query cost until this can get
// cleaned up.
// For now, we're only returning a target if there's exactly one and it's
// a branch, since we don't support landing to more esoteric targets like
// tags yet.
$property = id(new DifferentialDiffProperty())->loadOneWhere(
'diffID = %d AND name = %s',
$this->getID(),
'arc:onto');
if (!$property) {
return null;
}
$data = $property->getData();
if (!$data) {
return null;
}
if (!is_array($data)) {
return null;
}
if (count($data) != 1) {
return null;
}
$onto = head($data);
if (!is_array($onto)) {
return null;
}
$type = idx($onto, 'type');
if ($type != 'branch') {
return null;
}
return idx($onto, 'name');
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new DifferentialDiffEditor();
}
public function getApplicationTransactionTemplate() {
return new DifferentialDiffTransaction();
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$viewer = $engine->getViewer();
$this->openTransaction();
$this->delete();
foreach ($this->loadChangesets() as $changeset) {
$engine->destroyObject($changeset);
}
$properties = id(new DifferentialDiffProperty())->loadAllWhere(
'diffID = %d',
$this->getID());
foreach ($properties as $prop) {
$prop->delete();
}
$viewstate_query = id(new DifferentialViewStateQuery())
->setViewer($viewer)
->withObjectPHIDs(array($this->getPHID()));
$viewstates = new PhabricatorQueryIterator($viewstate_query);
foreach ($viewstates as $viewstate) {
$viewstate->delete();
}
$this->saveTransaction();
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('revisionPHID')
->setType('phid')
->setDescription(pht('Associated revision PHID.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('authorPHID')
->setType('phid')
->setDescription(pht('Revision author PHID.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('repositoryPHID')
->setType('phid')
->setDescription(pht('Associated repository PHID.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('refs')
->setType('map<string, wild>')
->setDescription(pht('List of related VCS references.')),
);
}
public function getFieldValuesForConduit() {
$refs = array();
$branch = $this->getBranch();
if (strlen($branch)) {
$refs[] = array(
'type' => 'branch',
'name' => $branch,
);
}
$onto = $this->loadTargetBranch();
if (strlen($onto)) {
$refs[] = array(
'type' => 'onto',
'name' => $onto,
);
}
$base = $this->getSourceControlBaseRevision();
if (strlen($base)) {
$refs[] = array(
'type' => 'base',
'identifier' => $base,
);
}
$bookmark = $this->getBookmark();
if (strlen($bookmark)) {
$refs[] = array(
'type' => 'bookmark',
'name' => $bookmark,
);
}
$revision_phid = null;
if ($this->getRevisionID()) {
$revision_phid = $this->getRevision()->getPHID();
}
return array(
'revisionPHID' => $revision_phid,
'authorPHID' => $this->getAuthorPHID(),
'repositoryPHID' => $this->getRepositoryPHID(),
'refs' => $refs,
);
}
public function getConduitSearchAttachments() {
return array(
id(new DifferentialCommitsSearchEngineAttachment())
->setAttachmentKey('commits'),
);
}
}
diff --git a/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php
index deda3d43d7..86ec7b7466 100644
--- a/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php
+++ b/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php
@@ -1,566 +1,574 @@
<?php
final class DiffusionBrowseQueryConduitAPIMethod
extends DiffusionQueryConduitAPIMethod {
public function getAPIMethodName() {
return 'diffusion.browsequery';
}
public function getMethodDescription() {
return pht(
'File(s) information for a repository at an (optional) path and '.
'(optional) commit.');
}
protected function defineReturnType() {
return 'array';
}
protected function defineCustomParamTypes() {
return array(
'path' => 'optional string',
'commit' => 'optional string',
'needValidityOnly' => 'optional bool',
'limit' => 'optional int',
'offset' => 'optional int',
);
}
protected function getResult(ConduitAPIRequest $request) {
$result = parent::getResult($request);
return $result->toDictionary();
}
protected function getGitResult(ConduitAPIRequest $request) {
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$path = $request->getValue('path');
if (!strlen($path) || $path === '/') {
$path = null;
}
$commit = $request->getValue('commit');
$offset = (int)$request->getValue('offset');
$limit = (int)$request->getValue('limit');
$result = $this->getEmptyResultSet();
if ($path === null) {
// Fast path to improve the performance of the repository view; we know
// the root is always a tree at any commit and always exists.
$path_type = 'tree';
} else {
try {
list($stdout) = $repository->execxLocalCommand(
'cat-file -t -- %s',
sprintf('%s:%s', $commit, $path));
$path_type = trim($stdout);
} catch (CommandException $e) {
// The "cat-file" command may fail if the path legitimately does not
// exist, but it may also fail if the path is a submodule. This can
// produce either "Not a valid object name" or "could not get object
// info".
// To detect if we have a submodule, use `git ls-tree`. If the path
// is a submodule, we'll get a "160000" mode mask with type "commit".
list($sub_err, $sub_stdout) = $repository->execLocalCommand(
'ls-tree %s -- %s',
gitsprintf('%s', $commit),
$path);
if (!$sub_err) {
// If the path failed "cat-file" but "ls-tree" worked, we assume it
// must be a submodule. If it is, the output will look something
// like this:
//
// 160000 commit <hash> <path>
//
// We make sure it has the 160000 mode mask to confirm that it's
// definitely a submodule.
$mode = (int)$sub_stdout;
if ($mode & 160000) {
$submodule_reason = DiffusionBrowseResultSet::REASON_IS_SUBMODULE;
$result
->setReasonForEmptyResultSet($submodule_reason);
return $result;
}
}
$stderr = $e->getStderr();
if (preg_match('/^fatal: Not a valid object name/', $stderr)) {
// Grab two logs, since the first one is when the object was deleted.
list($stdout) = $repository->execxLocalCommand(
'log -n2 %s %s -- %s',
'--format=%H',
gitsprintf('%s', $commit),
$path);
$stdout = trim($stdout);
if ($stdout) {
$commits = explode("\n", $stdout);
$result
->setReasonForEmptyResultSet(
DiffusionBrowseResultSet::REASON_IS_DELETED)
->setDeletedAtCommit(idx($commits, 0))
->setExistedAtCommit(idx($commits, 1));
return $result;
}
$result->setReasonForEmptyResultSet(
DiffusionBrowseResultSet::REASON_IS_NONEXISTENT);
return $result;
} else {
throw $e;
}
}
}
if ($path_type === 'blob') {
$result->setReasonForEmptyResultSet(
DiffusionBrowseResultSet::REASON_IS_FILE);
return $result;
}
$result->setIsValidResults(true);
if ($this->shouldOnlyTestValidity($request)) {
return $result;
}
if ($path === null) {
list($stdout) = $repository->execxLocalCommand(
'ls-tree -z -l %s --',
gitsprintf('%s', $commit));
} else {
if ($path_type === 'tree') {
$path = rtrim($path, '/').'/';
} else {
$path = rtrim($path, '/');
}
list($stdout) = $repository->execxLocalCommand(
'ls-tree -z -l %s -- %s',
gitsprintf('%s', $commit),
$path);
}
$submodules = array();
$count = 0;
$results = array();
$lines = empty($stdout)
? array()
: explode("\0", rtrim($stdout));
foreach ($lines as $line) {
// NOTE: Limit to 5 components so we parse filenames with spaces in them
// correctly.
// NOTE: The output uses a mixture of tabs and one-or-more spaces to
// delimit fields.
$parts = preg_split('/\s+/', $line, 5);
if (count($parts) < 5) {
throw new Exception(
pht(
'Expected "<mode> <type> <hash> <size>\t<name>", for ls-tree of '.
'"%s:%s", got: %s',
$commit,
$path,
$line));
}
list($mode, $type, $hash, $size, $full_path) = $parts;
$path_result = new DiffusionRepositoryPath();
if ($type == 'tree') {
$file_type = DifferentialChangeType::FILE_DIRECTORY;
} else if ($type == 'commit') {
$file_type = DifferentialChangeType::FILE_SUBMODULE;
$submodules[] = $path_result;
} else {
$mode = intval($mode, 8);
if (($mode & 0120000) == 0120000) {
$file_type = DifferentialChangeType::FILE_SYMLINK;
} else {
$file_type = DifferentialChangeType::FILE_NORMAL;
}
}
if ($path === null) {
$local_path = $full_path;
} else {
$local_path = basename($full_path);
}
$path_result->setFullPath($full_path);
$path_result->setPath($local_path);
$path_result->setHash($hash);
$path_result->setFileType($file_type);
$path_result->setFileSize($size);
if ($count >= $offset) {
$results[] = $path_result;
}
$count++;
if ($limit && $count >= ($offset + $limit)) {
break;
}
}
// If we identified submodules, lookup the module info at this commit to
// find their source URIs.
if ($submodules) {
// NOTE: We need to read the file out of git and write it to a temporary
// location because "git config -f" doesn't accept a "commit:path"-style
// argument.
// NOTE: This file may not exist, e.g. because the commit author removed
// it when they added the submodule. See T1448. If it's not present, just
// show the submodule without enriching it. If ".gitmodules" was removed
// it seems to partially break submodules, but the repository as a whole
// continues to work fine and we've seen at least two cases of this in
// the wild.
list($err, $contents) = $repository->execLocalCommand(
'cat-file blob -- %s:.gitmodules',
$commit);
if (!$err) {
- $tmp = new TempFile();
- Filesystem::writeFile($tmp, $contents);
- list($module_info) = $repository->execxLocalCommand(
- 'config -l -f %s',
- $tmp);
+
+ // NOTE: After T13673, the user executing "git" may not be the same
+ // as the user this process is running as (usually the webserver user),
+ // so we can't reliably use a temporary file: the daemon user may not
+ // be able to use it.
+
+ // Use "--file -" to read from stdin instead. If this fails in some
+ // older versions of Git, we could exempt this particular command from
+ // sudoing to the daemon user.
+
+ $future = $repository->getLocalCommandFuture('config -l --file - --');
+ $future->write($contents);
+ list($module_info) = $future->resolvex();
$dict = array();
$lines = explode("\n", trim($module_info));
foreach ($lines as $line) {
list($key, $value) = explode('=', $line, 2);
$parts = explode('.', $key);
$dict[$key] = $value;
}
foreach ($submodules as $submodule_path) {
$full_path = $submodule_path->getFullPath();
$key = 'submodule.'.$full_path.'.url';
if (isset($dict[$key])) {
$submodule_path->setExternalURI($dict[$key]);
}
}
}
}
return $result->setPaths($results);
}
protected function getMercurialResult(ConduitAPIRequest $request) {
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$path = $request->getValue('path');
$commit = $request->getValue('commit');
$offset = (int)$request->getValue('offset');
$limit = (int)$request->getValue('limit');
$result = $this->getEmptyResultSet();
$entire_manifest = id(new DiffusionLowLevelMercurialPathsQuery())
->setRepository($repository)
->withCommit($commit)
->withPath($path)
->execute();
$results = array();
$match_against = trim($path, '/');
$match_len = strlen($match_against);
// For the root, don't trim. For other paths, trim the "/" after we match.
// We need this because Mercurial's canonical paths have no leading "/",
// but ours do.
$trim_len = $match_len ? $match_len + 1 : 0;
$count = 0;
foreach ($entire_manifest as $path) {
if (strncmp($path, $match_against, $match_len)) {
continue;
}
if (!strlen($path)) {
continue;
}
$remainder = substr($path, $trim_len);
if (!strlen($remainder)) {
// There is a file with this exact name in the manifest, so clearly
// it's a file.
$result->setReasonForEmptyResultSet(
DiffusionBrowseResultSet::REASON_IS_FILE);
return $result;
}
$parts = explode('/', $remainder);
$name = reset($parts);
// If we've already seen this path component, we're looking at a file
// inside a directory we already processed. Just move on.
if (isset($results[$name])) {
continue;
}
if (count($parts) == 1) {
$type = DifferentialChangeType::FILE_NORMAL;
} else {
$type = DifferentialChangeType::FILE_DIRECTORY;
}
if ($count >= $offset) {
$results[$name] = $type;
}
$count++;
if ($limit && ($count >= ($offset + $limit))) {
break;
}
}
foreach ($results as $key => $type) {
$path_result = new DiffusionRepositoryPath();
$path_result->setPath($key);
$path_result->setFileType($type);
$path_result->setFullPath(ltrim($match_against.'/', '/').$key);
$results[$key] = $path_result;
}
$valid_results = true;
if (empty($results)) {
// TODO: Detect "deleted" by issuing "hg log"?
$result->setReasonForEmptyResultSet(
DiffusionBrowseResultSet::REASON_IS_NONEXISTENT);
$valid_results = false;
}
return $result
->setPaths($results)
->setIsValidResults($valid_results);
}
protected function getSVNResult(ConduitAPIRequest $request) {
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$path = $request->getValue('path');
$commit = $request->getValue('commit');
$offset = (int)$request->getValue('offset');
$limit = (int)$request->getValue('limit');
$result = $this->getEmptyResultSet();
$subpath = $repository->getDetail('svn-subpath');
if ($subpath && strncmp($subpath, $path, strlen($subpath))) {
// If we have a subpath and the path isn't a child of it, it (almost
// certainly) won't exist since we don't track commits which affect
// it. (Even if it exists, return a consistent result.)
$result->setReasonForEmptyResultSet(
DiffusionBrowseResultSet::REASON_IS_UNTRACKED_PARENT);
return $result;
}
$conn_r = $repository->establishConnection('r');
$parent_path = DiffusionPathIDQuery::getParentPath($path);
$path_query = new DiffusionPathIDQuery(
array(
$path,
$parent_path,
));
$path_map = $path_query->loadPathIDs();
$path_id = $path_map[$path];
$parent_path_id = $path_map[$parent_path];
if (empty($path_id)) {
$result->setReasonForEmptyResultSet(
DiffusionBrowseResultSet::REASON_IS_NONEXISTENT);
return $result;
}
if ($commit) {
$slice_clause = qsprintf($conn_r, 'AND svnCommit <= %d', $commit);
} else {
$slice_clause = qsprintf($conn_r, '');
}
$index = queryfx_all(
$conn_r,
'SELECT pathID, max(svnCommit) maxCommit FROM %T WHERE
repositoryID = %d AND parentID = %d
%Q GROUP BY pathID',
PhabricatorRepository::TABLE_FILESYSTEM,
$repository->getID(),
$path_id,
$slice_clause);
if (!$index) {
if ($path == '/') {
$result->setReasonForEmptyResultSet(
DiffusionBrowseResultSet::REASON_IS_EMPTY);
} else {
// NOTE: The parent path ID is included so this query can take
// advantage of the table's primary key; it is uniquely determined by
// the pathID but if we don't do the lookup ourselves MySQL doesn't have
// the information it needs to avoid a table scan.
$reasons = queryfx_all(
$conn_r,
'SELECT * FROM %T WHERE repositoryID = %d
AND parentID = %d
AND pathID = %d
%Q ORDER BY svnCommit DESC LIMIT 2',
PhabricatorRepository::TABLE_FILESYSTEM,
$repository->getID(),
$parent_path_id,
$path_id,
$slice_clause);
$reason = reset($reasons);
if (!$reason) {
$result->setReasonForEmptyResultSet(
DiffusionBrowseResultSet::REASON_IS_NONEXISTENT);
} else {
$file_type = $reason['fileType'];
if (empty($reason['existed'])) {
$result->setReasonForEmptyResultSet(
DiffusionBrowseResultSet::REASON_IS_DELETED);
$result->setDeletedAtCommit($reason['svnCommit']);
if (!empty($reasons[1])) {
$result->setExistedAtCommit($reasons[1]['svnCommit']);
}
} else if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
$result->setReasonForEmptyResultSet(
DiffusionBrowseResultSet::REASON_IS_EMPTY);
} else {
$result->setReasonForEmptyResultSet(
DiffusionBrowseResultSet::REASON_IS_FILE);
}
}
}
return $result;
}
$result->setIsValidResults(true);
if ($this->shouldOnlyTestValidity($request)) {
return $result;
}
$sql = array();
foreach ($index as $row) {
$sql[] = qsprintf(
$conn_r,
'(pathID = %d AND svnCommit = %d)',
$row['pathID'],
$row['maxCommit']);
}
$browse = queryfx_all(
$conn_r,
'SELECT *, p.path pathName
FROM %T f JOIN %T p ON f.pathID = p.id
WHERE repositoryID = %d
AND parentID = %d
AND existed = 1
AND (%LO)
ORDER BY pathName',
PhabricatorRepository::TABLE_FILESYSTEM,
PhabricatorRepository::TABLE_PATH,
$repository->getID(),
$path_id,
$sql);
$loadable_commits = array();
foreach ($browse as $key => $file) {
// We need to strip out directories because we don't store last-modified
// in the filesystem table.
if ($file['fileType'] != DifferentialChangeType::FILE_DIRECTORY) {
$loadable_commits[] = $file['svnCommit'];
$browse[$key]['hasCommit'] = true;
}
}
$commits = array();
$commit_data = array();
if ($loadable_commits) {
// NOTE: Even though these are integers, use '%Ls' because MySQL doesn't
// use the second part of the key otherwise!
$commits = id(new PhabricatorRepositoryCommit())->loadAllWhere(
'repositoryID = %d AND commitIdentifier IN (%Ls)',
$repository->getID(),
$loadable_commits);
$commits = mpull($commits, null, 'getCommitIdentifier');
if ($commits) {
$commit_data = id(new PhabricatorRepositoryCommitData())->loadAllWhere(
'commitID in (%Ld)',
mpull($commits, 'getID'));
$commit_data = mpull($commit_data, null, 'getCommitID');
} else {
$commit_data = array();
}
}
$path_normal = DiffusionPathIDQuery::normalizePath($path);
$results = array();
$count = 0;
foreach ($browse as $file) {
$full_path = $file['pathName'];
$file_path = ltrim(substr($full_path, strlen($path_normal)), '/');
$full_path = ltrim($full_path, '/');
$result_path = new DiffusionRepositoryPath();
$result_path->setPath($file_path);
$result_path->setFullPath($full_path);
$result_path->setFileType($file['fileType']);
if (!empty($file['hasCommit'])) {
$commit = idx($commits, $file['svnCommit']);
if ($commit) {
$data = idx($commit_data, $commit->getID());
$result_path->setLastModifiedCommit($commit);
$result_path->setLastCommitData($data);
}
}
if ($count >= $offset) {
$results[] = $result_path;
}
$count++;
if ($limit && ($count >= ($offset + $limit))) {
break;
}
}
if (empty($results)) {
$result->setReasonForEmptyResultSet(
DiffusionBrowseResultSet::REASON_IS_EMPTY);
}
return $result->setPaths($results);
}
private function getEmptyResultSet() {
return id(new DiffusionBrowseResultSet())
->setPaths(array())
->setReasonForEmptyResultSet(null)
->setIsValidResults(false);
}
private function shouldOnlyTestValidity(ConduitAPIRequest $request) {
return $request->getValue('needValidityOnly', false);
}
}
diff --git a/src/applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php
index 88d216e4a1..4d651b4e52 100644
--- a/src/applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php
+++ b/src/applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php
@@ -1,61 +1,61 @@
<?php
final class DiffusionLookSoonConduitAPIMethod
extends DiffusionConduitAPIMethod {
public function getAPIMethodName() {
return 'diffusion.looksoon';
}
public function getMethodStatus() {
return self::METHOD_STATUS_UNSTABLE;
}
public function getMethodDescription() {
return pht(
- 'Advises Phabricator to look for new commits in a repository as soon '.
+ 'Advises this server to look for new commits in a repository as soon '.
'as possible. This advice is most useful if you have just pushed new '.
'commits to that repository.');
}
protected function defineReturnType() {
return 'void';
}
protected function defineParamTypes() {
return array(
'callsigns' => 'optional list<string> (deprecated)',
'repositories' => 'optional list<string>',
'urgency' => 'optional string',
);
}
protected function execute(ConduitAPIRequest $request) {
// NOTE: The "urgency" parameter does nothing, it is just a hilarious joke
// which exemplifies the boundless clever wit of this project.
$identifiers = $request->getValue('repositories');
if (!$identifiers) {
$identifiers = $request->getValue('callsigns');
}
if (!$identifiers) {
return null;
}
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($request->getUser())
->withIdentifiers($identifiers)
->execute();
foreach ($repositories as $repository) {
$repository->writeStatusMessage(
PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE,
PhabricatorRepositoryStatusMessage::CODE_OKAY);
}
return null;
}
}
diff --git a/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php b/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php
index be889b4cc4..f1451be247 100644
--- a/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php
+++ b/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php
@@ -1,166 +1,165 @@
<?php
final class PhabricatorDiffusionConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Diffusion');
}
public function getDescription() {
return pht('Configure Diffusion repository browsing.');
}
public function getIcon() {
return 'fa-code';
}
public function getGroup() {
return 'apps';
}
public function getOptions() {
$custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType';
$fields = array(
new PhabricatorCommitRepositoryField(),
new PhabricatorCommitBranchesField(),
new PhabricatorCommitTagsField(),
new PhabricatorCommitMergedCommitsField(),
);
$default_fields = array();
foreach ($fields as $field) {
$default_fields[$field->getFieldKey()] = array(
'disabled' => $field->shouldDisableByDefault(),
);
}
return array(
$this->newOption(
'metamta.diffusion.attach-patches',
'bool',
false)
->setBoolOptions(
array(
pht('Attach Patches'),
pht('Do Not Attach Patches'),
))
->setDescription(
pht(
'Set this to true if you want patches to be attached to commit '.
'notifications from Diffusion.')),
$this->newOption('metamta.diffusion.inline-patches', 'int', 0)
->setSummary(pht('Include patches in Diffusion mail as body text.'))
->setDescription(
pht(
'To include patches in Diffusion email bodies, set this to a '.
'positive integer. Patches will be inlined if they are at most '.
'that many lines. By default, patches are not inlined.')),
$this->newOption('metamta.diffusion.byte-limit', 'int', 1024 * 1024)
->setDescription(pht('Hard byte limit on including patches in email.')),
$this->newOption('metamta.diffusion.time-limit', 'int', 60)
->setDescription(pht('Hard time limit on generating patches.')),
$this->newOption(
'audit.can-author-close-audit',
'bool',
false)
->setBoolOptions(
array(
pht('Enable Self-Accept'),
pht('Disable Self-Accept'),
))
->setDescription(
pht(
'Allows the author of a commit to be an auditor and accept their '.
'own commits. Note that this behavior is different from the '.
'behavior implied by the name of the option: long ago, it did '.
'something else.')),
$this->newOption('bugtraq.url', 'string', null)
->addExample('https://bugs.php.net/%BUGID%', pht('PHP bugs'))
->addExample('/%BUGID%', pht('Local Maniphest URL'))
->setDescription(
pht(
'URL of external bug tracker used by Diffusion. %s will be '.
'substituted by the bug ID.',
'%BUGID%')),
$this->newOption('bugtraq.logregex', 'list<regex>', array())
->addExample(array('/\B#([1-9]\d*)\b/'), pht('Issue #123'))
->addExample(
array('/[Ii]ssues?:?(\s*,?\s*#\d+)+/', '/(\d+)/'),
pht('Issue #123, #456'))
->addExample(array('/(?<!#)\b(T[1-9]\d*)\b/'), pht('Task T123'))
->addExample('/[A-Z]{2,}-\d+/', pht('JIRA-1234'))
->setDescription(
pht(
'Regular expression to link external bug tracker. See '.
'http://tortoisesvn.net/docs/release/TortoiseSVN_en/'.
'tsvn-dug-bugtracker.html for further explanation.')),
$this->newOption('diffusion.allow-http-auth', 'bool', false)
->setBoolOptions(
array(
pht('Allow HTTP Basic Auth'),
pht('Disallow HTTP Basic Auth'),
))
->setSummary(pht('Enable HTTP Basic Auth for repositories.'))
->setDescription(
pht(
- "Phabricator can serve repositories over HTTP, using HTTP basic ".
+ "This server can serve repositories over HTTP, using HTTP basic ".
"auth.\n\n".
"Because HTTP basic auth is less secure than SSH auth, it is ".
"disabled by default. You can enable it here if you'd like to use ".
"it anyway. There's nothing fundamentally insecure about it as ".
- "long as Phabricator uses HTTPS, but it presents a much lower ".
+ "long as this server uses HTTPS, but it presents a much lower ".
"barrier to attackers than SSH does.\n\n".
"Consider using SSH for authenticated access to repositories ".
"instead of HTTP.")),
$this->newOption('diffusion.allow-git-lfs', 'bool', false)
->setBoolOptions(
array(
pht('Allow Git LFS'),
pht('Disallow Git LFS'),
))
->setLocked(true)
->setSummary(pht('Allow Git Large File Storage (LFS).'))
->setDescription(
pht(
- 'Phabricator supports Git LFS, a Git extension for storing large '.
+ 'This server supports Git LFS, a Git extension for storing large '.
'files alongside a repository. Activate this setting to allow '.
- 'the extension to store file data in Phabricator.')),
+ 'the extension to store file data.')),
$this->newOption('diffusion.ssh-user', 'string', null)
->setLocked(true)
->setSummary(pht('Login username for SSH connections to repositories.'))
->setDescription(
pht(
'When constructing clone URIs to show to users, Diffusion will '.
'fill in this login username. If you have configured a VCS user '.
'like `git`, you should provide it here.')),
$this->newOption('diffusion.ssh-port', 'int', null)
->setLocked(true)
->setSummary(pht('Port for SSH connections to repositories.'))
->setDescription(
pht(
'When constructing clone URIs to show to users, Diffusion by '.
'default will not display a port assuming the default for your '.
'VCS. Explicitly declare when running on a non-standard port.')),
$this->newOption('diffusion.ssh-host', 'string', null)
->setLocked(true)
->setSummary(pht('Host for SSH connections to repositories.'))
->setDescription(
pht(
- 'If you accept Phabricator SSH traffic on a different host '.
- 'from web traffic (for example, if you use different SSH and '.
- 'web load balancers), you can set the SSH hostname here. This '.
- 'is an advanced option.')),
+ 'If you accept SSH traffic on a different host from web traffic '.
+ '(for example, if you use different SSH and web load balancers), '.
+ 'you can set the SSH hostname here. This is an advanced option.')),
$this->newOption('diffusion.fields', $custom_field_type, $default_fields)
->setCustomData(
id(new PhabricatorRepositoryCommit())
->getCustomFieldBaseClass())
->setDescription(
pht('Select and reorder Diffusion fields.')),
);
}
}
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php
index 9503a9e386..50f1d34da1 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php
@@ -1,88 +1,88 @@
<?php
final class DiffusionRepositoryEditDangerousController
extends DiffusionRepositoryManageController {
public function handleRequest(AphrontRequest $request) {
$response = $this->loadDiffusionContextForEdit();
if ($response) {
return $response;
}
$viewer = $this->getViewer();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$panel_uri = id(new DiffusionRepositoryBasicsManagementPanel())
->setRepository($repository)
->getPanelURI();
if (!$repository->canAllowDangerousChanges()) {
return $this->newDialog()
->setTitle(pht('Unprotectable Repository'))
->appendParagraph(
pht(
'This repository can not be protected from dangerous changes '.
- 'because Phabricator does not control what users are allowed '.
+ 'because this server does not control what users are allowed '.
'to push to it.'))
->addCancelButton($panel_uri);
}
if ($request->isFormPost()) {
$xaction = id(new PhabricatorRepositoryTransaction())
->setTransactionType(
PhabricatorRepositoryDangerousTransaction::TRANSACTIONTYPE)
->setNewValue(!$repository->shouldAllowDangerousChanges());
$editor = id(new PhabricatorRepositoryEditor())
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request)
->setActor($viewer)
->applyTransactions($repository, array($xaction));
return id(new AphrontReloadResponse())->setURI($panel_uri);
}
$force = phutil_tag('tt', array(), '--force');
if ($repository->shouldAllowDangerousChanges()) {
$title = pht('Prevent Dangerous Changes');
if ($repository->isSVN()) {
$body = pht(
'It will no longer be possible to edit revprops in this '.
'repository.');
} else {
$body = pht(
'It will no longer be possible to delete branches from this '.
'repository, or %s push to this repository.',
$force);
}
$submit = pht('Prevent Dangerous Changes');
} else {
$title = pht('Allow Dangerous Changes');
if ($repository->isSVN()) {
$body = pht(
'If you allow dangerous changes, it will be possible to edit '.
'reprops in this repository, including arbitrarily rewriting '.
'commit messages. These operations can alter a repository in a '.
'way that is difficult to recover from.');
} else {
$body = pht(
'If you allow dangerous changes, it will be possible to delete '.
'branches and %s push this repository. These operations can '.
'alter a repository in a way that is difficult to recover from.',
$force);
}
$submit = pht('Allow Dangerous Changes');
}
return $this->newDialog()
->setTitle($title)
->appendParagraph($body)
->addSubmitButton($submit)
->addCancelButton($panel_uri);
}
}
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditEnormousController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditEnormousController.php
index 11a3ee3736..3e0b56a758 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditEnormousController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditEnormousController.php
@@ -1,91 +1,91 @@
<?php
final class DiffusionRepositoryEditEnormousController
extends DiffusionRepositoryManageController {
public function handleRequest(AphrontRequest $request) {
$response = $this->loadDiffusionContextForEdit();
if ($response) {
return $response;
}
$viewer = $this->getViewer();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$panel_uri = id(new DiffusionRepositoryBasicsManagementPanel())
->setRepository($repository)
->getPanelURI();
if (!$repository->canAllowEnormousChanges()) {
return $this->newDialog()
->setTitle(pht('Unprotectable Repository'))
->appendParagraph(
pht(
'This repository can not be protected from enormous changes '.
- 'because Phabricator does not control what users are allowed '.
+ 'because this server does not control what users are allowed '.
'to push to it.'))
->addCancelButton($panel_uri);
}
if ($request->isFormPost()) {
$xaction = id(new PhabricatorRepositoryTransaction())
->setTransactionType(
PhabricatorRepositoryEnormousTransaction::TRANSACTIONTYPE)
->setNewValue(!$repository->shouldAllowEnormousChanges());
$editor = id(new PhabricatorRepositoryEditor())
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request)
->setActor($viewer)
->applyTransactions($repository, array($xaction));
return id(new AphrontReloadResponse())->setURI($panel_uri);
}
if ($repository->shouldAllowEnormousChanges()) {
$title = pht('Prevent Enormous Changes');
$body = pht(
'It will no longer be possible to push enormous changes to this '.
'repository.');
$submit = pht('Prevent Enormous Changes');
} else {
$title = pht('Allow Enormous Changes');
$body = array(
pht(
'If you allow enormous changes, users can push commits which are '.
'too large for Herald to process content rules for. This can allow '.
'users to evade content rules implemented in Herald.'),
pht(
'You can selectively configure Herald by adding rules to prevent a '.
'subset of enormous changes (for example, based on who is trying '.
'to push the change).'),
);
$submit = pht('Allow Enormous Changes');
}
$more_help = pht(
'Enormous changes are commits which are too large to process with '.
'content rules because: the diff text for the change is larger than '.
'%s bytes; or the diff text takes more than %s seconds to extract.',
new PhutilNumber(HeraldCommitAdapter::getEnormousByteLimit()),
new PhutilNumber(HeraldCommitAdapter::getEnormousTimeLimit()));
$response = $this->newDialog();
foreach ((array)$body as $paragraph) {
$response->appendParagraph($paragraph);
}
return $response
->setTitle($title)
->appendParagraph($more_help)
->addSubmitButton($submit)
->addCancelButton($panel_uri);
}
}
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php
index dd95654302..88ce864f9a 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php
@@ -1,68 +1,68 @@
<?php
final class DiffusionRepositoryEditUpdateController
extends DiffusionRepositoryManageController {
public function handleRequest(AphrontRequest $request) {
$response = $this->loadDiffusionContextForEdit();
if ($response) {
return $response;
}
$viewer = $this->getViewer();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$panel_uri = id(new DiffusionRepositoryBasicsManagementPanel())
->setRepository($repository)
->getPanelURI();
if ($request->isFormPost()) {
$params = array(
'repositories' => array(
$repository->getPHID(),
),
);
id(new ConduitCall('diffusion.looksoon', $params))
->setUser($viewer)
->execute();
return id(new AphrontRedirectResponse())->setURI($panel_uri);
}
$doc_name = 'Diffusion User Guide: Repository Updates';
$doc_href = PhabricatorEnv::getDoclink($doc_name);
$doc_link = phutil_tag(
'a',
array(
'href' => $doc_href,
'target' => '_blank',
),
$doc_name);
return $this->newDialog()
->setTitle(pht('Update Repository Now'))
->appendParagraph(
pht(
- 'Normally, Phabricator automatically updates repositories '.
+ 'Normally, repositories are automatically updated '.
'based on how much time has elapsed since the last commit. '.
'This helps reduce load if you have a large number of mostly '.
'inactive repositories, which is common.'))
->appendParagraph(
pht(
'You can manually schedule an update for this repository. The '.
'daemons will perform the update as soon as possible. This may '.
'be helpful if you have just made a commit to a rarely used '.
'repository.'))
->appendParagraph(
pht(
- 'To learn more about how Phabricator updates repositories, '.
+ 'To learn more about how repositories are updated, '.
'read %s in the documentation.',
$doc_link))
->addCancelButton($panel_uri)
->addSubmitButton(pht('Schedule Update'));
}
}
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryURICredentialController.php b/src/applications/diffusion/controller/DiffusionRepositoryURICredentialController.php
index 96b97673ce..63763a6c04 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryURICredentialController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryURICredentialController.php
@@ -1,159 +1,159 @@
<?php
final class DiffusionRepositoryURICredentialController
extends DiffusionController {
public function handleRequest(AphrontRequest $request) {
$response = $this->loadDiffusionContextForEdit();
if ($response) {
return $response;
}
$viewer = $this->getViewer();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$id = $request->getURIData('id');
$uri = id(new PhabricatorRepositoryURIQuery())
->setViewer($viewer)
->withIDs(array($id))
->withRepositories(array($repository))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$uri) {
return new Aphront404Response();
}
$is_builtin = $uri->isBuiltin();
$has_credential = (bool)$uri->getCredentialPHID();
$view_uri = $uri->getViewURI();
$is_remove = ($request->getURIData('action') == 'remove');
if ($is_builtin) {
return $this->newDialog()
->setTitle(pht('Builtin URIs Do Not Use Credentials'))
->appendParagraph(
pht(
- 'You can not set a credential for builtin URIs which Phabricator '.
- 'hosts and serves. Phabricator does not fetch from these URIs or '.
- 'push to these URIs, and does not need credentials to '.
- 'authenticate any activity against them.'))
+ 'You can not set a credential for builtin URIs which this '.
+ 'server hosts. These URIs are not fetched from or pushed to, '.
+ 'and credentials are not required to authenticate any '.
+ 'activity against them.'))
->addCancelButton($view_uri);
}
if ($request->isFormPost()) {
$xactions = array();
if ($is_remove) {
$new_phid = null;
} else {
$new_phid = $request->getStr('credentialPHID');
}
$type_credential = PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL;
$xactions[] = id(new PhabricatorRepositoryURITransaction())
->setTransactionType($type_credential)
->setNewValue($new_phid);
$editor = id(new DiffusionURIEditor())
->setActor($viewer)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->setContentSourceFromRequest($request)
->applyTransactions($uri, $xactions);
return id(new AphrontRedirectResponse())->setURI($view_uri);
}
$command_engine = $uri->newCommandEngine();
$is_supported = $command_engine->isCredentialSupported();
$body = null;
$form = null;
$width = AphrontDialogView::WIDTH_DEFAULT;
if ($is_remove) {
if ($has_credential) {
$title = pht('Remove Credential');
$body = pht(
'This credential will no longer be used to authenticate activity '.
'against this URI.');
$button = pht('Remove Credential');
} else {
$title = pht('No Credential');
$body = pht(
'This URI does not have an associated credential.');
$button = null;
}
} else if (!$is_supported) {
$title = pht('Unauthenticated Protocol');
$body = pht(
'The protocol for this URI ("%s") does not use authentication, so '.
'you can not provide a credential.',
$command_engine->getDisplayProtocol());
$button = null;
} else {
$effective_uri = $uri->getEffectiveURI();
$label = $command_engine->getPassphraseCredentialLabel();
$credential_type = $command_engine->getPassphraseDefaultCredentialType();
$provides_type = $command_engine->getPassphraseProvidesCredentialType();
$options = id(new PassphraseCredentialQuery())
->setViewer($viewer)
->withIsDestroyed(false)
->withProvidesTypes(array($provides_type))
->execute();
$control = id(new PassphraseCredentialControl())
->setName('credentialPHID')
->setLabel($label)
->setValue($uri->getCredentialPHID())
->setCredentialType($credential_type)
->setOptions($options);
$default_user = $effective_uri->getUser();
if (strlen($default_user)) {
$control->setDefaultUsername($default_user);
}
$form = id(new AphrontFormView())
->setViewer($viewer)
->appendControl($control);
if ($has_credential) {
$title = pht('Update Credential');
$button = pht('Update Credential');
} else {
$title = pht('Set Credential');
$button = pht('Set Credential');
}
$width = AphrontDialogView::WIDTH_FORM;
}
$dialog = $this->newDialog()
->setWidth($width)
->setTitle($title)
->addCancelButton($view_uri);
if ($body) {
$dialog->appendParagraph($body);
}
if ($form) {
$dialog->appendForm($form);
}
if ($button) {
$dialog->addSubmitButton($button);
}
return $dialog;
}
}
diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php
index d0d897e1f6..3040328fbf 100644
--- a/src/applications/diffusion/controller/DiffusionServeController.php
+++ b/src/applications/diffusion/controller/DiffusionServeController.php
@@ -1,1303 +1,1303 @@
<?php
final class DiffusionServeController extends DiffusionController {
private $serviceViewer;
private $serviceRepository;
private $isGitLFSRequest;
private $gitLFSToken;
private $gitLFSInput;
public function setServiceViewer(PhabricatorUser $viewer) {
$this->getRequest()->setUser($viewer);
$this->serviceViewer = $viewer;
return $this;
}
public function getServiceViewer() {
return $this->serviceViewer;
}
public function setServiceRepository(PhabricatorRepository $repository) {
$this->serviceRepository = $repository;
return $this;
}
public function getServiceRepository() {
return $this->serviceRepository;
}
public function getIsGitLFSRequest() {
return $this->isGitLFSRequest;
}
public function getGitLFSToken() {
return $this->gitLFSToken;
}
public function isVCSRequest(AphrontRequest $request) {
$identifier = $this->getRepositoryIdentifierFromRequest($request);
if ($identifier === null) {
return null;
}
$content_type = $request->getHTTPHeader('Content-Type');
$user_agent = idx($_SERVER, 'HTTP_USER_AGENT');
$request_type = $request->getHTTPHeader('X-Phabricator-Request-Type');
// This may have a "charset" suffix, so only match the prefix.
$lfs_pattern = '(^application/vnd\\.git-lfs\\+json(;|\z))';
$vcs = null;
if ($request->getExists('service')) {
$service = $request->getStr('service');
// We get this initially for `info/refs`.
// Git also gives us a User-Agent like "git/1.8.2.3".
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
} else if (strncmp($user_agent, 'git/', 4) === 0) {
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
} else if ($content_type == 'application/x-git-upload-pack-request') {
// We get this for `git-upload-pack`.
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
} else if ($content_type == 'application/x-git-receive-pack-request') {
// We get this for `git-receive-pack`.
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
} else if (preg_match($lfs_pattern, $content_type)) {
// This is a Git LFS HTTP API request.
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
$this->isGitLFSRequest = true;
} else if ($request_type == 'git-lfs') {
// This is a Git LFS object content request.
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
$this->isGitLFSRequest = true;
} else if ($request->getExists('cmd')) {
// Mercurial also sends an Accept header like
// "application/mercurial-0.1", and a User-Agent like
// "mercurial/proto-1.0".
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL;
} else {
// Subversion also sends an initial OPTIONS request (vs GET/POST), and
// has a User-Agent like "SVN/1.8.3 (x86_64-apple-darwin11.4.2)
// serf/1.3.2".
$dav = $request->getHTTPHeader('DAV');
$dav = new PhutilURI($dav);
if ($dav->getDomain() === 'subversion.tigris.org') {
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_SVN;
}
}
return $vcs;
}
public function handleRequest(AphrontRequest $request) {
$service_exception = null;
$response = null;
try {
$response = $this->serveRequest($request);
} catch (Exception $ex) {
$service_exception = $ex;
}
try {
$remote_addr = $request->getRemoteAddress();
if ($request->isHTTPS()) {
$remote_protocol = PhabricatorRepositoryPullEvent::PROTOCOL_HTTPS;
} else {
$remote_protocol = PhabricatorRepositoryPullEvent::PROTOCOL_HTTP;
}
$pull_event = id(new PhabricatorRepositoryPullEvent())
->setEpoch(PhabricatorTime::getNow())
->setRemoteAddress($remote_addr)
->setRemoteProtocol($remote_protocol);
if ($response) {
$response_code = $response->getHTTPResponseCode();
if ($response_code == 200) {
$pull_event
->setResultType(PhabricatorRepositoryPullEvent::RESULT_PULL)
->setResultCode($response_code);
} else {
$pull_event
->setResultType(PhabricatorRepositoryPullEvent::RESULT_ERROR)
->setResultCode($response_code);
}
if ($response instanceof PhabricatorVCSResponse) {
$pull_event->setProperties(
array(
'response.message' => $response->getMessage(),
));
}
} else {
$pull_event
->setResultType(PhabricatorRepositoryPullEvent::RESULT_EXCEPTION)
->setResultCode(500)
->setProperties(
array(
'exception.class' => get_class($ex),
'exception.message' => $ex->getMessage(),
));
}
$viewer = $this->getServiceViewer();
if ($viewer) {
$pull_event->setPullerPHID($viewer->getPHID());
}
$repository = $this->getServiceRepository();
if ($repository) {
$pull_event->setRepositoryPHID($repository->getPHID());
}
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$pull_event->save();
unset($unguarded);
} catch (Exception $ex) {
if ($service_exception) {
throw $service_exception;
}
throw $ex;
}
if ($service_exception) {
throw $service_exception;
}
return $response;
}
private function serveRequest(AphrontRequest $request) {
$identifier = $this->getRepositoryIdentifierFromRequest($request);
// If authentication credentials have been provided, try to find a user
// that actually matches those credentials.
// We require both the username and password to be nonempty, because Git
// won't prompt users who provide a username but no password otherwise.
// See T10797 for discussion.
$have_user = strlen(idx($_SERVER, 'PHP_AUTH_USER'));
$have_pass = strlen(idx($_SERVER, 'PHP_AUTH_PW'));
if ($have_user && $have_pass) {
$username = $_SERVER['PHP_AUTH_USER'];
$password = new PhutilOpaqueEnvelope($_SERVER['PHP_AUTH_PW']);
// Try Git LFS auth first since we can usually reject it without doing
// any queries, since the username won't match the one we expect or the
// request won't be LFS.
$viewer = $this->authenticateGitLFSUser(
$username,
$password,
$identifier);
// If that failed, try normal auth. Note that we can use normal auth on
// LFS requests, so this isn't strictly an alternative to LFS auth.
if (!$viewer) {
$viewer = $this->authenticateHTTPRepositoryUser($username, $password);
}
if (!$viewer) {
return new PhabricatorVCSResponse(
403,
pht('Invalid credentials.'));
}
} else {
// User hasn't provided credentials, which means we count them as
// being "not logged in".
$viewer = new PhabricatorUser();
}
// See T13590. Some pathways, like error handling, may require unusual
// access to things like timezone information. These are fine to build
// inline; this pathway is not lightweight anyway.
$viewer->setAllowInlineCacheGeneration(true);
$this->setServiceViewer($viewer);
$allow_public = PhabricatorEnv::getEnvConfig('policy.allow-public');
$allow_auth = PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth');
if (!$allow_public) {
if (!$viewer->isLoggedIn()) {
if ($allow_auth) {
return new PhabricatorVCSResponse(
401,
pht('You must log in to access repositories.'));
} else {
return new PhabricatorVCSResponse(
403,
pht('Public and authenticated HTTP access are both forbidden.'));
}
}
}
try {
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->withIdentifiers(array($identifier))
->needURIs(true)
->executeOne();
if (!$repository) {
return new PhabricatorVCSResponse(
404,
pht('No such repository exists.'));
}
} catch (PhabricatorPolicyException $ex) {
if ($viewer->isLoggedIn()) {
return new PhabricatorVCSResponse(
403,
pht('You do not have permission to access this repository.'));
} else {
if ($allow_auth) {
return new PhabricatorVCSResponse(
401,
pht('You must log in to access this repository.'));
} else {
return new PhabricatorVCSResponse(
403,
pht(
'This repository requires authentication, which is forbidden '.
'over HTTP.'));
}
}
}
$response = $this->validateGitLFSRequest($repository, $viewer);
if ($response) {
return $response;
}
$this->setServiceRepository($repository);
if (!$repository->isTracked()) {
return new PhabricatorVCSResponse(
403,
pht('This repository is inactive.'));
}
$is_push = !$this->isReadOnlyRequest($repository);
if ($this->getIsGitLFSRequest() && $this->getGitLFSToken()) {
// We allow git LFS requests over HTTP even if the repository does not
// otherwise support HTTP reads or writes, as long as the user is using a
// token from SSH. If they're using HTTP username + password auth, they
// have to obey the normal HTTP rules.
} else {
// For now, we don't distinguish between HTTP and HTTPS-originated
// requests that are proxied within the cluster, so the user can connect
// with HTTPS but we may be on HTTP by the time we reach this part of
// the code. Allow things to move forward as long as either protocol
// can be served.
$proto_https = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTPS;
$proto_http = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTP;
$can_read =
$repository->canServeProtocol($proto_https, false) ||
$repository->canServeProtocol($proto_http, false);
if (!$can_read) {
return new PhabricatorVCSResponse(
403,
pht('This repository is not available over HTTP.'));
}
if ($is_push) {
if ($repository->isReadOnly()) {
return new PhabricatorVCSResponse(
503,
$repository->getReadOnlyMessageForDisplay());
}
$can_write =
$repository->canServeProtocol($proto_https, true) ||
$repository->canServeProtocol($proto_http, true);
if (!$can_write) {
return new PhabricatorVCSResponse(
403,
pht('This repository is read-only over HTTP.'));
}
}
}
if ($is_push) {
$can_push = PhabricatorPolicyFilter::hasCapability(
$viewer,
$repository,
DiffusionPushCapability::CAPABILITY);
if (!$can_push) {
if ($viewer->isLoggedIn()) {
$error_code = 403;
$error_message = pht(
'You do not have permission to push to this repository ("%s").',
$repository->getDisplayName());
if ($this->getIsGitLFSRequest()) {
return DiffusionGitLFSResponse::newErrorResponse(
$error_code,
$error_message);
} else {
return new PhabricatorVCSResponse(
$error_code,
$error_message);
}
} else {
if ($allow_auth) {
return new PhabricatorVCSResponse(
401,
pht('You must log in to push to this repository.'));
} else {
return new PhabricatorVCSResponse(
403,
pht(
'Pushing to this repository requires authentication, '.
'which is forbidden over HTTP.'));
}
}
}
}
$vcs_type = $repository->getVersionControlSystem();
$req_type = $this->isVCSRequest($request);
if ($vcs_type != $req_type) {
switch ($req_type) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$result = new PhabricatorVCSResponse(
500,
pht(
'This repository ("%s") is not a Git repository.',
$repository->getDisplayName()));
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$result = new PhabricatorVCSResponse(
500,
pht(
'This repository ("%s") is not a Mercurial repository.',
$repository->getDisplayName()));
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$result = new PhabricatorVCSResponse(
500,
pht(
'This repository ("%s") is not a Subversion repository.',
$repository->getDisplayName()));
break;
default:
$result = new PhabricatorVCSResponse(
500,
pht('Unknown request type.'));
break;
}
} else {
switch ($vcs_type) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$caught = null;
try {
$result = $this->serveVCSRequest($repository, $viewer);
} catch (Exception $ex) {
$caught = $ex;
} catch (Throwable $ex) {
$caught = $ex;
}
if ($caught) {
// We never expect an uncaught exception here, so dump it to the
// log. All routine errors should have been converted into Response
// objects by a lower layer.
phlog($caught);
$result = new PhabricatorVCSResponse(
500,
phutil_string_cast($caught->getMessage()));
}
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$result = new PhabricatorVCSResponse(
500,
pht(
- 'Phabricator does not support HTTP access to Subversion '.
+ 'This server does not support HTTP access to Subversion '.
'repositories.'));
break;
default:
$result = new PhabricatorVCSResponse(
500,
pht('Unknown version control system.'));
break;
}
}
$code = $result->getHTTPResponseCode();
if ($is_push && ($code == 200)) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$repository->writeStatusMessage(
PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE,
PhabricatorRepositoryStatusMessage::CODE_OKAY);
unset($unguarded);
}
return $result;
}
private function serveVCSRequest(
PhabricatorRepository $repository,
PhabricatorUser $viewer) {
// We can serve Git LFS requests first, since we don't need to proxy them.
// It's also important that LFS requests never fall through to standard
// service pathways, because that would let you use LFS tokens to read
// normal repository data.
if ($this->getIsGitLFSRequest()) {
return $this->serveGitLFSRequest($repository, $viewer);
}
// If this repository is hosted on a service, we need to proxy the request
// to a host which can serve it.
$is_cluster_request = $this->getRequest()->isProxiedClusterRequest();
$uri = $repository->getAlmanacServiceURI(
$viewer,
array(
'neverProxy' => $is_cluster_request,
'protocols' => array(
'http',
'https',
),
'writable' => !$this->isReadOnlyRequest($repository),
));
if ($uri) {
$future = $this->getRequest()->newClusterProxyFuture($uri);
return id(new AphrontHTTPProxyResponse())
->setHTTPFuture($future);
}
// Otherwise, we're going to handle the request locally.
$vcs_type = $repository->getVersionControlSystem();
switch ($vcs_type) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$result = $this->serveGitRequest($repository, $viewer);
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$result = $this->serveMercurialRequest($repository, $viewer);
break;
}
return $result;
}
private function isReadOnlyRequest(
PhabricatorRepository $repository) {
$request = $this->getRequest();
$method = $_SERVER['REQUEST_METHOD'];
// TODO: This implementation is safe by default, but very incomplete.
if ($this->getIsGitLFSRequest()) {
return $this->isGitLFSReadOnlyRequest($repository);
}
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$service = $request->getStr('service');
$path = $this->getRequestDirectoryPath($repository);
// NOTE: Service names are the reverse of what you might expect, as they
// are from the point of view of the server. The main read service is
// "git-upload-pack", and the main write service is "git-receive-pack".
if ($method == 'GET' &&
$path == '/info/refs' &&
$service == 'git-upload-pack') {
return true;
}
if ($path == '/git-upload-pack') {
return true;
}
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$cmd = $request->getStr('cmd');
if ($cmd == 'batch') {
$cmds = idx($this->getMercurialArguments(), 'cmds');
return DiffusionMercurialWireProtocol::isReadOnlyBatchCommand($cmds);
}
return DiffusionMercurialWireProtocol::isReadOnlyCommand($cmd);
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
break;
}
return false;
}
/**
* @phutil-external-symbol class PhabricatorStartup
*/
private function serveGitRequest(
PhabricatorRepository $repository,
PhabricatorUser $viewer) {
$request = $this->getRequest();
$request_path = $this->getRequestDirectoryPath($repository);
$repository_root = $repository->getLocalPath();
// Rebuild the query string to strip `__magic__` parameters and prevent
// issues where we might interpret inputs like "service=read&service=write"
// differently than the server does and pass it an unsafe command.
// NOTE: This does not use getPassthroughRequestParameters() because
// that code is HTTP-method agnostic and will encode POST data.
$query_data = $_GET;
foreach ($query_data as $key => $value) {
if (!strncmp($key, '__', 2)) {
unset($query_data[$key]);
}
}
$query_string = phutil_build_http_querystring($query_data);
// We're about to wipe out PATH with the rest of the environment, so
// resolve the binary first.
$bin = Filesystem::resolveBinary('git-http-backend');
if (!$bin) {
throw new Exception(
pht(
'Unable to find `%s` in %s!',
'git-http-backend',
'$PATH'));
}
// NOTE: We do not set HTTP_CONTENT_ENCODING here, because we already
// decompressed the request when we read the request body, so the body is
// just plain data with no encoding.
$env = array(
'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'],
'QUERY_STRING' => $query_string,
'CONTENT_TYPE' => $request->getHTTPHeader('Content-Type'),
'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'],
'GIT_PROJECT_ROOT' => $repository_root,
'GIT_HTTP_EXPORT_ALL' => '1',
'PATH_INFO' => $request_path,
'REMOTE_USER' => $viewer->getUsername(),
// TODO: Set these correctly.
// GIT_COMMITTER_NAME
// GIT_COMMITTER_EMAIL
) + $this->getCommonEnvironment($viewer);
$input = PhabricatorStartup::getRawInput();
$command = csprintf('%s', $bin);
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$cluster_engine = id(new DiffusionRepositoryClusterEngine())
->setViewer($viewer)
->setRepository($repository);
$did_write_lock = false;
if ($this->isReadOnlyRequest($repository)) {
$cluster_engine->synchronizeWorkingCopyBeforeRead();
} else {
$did_write_lock = true;
$cluster_engine->synchronizeWorkingCopyBeforeWrite();
}
$caught = null;
try {
list($err, $stdout, $stderr) = id(new ExecFuture('%C', $command))
->setEnv($env, true)
->write($input)
->resolve();
} catch (Exception $ex) {
$caught = $ex;
}
if ($did_write_lock) {
$cluster_engine->synchronizeWorkingCopyAfterWrite();
}
unset($unguarded);
if ($caught) {
throw $caught;
}
if ($err) {
if ($this->isValidGitShallowCloneResponse($stdout, $stderr)) {
// Ignore the error if the response passes this special check for
// validity.
$err = 0;
}
}
if ($err) {
return new PhabricatorVCSResponse(
500,
pht(
'Error %d: %s',
$err,
phutil_utf8ize($stderr)));
}
return id(new DiffusionGitResponse())->setGitData($stdout);
}
private function getRequestDirectoryPath(PhabricatorRepository $repository) {
$request = $this->getRequest();
$request_path = $request->getRequestURI()->getPath();
$info = PhabricatorRepository::parseRepositoryServicePath(
$request_path,
$repository->getVersionControlSystem());
$base_path = $info['path'];
// For Git repositories, strip an optional directory component if it
// isn't the name of a known Git resource. This allows users to clone
// repositories as "/diffusion/X/anything.git", for example.
if ($repository->isGit()) {
$known = array(
'info',
'git-upload-pack',
'git-receive-pack',
);
foreach ($known as $key => $path) {
$known[$key] = preg_quote($path, '@');
}
$known = implode('|', $known);
if (preg_match('@^/([^/]+)/('.$known.')(/|$)@', $base_path)) {
$base_path = preg_replace('@^/([^/]+)@', '', $base_path);
}
}
return $base_path;
}
private function authenticateGitLFSUser(
$username,
PhutilOpaqueEnvelope $password,
$identifier) {
// Never accept these credentials for requests which aren't LFS requests.
if (!$this->getIsGitLFSRequest()) {
return null;
}
// If we have the wrong username, don't bother checking if the token
// is right.
if ($username !== DiffusionGitLFSTemporaryTokenType::HTTP_USERNAME) {
return null;
}
// See PHI1123. We need to be able to constrain the token query with
// "withTokenResources(...)" to take advantage of the key on the table.
// In this case, the repository PHID is the "resource" we're after.
// In normal workflows, we figure out the viewer first, then use the
// viewer to load the repository, but that won't work here. Load the
// repository as the omnipotent viewer, then use the repository PHID to
// look for a token.
$omnipotent_viewer = PhabricatorUser::getOmnipotentUser();
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($omnipotent_viewer)
->withIdentifiers(array($identifier))
->executeOne();
if (!$repository) {
return null;
}
$lfs_pass = $password->openEnvelope();
$lfs_hash = PhabricatorHash::weakDigest($lfs_pass);
$token = id(new PhabricatorAuthTemporaryTokenQuery())
->setViewer($omnipotent_viewer)
->withTokenResources(array($repository->getPHID()))
->withTokenTypes(array(DiffusionGitLFSTemporaryTokenType::TOKENTYPE))
->withTokenCodes(array($lfs_hash))
->withExpired(false)
->executeOne();
if (!$token) {
return null;
}
$user = id(new PhabricatorPeopleQuery())
->setViewer($omnipotent_viewer)
->withPHIDs(array($token->getUserPHID()))
->executeOne();
if (!$user) {
return null;
}
if (!$user->isUserActivated()) {
return null;
}
$this->gitLFSToken = $token;
return $user;
}
private function authenticateHTTPRepositoryUser(
$username,
PhutilOpaqueEnvelope $password) {
if (!PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth')) {
// No HTTP auth permitted.
return null;
}
if (!strlen($username)) {
// No username.
return null;
}
if (!strlen($password->openEnvelope())) {
// No password.
return null;
}
$user = id(new PhabricatorPeopleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withUsernames(array($username))
->executeOne();
if (!$user) {
// Username doesn't match anything.
return null;
}
if (!$user->isUserActivated()) {
// User is not activated.
return null;
}
$request = $this->getRequest();
$content_source = PhabricatorContentSource::newFromRequest($request);
$engine = id(new PhabricatorAuthPasswordEngine())
->setViewer($user)
->setContentSource($content_source)
->setPasswordType(PhabricatorAuthPassword::PASSWORD_TYPE_VCS)
->setObject($user);
if (!$engine->isValidPassword($password)) {
return null;
}
return $user;
}
private function serveMercurialRequest(
PhabricatorRepository $repository,
PhabricatorUser $viewer) {
$request = $this->getRequest();
$bin = Filesystem::resolveBinary('hg');
if (!$bin) {
throw new Exception(
pht(
'Unable to find `%s` in %s!',
'hg',
'$PATH'));
}
$env = $this->getCommonEnvironment($viewer);
$input = PhabricatorStartup::getRawInput();
$cmd = $request->getStr('cmd');
$args = $this->getMercurialArguments();
$args = $this->formatMercurialArguments($cmd, $args);
if (strlen($input)) {
$input = strlen($input)."\n".$input."0\n";
}
$command = csprintf(
'%s -R %s serve --stdio',
$bin,
$repository->getLocalPath());
$command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
list($err, $stdout, $stderr) = id(new ExecFuture('%C', $command))
->setEnv($env, true)
->setCWD($repository->getLocalPath())
->write("{$cmd}\n{$args}{$input}")
->resolve();
if ($err) {
return new PhabricatorVCSResponse(
500,
pht('Error %d: %s', $err, $stderr));
}
if ($cmd == 'getbundle' ||
$cmd == 'changegroup' ||
$cmd == 'changegroupsubset') {
// We're not completely sure that "changegroup" and "changegroupsubset"
// actually work, they're for very old Mercurial.
$body = gzcompress($stdout);
} else if ($cmd == 'unbundle') {
// This includes diagnostic information and anything echoed by commit
// hooks. We ignore `stdout` since it just has protocol garbage, and
// substitute `stderr`.
$body = strlen($stderr)."\n".$stderr;
} else {
list($length, $body) = explode("\n", $stdout, 2);
if ($cmd == 'capabilities') {
$body = DiffusionMercurialWireProtocol::filterBundle2Capability($body);
}
}
return id(new DiffusionMercurialResponse())->setContent($body);
}
private function getMercurialArguments() {
// Mercurial sends arguments in HTTP headers. "Why?", you might wonder,
// "Why would you do this?".
$args_raw = array();
for ($ii = 1;; $ii++) {
$header = 'HTTP_X_HGARG_'.$ii;
if (!array_key_exists($header, $_SERVER)) {
break;
}
$args_raw[] = $_SERVER[$header];
}
$args_raw = implode('', $args_raw);
return id(new PhutilQueryStringParser())
->parseQueryString($args_raw);
}
private function formatMercurialArguments($command, array $arguments) {
$spec = DiffusionMercurialWireProtocol::getCommandArgs($command);
$out = array();
// Mercurial takes normal arguments like this:
//
// name <length(value)>
// value
$has_star = false;
foreach ($spec as $arg_key) {
if ($arg_key == '*') {
$has_star = true;
continue;
}
if (isset($arguments[$arg_key])) {
$value = $arguments[$arg_key];
$size = strlen($value);
$out[] = "{$arg_key} {$size}\n{$value}";
unset($arguments[$arg_key]);
}
}
if ($has_star) {
// Mercurial takes arguments for variable argument lists roughly like
// this:
//
// * <count(args)>
// argname1 <length(argvalue1)>
// argvalue1
// argname2 <length(argvalue2)>
// argvalue2
$count = count($arguments);
$out[] = "* {$count}\n";
foreach ($arguments as $key => $value) {
if (in_array($key, $spec)) {
// We already added this argument above, so skip it.
continue;
}
$size = strlen($value);
$out[] = "{$key} {$size}\n{$value}";
}
}
return implode('', $out);
}
private function isValidGitShallowCloneResponse($stdout, $stderr) {
// If you execute `git clone --depth N ...`, git sends a request which
// `git-http-backend` responds to by emitting valid output and then exiting
// with a failure code and an error message. If we ignore this error,
// everything works.
// This is a pretty funky fix: it would be nice to more precisely detect
// that a request is a `--depth N` clone request, but we don't have any code
// to decode protocol frames yet. Instead, look for reasonable evidence
// in the output that we're looking at a `--depth` clone.
// A valid x-git-upload-pack-result response during packfile negotiation
// should end with a flush packet ("0000"). As long as that packet
// terminates the response body in the response, we'll assume the response
// is correct and complete.
// See https://git-scm.com/docs/pack-protocol#_packfile_negotiation
$stdout_regexp = '(^Content-Type: application/x-git-upload-pack-result)m';
$has_pack = preg_match($stdout_regexp, $stdout);
if (strlen($stdout) >= 4) {
$has_flush_packet = (substr($stdout, -4) === "0000");
} else {
$has_flush_packet = false;
}
return ($has_pack && $has_flush_packet);
}
private function getCommonEnvironment(PhabricatorUser $viewer) {
$remote_address = $this->getRequest()->getRemoteAddress();
return array(
DiffusionCommitHookEngine::ENV_USER => $viewer->getUsername(),
DiffusionCommitHookEngine::ENV_REMOTE_ADDRESS => $remote_address,
DiffusionCommitHookEngine::ENV_REMOTE_PROTOCOL => 'http',
);
}
private function validateGitLFSRequest(
PhabricatorRepository $repository,
PhabricatorUser $viewer) {
if (!$this->getIsGitLFSRequest()) {
return null;
}
if (!$repository->canUseGitLFS()) {
return new PhabricatorVCSResponse(
403,
pht(
'The requested repository ("%s") does not support Git LFS.',
$repository->getDisplayName()));
}
// If this is using an LFS token, sanity check that we're using it on the
// correct repository. This shouldn't really matter since the user could
// just request a proper token anyway, but it suspicious and should not
// be permitted.
$token = $this->getGitLFSToken();
if ($token) {
$resource = $token->getTokenResource();
if ($resource !== $repository->getPHID()) {
return new PhabricatorVCSResponse(
403,
pht(
'The authentication token provided in the request is bound to '.
'a different repository than the requested repository ("%s").',
$repository->getDisplayName()));
}
}
return null;
}
private function serveGitLFSRequest(
PhabricatorRepository $repository,
PhabricatorUser $viewer) {
if (!$this->getIsGitLFSRequest()) {
throw new Exception(pht('This is not a Git LFS request!'));
}
$path = $this->getGitLFSRequestPath($repository);
$matches = null;
if (preg_match('(^upload/(.*)\z)', $path, $matches)) {
$oid = $matches[1];
return $this->serveGitLFSUploadRequest($repository, $viewer, $oid);
} else if ($path == 'objects/batch') {
return $this->serveGitLFSBatchRequest($repository, $viewer);
} else {
return DiffusionGitLFSResponse::newErrorResponse(
404,
pht(
'Git LFS operation "%s" is not supported by this server.',
$path));
}
}
private function serveGitLFSBatchRequest(
PhabricatorRepository $repository,
PhabricatorUser $viewer) {
$input = $this->getGitLFSInput();
$operation = idx($input, 'operation');
switch ($operation) {
case 'upload':
$want_upload = true;
break;
case 'download':
$want_upload = false;
break;
default:
return DiffusionGitLFSResponse::newErrorResponse(
404,
pht(
'Git LFS batch operation "%s" is not supported by this server.',
$operation));
}
$objects = idx($input, 'objects', array());
$hashes = array();
foreach ($objects as $object) {
$hashes[] = idx($object, 'oid');
}
if ($hashes) {
$refs = id(new PhabricatorRepositoryGitLFSRefQuery())
->setViewer($viewer)
->withRepositoryPHIDs(array($repository->getPHID()))
->withObjectHashes($hashes)
->execute();
$refs = mpull($refs, null, 'getObjectHash');
} else {
$refs = array();
}
$file_phids = mpull($refs, 'getFilePHID');
if ($file_phids) {
$files = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs($file_phids)
->execute();
$files = mpull($files, null, 'getPHID');
} else {
$files = array();
}
$authorization = null;
$output = array();
foreach ($objects as $object) {
$oid = idx($object, 'oid');
$size = idx($object, 'size');
$ref = idx($refs, $oid);
$error = null;
// NOTE: If we already have a ref for this object, we only emit a
// "download" action. The client should not upload the file again.
$actions = array();
if ($ref) {
$file = idx($files, $ref->getFilePHID());
if ($file) {
// Git LFS may prompt users for authentication if the action does
// not provide an "Authorization" header and does not have a query
// parameter named "token". See here for discussion:
// <https://github.com/github/git-lfs/issues/1088>
$no_authorization = 'Basic '.base64_encode('none');
$get_uri = $file->getCDNURI('data');
$actions['download'] = array(
'href' => $get_uri,
'header' => array(
'Authorization' => $no_authorization,
'X-Phabricator-Request-Type' => 'git-lfs',
),
);
} else {
$error = array(
'code' => 404,
'message' => pht(
'Object "%s" was previously uploaded, but no longer exists '.
'on this server.',
$oid),
);
}
} else if ($want_upload) {
if (!$authorization) {
// Here, we could reuse the existing authorization if we have one,
// but it's a little simpler to just generate a new one
// unconditionally.
$authorization = $this->newGitLFSHTTPAuthorization(
$repository,
$viewer,
$operation);
}
$put_uri = $repository->getGitLFSURI("info/lfs/upload/{$oid}");
$actions['upload'] = array(
'href' => $put_uri,
'header' => array(
'Authorization' => $authorization,
'X-Phabricator-Request-Type' => 'git-lfs',
),
);
}
$object = array(
'oid' => $oid,
'size' => $size,
);
if ($actions) {
$object['actions'] = $actions;
}
if ($error) {
$object['error'] = $error;
}
$output[] = $object;
}
$output = array(
'objects' => $output,
);
return id(new DiffusionGitLFSResponse())
->setContent($output);
}
private function serveGitLFSUploadRequest(
PhabricatorRepository $repository,
PhabricatorUser $viewer,
$oid) {
$ref = id(new PhabricatorRepositoryGitLFSRefQuery())
->setViewer($viewer)
->withRepositoryPHIDs(array($repository->getPHID()))
->withObjectHashes(array($oid))
->executeOne();
if ($ref) {
return DiffusionGitLFSResponse::newErrorResponse(
405,
pht(
'Content for object "%s" is already known to this server. It can '.
'not be uploaded again.',
$oid));
}
// Remove the execution time limit because uploading large files may take
// a while.
set_time_limit(0);
$request_stream = new AphrontRequestStream();
$request_iterator = $request_stream->getIterator();
$hashing_iterator = id(new PhutilHashingIterator($request_iterator))
->setAlgorithm('sha256');
$source = id(new PhabricatorIteratorFileUploadSource())
->setName('lfs-'.$oid)
->setViewPolicy(PhabricatorPolicies::POLICY_NOONE)
->setIterator($hashing_iterator);
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$file = $source->uploadFile();
unset($unguarded);
$hash = $hashing_iterator->getHash();
if ($hash !== $oid) {
return DiffusionGitLFSResponse::newErrorResponse(
400,
pht(
'Uploaded data is corrupt or invalid. Expected hash "%s", actual '.
'hash "%s".',
$oid,
$hash));
}
$ref = id(new PhabricatorRepositoryGitLFSRef())
->setRepositoryPHID($repository->getPHID())
->setObjectHash($hash)
->setByteSize($file->getByteSize())
->setAuthorPHID($viewer->getPHID())
->setFilePHID($file->getPHID());
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
// Attach the file to the repository to give users permission
// to access it.
$file->attachToObject($repository->getPHID());
$ref->save();
unset($unguarded);
// This is just a plain HTTP 200 with no content, which is what `git lfs`
// expects.
return new DiffusionGitLFSResponse();
}
private function newGitLFSHTTPAuthorization(
PhabricatorRepository $repository,
PhabricatorUser $viewer,
$operation) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$authorization = DiffusionGitLFSTemporaryTokenType::newHTTPAuthorization(
$repository,
$viewer,
$operation);
unset($unguarded);
return $authorization;
}
private function getGitLFSRequestPath(PhabricatorRepository $repository) {
$request_path = $this->getRequestDirectoryPath($repository);
$matches = null;
if (preg_match('(^/info/lfs(?:\z|/)(.*))', $request_path, $matches)) {
return $matches[1];
}
return null;
}
private function getGitLFSInput() {
if (!$this->gitLFSInput) {
$input = PhabricatorStartup::getRawInput();
$input = phutil_json_decode($input);
$this->gitLFSInput = $input;
}
return $this->gitLFSInput;
}
private function isGitLFSReadOnlyRequest(PhabricatorRepository $repository) {
if (!$this->getIsGitLFSRequest()) {
return false;
}
$path = $this->getGitLFSRequestPath($repository);
if ($path === 'objects/batch') {
$input = $this->getGitLFSInput();
$operation = idx($input, 'operation');
switch ($operation) {
case 'download':
return true;
default:
return false;
}
}
return false;
}
}
diff --git a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php
index a026e176fc..07213afd2b 100644
--- a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php
+++ b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php
@@ -1,525 +1,525 @@
<?php
final class DiffusionRepositoryEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'diffusion.repository';
private $versionControlSystem;
public function setVersionControlSystem($version_control_system) {
$this->versionControlSystem = $version_control_system;
return $this;
}
public function getVersionControlSystem() {
return $this->versionControlSystem;
}
public function isEngineConfigurable() {
return false;
}
public function isDefaultQuickCreateEngine() {
return true;
}
public function getQuickCreateOrderVector() {
return id(new PhutilSortVector())->addInt(300);
}
public function getEngineName() {
return pht('Repositories');
}
public function getSummaryHeader() {
return pht('Edit Repositories');
}
public function getSummaryText() {
return pht('Creates and edits repositories.');
}
public function getEngineApplicationClass() {
return 'PhabricatorDiffusionApplication';
}
protected function newEditableObject() {
$viewer = $this->getViewer();
$repository = PhabricatorRepository::initializeNewRepository($viewer);
$repository->setDetail('newly-initialized', true);
$vcs = $this->getVersionControlSystem();
if ($vcs) {
$repository->setVersionControlSystem($vcs);
}
// Pick a random open service to allocate this repository on, if any exist.
// If there are no services, we aren't in cluster mode and will allocate
// locally. If there are services but none permit allocations, we fail.
// Eventually we can make this more flexible, but this rule is a reasonable
// starting point as we begin to deploy cluster services.
$services = id(new AlmanacServiceQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withServiceTypes(
array(
AlmanacClusterRepositoryServiceType::SERVICETYPE,
))
->needProperties(true)
->execute();
if ($services) {
// Filter out services which do not permit new allocations.
foreach ($services as $key => $possible_service) {
if ($possible_service->getAlmanacPropertyValue('closed')) {
unset($services[$key]);
}
}
if (!$services) {
throw new Exception(
pht(
'This install is configured in cluster mode, but all available '.
'repository cluster services are closed to new allocations. '.
'At least one service must be open to allow new allocations to '.
'take place.'));
}
shuffle($services);
$service = head($services);
$repository->setAlmanacServicePHID($service->getPHID());
}
return $repository;
}
protected function newObjectQuery() {
return new PhabricatorRepositoryQuery();
}
protected function getObjectCreateTitleText($object) {
return pht('Create Repository');
}
protected function getObjectCreateButtonText($object) {
return pht('Create Repository');
}
protected function getObjectEditTitleText($object) {
return pht('Edit Repository: %s', $object->getName());
}
protected function getObjectEditShortText($object) {
return $object->getDisplayName();
}
protected function getObjectCreateShortText() {
return pht('Create Repository');
}
protected function getObjectName() {
return pht('Repository');
}
protected function getObjectViewURI($object) {
return $object->getPathURI('manage/');
}
protected function getCreateNewObjectPolicy() {
return $this->getApplication()->getPolicy(
DiffusionCreateRepositoriesCapability::CAPABILITY);
}
protected function newPages($object) {
$panels = DiffusionRepositoryManagementPanel::getAllPanels();
$pages = array();
$uris = array();
foreach ($panels as $panel_key => $panel) {
$panel->setRepository($object);
$uris[$panel_key] = $panel->getPanelURI();
$page = $panel->newEditEnginePage();
if (!$page) {
continue;
}
$pages[] = $page;
}
$basics_key = DiffusionRepositoryBasicsManagementPanel::PANELKEY;
$basics_uri = $uris[$basics_key];
$more_pages = array(
id(new PhabricatorEditPage())
->setKey('encoding')
->setLabel(pht('Text Encoding'))
->setViewURI($basics_uri)
->setFieldKeys(
array(
'encoding',
)),
id(new PhabricatorEditPage())
->setKey('extensions')
->setLabel(pht('Extensions'))
->setIsDefault(true),
);
foreach ($more_pages as $page) {
$pages[] = $page;
}
return $pages;
}
protected function willConfigureFields($object, array $fields) {
// Change the default field order so related fields are adjacent.
$after = array(
'policy.edit' => array('policy.push'),
);
$result = array();
foreach ($fields as $key => $value) {
$result[$key] = $value;
if (!isset($after[$key])) {
continue;
}
foreach ($after[$key] as $next_key) {
if (!isset($fields[$next_key])) {
continue;
}
unset($result[$next_key]);
$result[$next_key] = $fields[$next_key];
unset($fields[$next_key]);
}
}
return $result;
}
protected function buildCustomEditFields($object) {
$viewer = $this->getViewer();
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($object)
->execute();
$fetch_value = $object->getFetchRules();
$track_value = $object->getTrackOnlyRules();
$permanent_value = $object->getPermanentRefRules();
$automation_instructions = pht(
- "Configure **Repository Automation** to allow Phabricator to ".
+ "Configure **Repository Automation** to allow this server to ".
"write to this repository.".
"\n\n".
"IMPORTANT: This feature is new, experimental, and not supported. ".
"Use it at your own risk.");
$staging_instructions = pht(
"To make it easier to run integration tests and builds on code ".
"under review, you can configure a **Staging Area**. When `arc` ".
"creates a diff, it will push a copy of the changes to the ".
"configured staging area with a corresponding tag.".
"\n\n".
"IMPORTANT: This feature is new, experimental, and not supported. ".
"Use it at your own risk.");
$subpath_instructions = pht(
'If you want to import only part of a repository, like `trunk/`, '.
- 'you can set a path in **Import Only**. Phabricator will ignore '.
+ 'you can set a path in **Import Only**. The import process will ignore '.
'commits which do not affect this path.');
$filesize_warning = null;
if ($object->isGit()) {
$git_binary = PhutilBinaryAnalyzer::getForBinary('git');
$git_version = $git_binary->getBinaryVersion();
$filesize_version = '1.8.4';
if (version_compare($git_version, $filesize_version, '<')) {
$filesize_warning = pht(
'(WARNING) {icon exclamation-triangle} The version of "git" ("%s") '.
'installed on this server does not support '.
'"--batch-check=<format>", a feature required to enforce filesize '.
'limits. Upgrade to "git" %s or newer to use this feature.',
$git_version,
$filesize_version);
}
}
$track_instructions = pht(
'WARNING: The "Track Only" feature is deprecated. Use "Fetch Refs" '.
'and "Permanent Refs" instead. This feature will be removed in a '.
- 'future version of Phabricator.');
+ 'future version of this software.');
return array(
id(new PhabricatorSelectEditField())
->setKey('vcs')
->setLabel(pht('Version Control System'))
->setTransactionType(
PhabricatorRepositoryVCSTransaction::TRANSACTIONTYPE)
->setIsFormField(false)
->setIsCopyable(true)
->setOptions(PhabricatorRepositoryType::getAllRepositoryTypes())
->setDescription(pht('Underlying repository version control system.'))
->setConduitDescription(
pht(
'Choose which version control system to use when creating a '.
'repository.'))
->setConduitTypeDescription(pht('Version control system selection.'))
->setValue($object->getVersionControlSystem()),
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setIsRequired(true)
->setTransactionType(
PhabricatorRepositoryNameTransaction::TRANSACTIONTYPE)
->setDescription(pht('The repository name.'))
->setConduitDescription(pht('Rename the repository.'))
->setConduitTypeDescription(pht('New repository name.'))
->setValue($object->getName()),
id(new PhabricatorTextEditField())
->setKey('callsign')
->setLabel(pht('Callsign'))
->setTransactionType(
PhabricatorRepositoryCallsignTransaction::TRANSACTIONTYPE)
->setDescription(pht('The repository callsign.'))
->setConduitDescription(pht('Change the repository callsign.'))
->setConduitTypeDescription(pht('New repository callsign.'))
->setValue($object->getCallsign()),
id(new PhabricatorTextEditField())
->setKey('shortName')
->setLabel(pht('Short Name'))
->setTransactionType(
PhabricatorRepositorySlugTransaction::TRANSACTIONTYPE)
->setDescription(pht('Short, unique repository name.'))
->setConduitDescription(pht('Change the repository short name.'))
->setConduitTypeDescription(pht('New short name for the repository.'))
->setValue($object->getRepositorySlug()),
id(new PhabricatorRemarkupEditField())
->setKey('description')
->setLabel(pht('Description'))
->setTransactionType(
PhabricatorRepositoryDescriptionTransaction::TRANSACTIONTYPE)
->setDescription(pht('Repository description.'))
->setConduitDescription(pht('Change the repository description.'))
->setConduitTypeDescription(pht('New repository description.'))
->setValue($object->getDetail('description')),
id(new PhabricatorTextEditField())
->setKey('encoding')
->setLabel(pht('Text Encoding'))
->setIsCopyable(true)
->setTransactionType(
PhabricatorRepositoryEncodingTransaction::TRANSACTIONTYPE)
->setDescription(pht('Default text encoding.'))
->setConduitDescription(pht('Change the default text encoding.'))
->setConduitTypeDescription(pht('New text encoding.'))
->setValue($object->getDetail('encoding')),
id(new PhabricatorBoolEditField())
->setKey('allowDangerousChanges')
->setLabel(pht('Allow Dangerous Changes'))
->setIsCopyable(true)
->setIsFormField(false)
->setOptions(
pht('Prevent Dangerous Changes'),
pht('Allow Dangerous Changes'))
->setTransactionType(
PhabricatorRepositoryDangerousTransaction::TRANSACTIONTYPE)
->setDescription(pht('Permit dangerous changes to be made.'))
->setConduitDescription(pht('Allow or prevent dangerous changes.'))
->setConduitTypeDescription(pht('New protection setting.'))
->setValue($object->shouldAllowDangerousChanges()),
id(new PhabricatorBoolEditField())
->setKey('allowEnormousChanges')
->setLabel(pht('Allow Enormous Changes'))
->setIsCopyable(true)
->setIsFormField(false)
->setOptions(
pht('Prevent Enormous Changes'),
pht('Allow Enormous Changes'))
->setTransactionType(
PhabricatorRepositoryEnormousTransaction::TRANSACTIONTYPE)
->setDescription(pht('Permit enormous changes to be made.'))
->setConduitDescription(pht('Allow or prevent enormous changes.'))
->setConduitTypeDescription(pht('New protection setting.'))
->setValue($object->shouldAllowEnormousChanges()),
id(new PhabricatorSelectEditField())
->setKey('status')
->setLabel(pht('Status'))
->setTransactionType(
PhabricatorRepositoryActivateTransaction::TRANSACTIONTYPE)
->setIsFormField(false)
->setOptions(PhabricatorRepository::getStatusNameMap())
->setDescription(pht('Active or inactive status.'))
->setConduitDescription(pht('Active or deactivate the repository.'))
->setConduitTypeDescription(pht('New repository status.'))
->setValue($object->getStatus()),
id(new PhabricatorTextEditField())
->setKey('defaultBranch')
->setLabel(pht('Default Branch'))
->setTransactionType(
PhabricatorRepositoryDefaultBranchTransaction::TRANSACTIONTYPE)
->setIsCopyable(true)
->setDescription(pht('Default branch name.'))
->setConduitDescription(pht('Set the default branch name.'))
->setConduitTypeDescription(pht('New default branch name.'))
->setValue($object->getDetail('default-branch')),
id(new PhabricatorTextAreaEditField())
->setIsStringList(true)
->setKey('fetchRefs')
->setLabel(pht('Fetch Refs'))
->setTransactionType(
PhabricatorRepositoryFetchRefsTransaction::TRANSACTIONTYPE)
->setIsCopyable(true)
->setDescription(pht('Fetch only these refs.'))
->setConduitDescription(pht('Set the fetched refs.'))
->setConduitTypeDescription(pht('New fetched refs.'))
->setValue($fetch_value),
id(new PhabricatorTextAreaEditField())
->setIsStringList(true)
->setKey('permanentRefs')
->setLabel(pht('Permanent Refs'))
->setTransactionType(
PhabricatorRepositoryPermanentRefsTransaction::TRANSACTIONTYPE)
->setIsCopyable(true)
->setDescription(pht('Only these refs are considered permanent.'))
->setConduitDescription(pht('Set the permanent refs.'))
->setConduitTypeDescription(pht('New permanent ref rules.'))
->setValue($permanent_value),
id(new PhabricatorTextAreaEditField())
->setIsStringList(true)
->setKey('trackOnly')
->setLabel(pht('Track Only'))
->setTransactionType(
PhabricatorRepositoryTrackOnlyTransaction::TRANSACTIONTYPE)
->setIsCopyable(true)
->setControlInstructions($track_instructions)
->setDescription(pht('Track only these branches.'))
->setConduitDescription(pht('Set the tracked branches.'))
->setConduitTypeDescription(pht('New tracked branches.'))
->setValue($track_value),
id(new PhabricatorTextEditField())
->setKey('importOnly')
->setLabel(pht('Import Only'))
->setTransactionType(
PhabricatorRepositorySVNSubpathTransaction::TRANSACTIONTYPE)
->setIsCopyable(true)
->setDescription(pht('Subpath to selectively import.'))
->setConduitDescription(pht('Set the subpath to import.'))
->setConduitTypeDescription(pht('New subpath to import.'))
->setValue($object->getDetail('svn-subpath'))
->setControlInstructions($subpath_instructions),
id(new PhabricatorTextEditField())
->setKey('stagingAreaURI')
->setLabel(pht('Staging Area URI'))
->setTransactionType(
PhabricatorRepositoryStagingURITransaction::TRANSACTIONTYPE)
->setIsCopyable(true)
->setDescription(pht('Staging area URI.'))
->setConduitDescription(pht('Set the staging area URI.'))
->setConduitTypeDescription(pht('New staging area URI.'))
->setValue($object->getStagingURI())
->setControlInstructions($staging_instructions),
id(new PhabricatorDatasourceEditField())
->setKey('automationBlueprintPHIDs')
->setLabel(pht('Use Blueprints'))
->setTransactionType(
PhabricatorRepositoryBlueprintsTransaction::TRANSACTIONTYPE)
->setIsCopyable(true)
->setDatasource(new DrydockBlueprintDatasource())
->setDescription(pht('Automation blueprints.'))
->setConduitDescription(pht('Change automation blueprints.'))
->setConduitTypeDescription(pht('New blueprint PHIDs.'))
->setValue($object->getAutomationBlueprintPHIDs())
->setControlInstructions($automation_instructions),
id(new PhabricatorStringListEditField())
->setKey('symbolLanguages')
->setLabel(pht('Languages'))
->setTransactionType(
PhabricatorRepositorySymbolLanguagesTransaction::TRANSACTIONTYPE)
->setIsCopyable(true)
->setDescription(
pht('Languages which define symbols in this repository.'))
->setConduitDescription(
pht('Change symbol languages for this repository.'))
->setConduitTypeDescription(
pht('New symbol languages.'))
->setValue($object->getSymbolLanguages()),
id(new PhabricatorDatasourceEditField())
->setKey('symbolRepositoryPHIDs')
->setLabel(pht('Uses Symbols From'))
->setTransactionType(
PhabricatorRepositorySymbolSourcesTransaction::TRANSACTIONTYPE)
->setIsCopyable(true)
->setDatasource(new DiffusionRepositoryDatasource())
->setDescription(pht('Repositories to link symbols from.'))
->setConduitDescription(pht('Change symbol source repositories.'))
->setConduitTypeDescription(pht('New symbol repositories.'))
->setValue($object->getSymbolSources()),
id(new PhabricatorBoolEditField())
->setKey('publish')
->setLabel(pht('Publish/Notify'))
->setTransactionType(
PhabricatorRepositoryNotifyTransaction::TRANSACTIONTYPE)
->setIsCopyable(true)
->setOptions(
pht('Disable Notifications, Feed, and Herald'),
pht('Enable Notifications, Feed, and Herald'))
->setDescription(pht('Configure how changes are published.'))
->setConduitDescription(pht('Change publishing options.'))
->setConduitTypeDescription(pht('New notification setting.'))
->setValue(!$object->isPublishingDisabled()),
id(new PhabricatorPolicyEditField())
->setKey('policy.push')
->setLabel(pht('Push Policy'))
->setAliases(array('push'))
->setIsCopyable(true)
->setCapability(DiffusionPushCapability::CAPABILITY)
->setPolicies($policies)
->setTransactionType(
PhabricatorRepositoryPushPolicyTransaction::TRANSACTIONTYPE)
->setDescription(
pht('Controls who can push changes to the repository.'))
->setConduitDescription(
pht('Change the push policy of the repository.'))
->setConduitTypeDescription(pht('New policy PHID or constant.'))
->setValue($object->getPolicy(DiffusionPushCapability::CAPABILITY)),
id(new PhabricatorTextEditField())
->setKey('filesizeLimit')
->setLabel(pht('Filesize Limit'))
->setTransactionType(
PhabricatorRepositoryFilesizeLimitTransaction::TRANSACTIONTYPE)
->setDescription(pht('Maximum permitted file size.'))
->setConduitDescription(pht('Change the filesize limit.'))
->setConduitTypeDescription(pht('New repository filesize limit.'))
->setControlInstructions($filesize_warning)
->setValue($object->getFilesizeLimit()),
id(new PhabricatorTextEditField())
->setKey('copyTimeLimit')
->setLabel(pht('Clone/Fetch Timeout'))
->setTransactionType(
PhabricatorRepositoryCopyTimeLimitTransaction::TRANSACTIONTYPE)
->setDescription(
pht('Maximum permitted duration of internal clone/fetch.'))
->setConduitDescription(pht('Change the copy time limit.'))
->setConduitTypeDescription(pht('New repository copy time limit.'))
->setValue($object->getCopyTimeLimit()),
id(new PhabricatorTextEditField())
->setKey('touchLimit')
->setLabel(pht('Touched Paths Limit'))
->setTransactionType(
PhabricatorRepositoryTouchLimitTransaction::TRANSACTIONTYPE)
->setDescription(pht('Maximum permitted paths touched per commit.'))
->setConduitDescription(pht('Change the touch limit.'))
->setConduitTypeDescription(pht('New repository touch limit.'))
->setValue($object->getTouchLimit()),
);
}
}
diff --git a/src/applications/diffusion/herald/HeraldCommitAdapter.php b/src/applications/diffusion/herald/HeraldCommitAdapter.php
index b8ebf9c022..af7c757cd9 100644
--- a/src/applications/diffusion/herald/HeraldCommitAdapter.php
+++ b/src/applications/diffusion/herald/HeraldCommitAdapter.php
@@ -1,396 +1,405 @@
<?php
final class HeraldCommitAdapter
extends HeraldAdapter
implements HarbormasterBuildableAdapterInterface {
protected $diff;
protected $revision;
protected $commit;
private $commitDiff;
protected $affectedPaths;
protected $affectedRevision;
protected $affectedPackages;
protected $auditNeededPackages;
private $buildRequests = array();
public function getAdapterApplicationClass() {
return 'PhabricatorDiffusionApplication';
}
protected function newObject() {
return new PhabricatorRepositoryCommit();
}
public function isTestAdapterForObject($object) {
return ($object instanceof PhabricatorRepositoryCommit);
}
public function getAdapterTestDescription() {
return pht(
'Test rules which run after a commit is discovered and imported.');
}
public function newTestAdapter(PhabricatorUser $viewer, $object) {
return id(clone $this)
->setObject($object);
}
protected function initializeNewAdapter() {
$this->commit = $this->newObject();
}
public function setObject($object) {
$viewer = $this->getViewer();
$commit_phid = $object->getPHID();
$commit = id(new DiffusionCommitQuery())
->setViewer($viewer)
->withPHIDs(array($commit_phid))
->needCommitData(true)
->needIdentities(true)
->needAuditRequests(true)
->executeOne();
if (!$commit) {
throw new Exception(
pht(
'Failed to reload commit ("%s") to fetch commit data.',
$commit_phid));
}
$this->commit = $commit;
return $this;
}
public function getObject() {
return $this->commit;
}
public function getAdapterContentType() {
return 'commit';
}
public function getAdapterContentName() {
return pht('Commits');
}
public function getAdapterContentDescription() {
return pht(
"React to new commits appearing in tracked repositories.\n".
"Commit rules can send email, flag commits, trigger audits, ".
"and run build plans.");
}
public function supportsRuleType($rule_type) {
switch ($rule_type) {
case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL:
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
case HeraldRuleTypeConfig::RULE_TYPE_OBJECT:
return true;
default:
return false;
}
}
public function canTriggerOnObject($object) {
if ($object instanceof PhabricatorRepository) {
return true;
}
if ($object instanceof PhabricatorProject) {
return true;
}
return false;
}
public function getTriggerObjectPHIDs() {
$project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
$repository_phid = $this->getRepository()->getPHID();
$commit_phid = $this->getObject()->getPHID();
$phids = array();
$phids[] = $commit_phid;
$phids[] = $repository_phid;
// NOTE: This is projects for the repository, not for the commit. When
// Herald evaluates, commits normally can not have any project tags yet.
$repository_project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$repository_phid,
$project_type);
foreach ($repository_project_phids as $phid) {
$phids[] = $phid;
}
$phids = array_unique($phids);
$phids = array_values($phids);
return $phids;
}
public function explainValidTriggerObjects() {
return pht('This rule can trigger for **repositories** and **projects**.');
}
public function getHeraldName() {
return $this->commit->getMonogram();
}
public function loadAffectedPaths() {
$viewer = $this->getViewer();
if ($this->affectedPaths === null) {
$result = PhabricatorOwnerPathQuery::loadAffectedPaths(
$this->getRepository(),
$this->commit,
$viewer);
$this->affectedPaths = $result;
}
return $this->affectedPaths;
}
public function loadAffectedPackages() {
if ($this->affectedPackages === null) {
$packages = PhabricatorOwnersPackage::loadAffectedPackages(
$this->getRepository(),
$this->loadAffectedPaths());
$this->affectedPackages = $packages;
}
return $this->affectedPackages;
}
public function loadAuditNeededPackages() {
if ($this->auditNeededPackages === null) {
$status_arr = array(
PhabricatorAuditRequestStatus::AUDIT_REQUIRED,
PhabricatorAuditRequestStatus::CONCERNED,
);
$requests = id(new PhabricatorRepositoryAuditRequest())
->loadAllWhere(
'commitPHID = %s AND auditStatus IN (%Ls)',
$this->commit->getPHID(),
$status_arr);
$this->auditNeededPackages = $requests;
}
return $this->auditNeededPackages;
}
public function loadDifferentialRevision() {
if ($this->affectedRevision === null) {
$viewer = $this->getViewer();
// NOTE: The viewer here is omnipotent, which means that Herald discloses
// some information users do not normally have access to when rules load
// the revision related to a commit. See D20468.
// A user who wants to learn about "Dxyz" can write a Herald rule which
// uses all the "Related revision..." fields, then push a commit which
// contains "Differential Revision: Dxyz" in the message to make Herald
// evaluate the commit with "Dxyz" as the related revision.
// At time of writing, this commit will link to the revision and the
// transcript for the commit will disclose some information about the
// revision (like reviewers, subscribers, and build status) which the
// commit author could not otherwise see.
// For now, we just accept this. The disclosures are relatively
// uninteresting and you have to jump through a lot of hoops (and leave
// a lot of evidence) to get this information.
$revision = DiffusionCommitRevisionQuery::loadRevisionForCommit(
$viewer,
$this->getObject());
if ($revision) {
$this->affectedRevision = $revision;
} else {
$this->affectedRevision = false;
}
}
return $this->affectedRevision;
}
public static function getEnormousByteLimit() {
return 256 * 1024 * 1024; // 256MB. See T13142 and T13143.
}
public static function getEnormousTimeLimit() {
return 60 * 15; // 15 Minutes
}
private function loadCommitDiff() {
$viewer = $this->getViewer();
$byte_limit = self::getEnormousByteLimit();
$time_limit = self::getEnormousTimeLimit();
$diff_info = $this->callConduit(
'diffusion.rawdiffquery',
array(
'commit' => $this->commit->getCommitIdentifier(),
'timeout' => $time_limit,
'byteLimit' => $byte_limit,
'linesOfContext' => 0,
));
if ($diff_info['tooHuge']) {
throw new Exception(
pht(
'The raw text of this change is enormous (larger than %s byte(s)). '.
'Herald can not process it.',
new PhutilNumber($byte_limit)));
}
if ($diff_info['tooSlow']) {
throw new Exception(
pht(
'The raw text of this change took too long to process (longer '.
'than %s second(s)). Herald can not process it.',
new PhutilNumber($time_limit)));
}
$file_phid = $diff_info['filePHID'];
$diff_file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($file_phid))
->executeOne();
if (!$diff_file) {
throw new Exception(
pht(
'Failed to load diff ("%s") for this change.',
$file_phid));
}
$raw = $diff_file->loadFileData();
+ // See T13667. This happens when a commit is empty and affects no files.
+ if (!strlen($raw)) {
+ return false;
+ }
+
$parser = new ArcanistDiffParser();
$changes = $parser->parseDiff($raw);
$diff = DifferentialDiff::newEphemeralFromRawChanges(
$changes);
return $diff;
}
public function isDiffEnormous() {
$this->loadDiffContent('*');
return ($this->commitDiff instanceof Exception);
}
public function loadDiffContent($type) {
if ($this->commitDiff === null) {
try {
$this->commitDiff = $this->loadCommitDiff();
} catch (Exception $ex) {
$this->commitDiff = $ex;
phlog($ex);
}
}
+ if ($this->commitDiff === false) {
+ return array();
+ }
+
if ($this->commitDiff instanceof Exception) {
$ex = $this->commitDiff;
$ex_class = get_class($ex);
$ex_message = pht('Failed to load changes: %s', $ex->getMessage());
return array(
'<'.$ex_class.'>' => $ex_message,
);
}
$changes = $this->commitDiff->getChangesets();
$result = array();
foreach ($changes as $change) {
$lines = array();
foreach ($change->getHunks() as $hunk) {
switch ($type) {
case '-':
$lines[] = $hunk->makeOldFile();
break;
case '+':
$lines[] = $hunk->makeNewFile();
break;
case '*':
$lines[] = $hunk->makeChanges();
break;
default:
throw new Exception(pht("Unknown content selection '%s'!", $type));
}
}
$result[$change->getFilename()] = implode("\n", $lines);
}
return $result;
}
public function loadIsMergeCommit() {
$parents = $this->callConduit(
'diffusion.commitparentsquery',
array(
'commit' => $this->getObject()->getCommitIdentifier(),
));
return (count($parents) > 1);
}
private function callConduit($method, array $params) {
$viewer = $this->getViewer();
$drequest = DiffusionRequest::newFromDictionary(
array(
'user' => $viewer,
'repository' => $this->getRepository(),
'commit' => $this->commit->getCommitIdentifier(),
));
return DiffusionQuery::callConduitWithDiffusionRequest(
$viewer,
$drequest,
$method,
$params);
}
private function getRepository() {
return $this->getObject()->getRepository();
}
public function getAuthorPHID() {
return $this->getObject()->getEffectiveAuthorPHID();
}
public function getCommitterPHID() {
$commit = $this->getObject();
if ($commit->hasCommitterIdentity()) {
$identity = $commit->getCommitterIdentity();
return $identity->getCurrentEffectiveUserPHID();
}
return null;
}
/* -( HarbormasterBuildableAdapterInterface )------------------------------ */
public function getHarbormasterBuildablePHID() {
return $this->getObject()->getPHID();
}
public function getHarbormasterContainerPHID() {
return $this->getObject()->getRepository()->getPHID();
}
public function getQueuedHarbormasterBuildRequests() {
return $this->buildRequests;
}
public function queueHarbormasterBuildRequest(
HarbormasterBuildRequest $request) {
$this->buildRequests[] = $request;
}
}
diff --git a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php
index a795c955a7..c4ce110c5d 100644
--- a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php
+++ b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php
@@ -1,160 +1,160 @@
<?php
final class DiffusionRepositoryURIsManagementPanel
extends DiffusionRepositoryManagementPanel {
const PANELKEY = 'uris';
public function getManagementPanelLabel() {
return pht('URIs');
}
public function getManagementPanelIcon() {
return 'fa-globe';
}
public function getManagementPanelOrder() {
return 400;
}
public function buildManagementPanelCurtain() {
$repository = $this->getRepository();
$viewer = $this->getViewer();
$action_list = $this->newActionList();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$repository,
PhabricatorPolicyCapability::CAN_EDIT);
$doc_href = PhabricatorEnv::getDoclink('Diffusion User Guide: URIs');
$add_href = $repository->getPathURI('uri/edit/');
$action_list->addAction(
id(new PhabricatorActionView())
->setIcon('fa-plus')
->setHref($add_href)
->setDisabled(!$can_edit)
->setName(pht('Add New URI')));
$action_list->addAction(
id(new PhabricatorActionView())
->setIcon('fa-book')
->setHref($doc_href)
->setName(pht('URI Documentation')));
return $this->newCurtainView()
->setActionList($action_list);
}
public function buildManagementPanelContent() {
$repository = $this->getRepository();
$viewer = $this->getViewer();
$uris = $repository->getURIs();
Javelin::initBehavior('phabricator-tooltips');
$rows = array();
foreach ($uris as $uri) {
$uri_name = $uri->getDisplayURI();
$uri_name = phutil_tag(
'a',
array(
'href' => $uri->getViewURI(),
),
$uri_name);
if ($uri->getIsDisabled()) {
$status_icon = 'fa-times grey';
} else {
$status_icon = 'fa-check green';
}
$uri_status = id(new PHUIIconView())->setIcon($status_icon);
$io_type = $uri->getEffectiveIOType();
$io_map = PhabricatorRepositoryURI::getIOTypeMap();
$io_spec = idx($io_map, $io_type, array());
$io_icon = idx($io_spec, 'icon');
$io_color = idx($io_spec, 'color');
$io_label = idx($io_spec, 'label', $io_type);
$uri_io = array(
id(new PHUIIconView())->setIcon("{$io_icon} {$io_color}"),
' ',
$io_label,
);
$display_type = $uri->getEffectiveDisplayType();
$display_map = PhabricatorRepositoryURI::getDisplayTypeMap();
$display_spec = idx($display_map, $display_type, array());
$display_icon = idx($display_spec, 'icon');
$display_color = idx($display_spec, 'color');
$display_label = idx($display_spec, 'label', $display_type);
$uri_display = array(
id(new PHUIIconView())->setIcon("{$display_icon} {$display_color}"),
' ',
$display_label,
);
$rows[] = array(
$uri_status,
$uri_name,
$uri_io,
$uri_display,
);
}
$table = id(new AphrontTableView($rows))
->setNoDataString(pht('This repository has no URIs.'))
->setHeaders(
array(
null,
pht('URI'),
pht('I/O'),
pht('Display'),
))
->setColumnClasses(
array(
null,
'pri wide',
null,
null,
));
$is_new = $repository->isNewlyInitialized();
$messages = array();
if ($repository->isHosted()) {
if ($is_new) {
- $host_message = pht('Phabricator will host this repository.');
+ $host_message = pht('This repository will be hosted.');
} else {
- $host_message = pht('Phabricator is hosting this repository.');
+ $host_message = pht('This repository is observed.');
}
$messages[] = $host_message;
} else {
if ($is_new) {
$observe_message = pht(
- 'Phabricator will observe a remote repository.');
+ 'This repository will be observed.');
} else {
$observe_message = pht(
- 'This repository is hosted remotely. Phabricator is observing it.');
+ 'This remote repository is being observed.');
}
$messages[] = $observe_message;
}
$info_view = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->setErrors($messages);
$box = $this->newBox(pht('Repository URIs'), $table);
return array($info_view, $box);
}
}
diff --git a/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php b/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php
index 789adfbf57..3b6d0c0f6b 100644
--- a/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php
+++ b/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php
@@ -1,268 +1,268 @@
<?php
final class DiffusionSetPasswordSettingsPanel extends PhabricatorSettingsPanel {
public function isManagementPanel() {
if ($this->getUser()->getIsMailingList()) {
return false;
}
return true;
}
public function getPanelKey() {
return 'vcspassword';
}
public function getPanelName() {
return pht('VCS Password');
}
public function getPanelMenuIcon() {
return 'fa-code';
}
public function getPanelGroupKey() {
return PhabricatorSettingsAuthenticationPanelGroup::PANELGROUPKEY;
}
public function isEnabled() {
return PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth');
}
public function processRequest(AphrontRequest $request) {
$viewer = $request->getUser();
$user = $this->getUser();
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
$request,
'/settings/');
$vcs_type = PhabricatorAuthPassword::PASSWORD_TYPE_VCS;
$vcspasswords = id(new PhabricatorAuthPasswordQuery())
->setViewer($viewer)
->withObjectPHIDs(array($user->getPHID()))
->withPasswordTypes(array($vcs_type))
->withIsRevoked(false)
->execute();
if ($vcspasswords) {
$vcspassword = head($vcspasswords);
} else {
$vcspassword = PhabricatorAuthPassword::initializeNewPassword(
$user,
$vcs_type);
}
$panel_uri = $this->getPanelURI('?saved=true');
$errors = array();
$e_password = true;
$e_confirm = true;
$content_source = PhabricatorContentSource::newFromRequest($request);
// NOTE: This test is against $viewer (not $user), so that the error
// message below makes sense in the case that the two are different,
// and because an admin reusing their own password is bad, while
// system agents generally do not have passwords anyway.
$engine = id(new PhabricatorAuthPasswordEngine())
->setViewer($viewer)
->setContentSource($content_source)
->setObject($viewer)
->setPasswordType($vcs_type);
if ($request->isFormPost()) {
if ($request->getBool('remove')) {
if ($vcspassword->getID()) {
$vcspassword->delete();
return id(new AphrontRedirectResponse())->setURI($panel_uri);
}
}
$new_password = $request->getStr('password');
$confirm = $request->getStr('confirm');
$envelope = new PhutilOpaqueEnvelope($new_password);
$confirm_envelope = new PhutilOpaqueEnvelope($confirm);
try {
$engine->checkNewPassword($envelope, $confirm_envelope);
$e_password = null;
$e_confirm = null;
} catch (PhabricatorAuthPasswordException $ex) {
$errors[] = $ex->getMessage();
$e_password = $ex->getPasswordError();
$e_confirm = $ex->getConfirmError();
}
if (!$errors) {
$vcspassword
->setPassword($envelope, $user)
->save();
return id(new AphrontRedirectResponse())->setURI($panel_uri);
}
}
$title = pht('Set VCS Password');
$form = id(new AphrontFormView())
->setUser($viewer)
->appendRemarkupInstructions(
pht(
- 'To access repositories hosted by Phabricator over HTTP, you must '.
+ 'To access repositories hosted on this server over HTTP, you must '.
'set a version control password. This password should be unique.'.
"\n\n".
"This password applies to all repositories available over ".
"HTTP."));
if ($vcspassword->getID()) {
$form
->appendChild(
id(new AphrontFormPasswordControl())
->setDisableAutocomplete(true)
->setLabel(pht('Current Password'))
->setDisabled(true)
->setValue('********************'));
} else {
$form
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Current Password'))
->setValue(phutil_tag('em', array(), pht('No Password Set'))));
}
$form
->appendChild(
id(new AphrontFormPasswordControl())
->setDisableAutocomplete(true)
->setName('password')
->setLabel(pht('New VCS Password'))
->setError($e_password))
->appendChild(
id(new AphrontFormPasswordControl())
->setDisableAutocomplete(true)
->setName('confirm')
->setLabel(pht('Confirm VCS Password'))
->setError($e_confirm))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Change Password')));
if (!$vcspassword->getID()) {
$is_serious = PhabricatorEnv::getEnvConfig(
'phabricator.serious-business');
$suggest = Filesystem::readRandomBytes(128);
$suggest = preg_replace('([^A-Za-z0-9/!().,;{}^&*%~])', '', $suggest);
$suggest = substr($suggest, 0, 20);
if ($is_serious) {
$form->appendRemarkupInstructions(
pht(
'Having trouble coming up with a good password? Try this randomly '.
'generated one, made by a computer:'.
"\n\n".
"`%s`",
$suggest));
} else {
$form->appendRemarkupInstructions(
pht(
'Having trouble coming up with a good password? Try this '.
'artisanal password, hand made in small batches by our expert '.
'craftspeople: '.
"\n\n".
"`%s`",
$suggest));
}
}
$hash_envelope = new PhutilOpaqueEnvelope($vcspassword->getPasswordHash());
$form->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Current Algorithm'))
->setValue(
PhabricatorPasswordHasher::getCurrentAlgorithmName($hash_envelope)));
$form->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Best Available Algorithm'))
->setValue(PhabricatorPasswordHasher::getBestAlgorithmName()));
if (strlen($hash_envelope->openEnvelope())) {
try {
$can_upgrade = PhabricatorPasswordHasher::canUpgradeHash(
$hash_envelope);
} catch (PhabricatorPasswordHasherUnavailableException $ex) {
$can_upgrade = false;
$errors[] = pht(
'Your VCS password is currently hashed using an algorithm which is '.
'no longer available on this install.');
$errors[] = pht(
'Because the algorithm implementation is missing, your password '.
'can not be used.');
$errors[] = pht(
'You can set a new password to replace the old password.');
}
if ($can_upgrade) {
$errors[] = pht(
'The strength of your stored VCS password hash can be upgraded. '.
'To upgrade, either: use the password to authenticate with a '.
'repository; or change your password.');
}
}
$object_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->setForm($form)
->setFormErrors($errors);
$remove_form = id(new AphrontFormView())
->setUser($viewer);
if ($vcspassword->getID()) {
$remove_form
->addHiddenInput('remove', true)
->appendRemarkupInstructions(
pht(
'You can remove your VCS password, which will prevent your '.
'account from accessing repositories.'))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Remove Password')));
} else {
$remove_form->appendRemarkupInstructions(
pht(
'You do not currently have a VCS password set. If you set one, you '.
'can remove it here later.'));
}
$remove_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Remove VCS Password'))
->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->setForm($remove_form);
$saved = null;
if ($request->getBool('saved')) {
$saved = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->setTitle(pht('Password Updated'))
->appendChild(pht('Your VCS password has been updated.'));
}
return array(
$saved,
$object_box,
$remove_box,
);
}
}
diff --git a/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php b/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php
index 0372e44f4b..dfc86e5f56 100644
--- a/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php
+++ b/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php
@@ -1,941 +1,942 @@
<?php
/**
* Manages repository synchronization for cluster repositories.
*
* @task config Configuring Synchronization
* @task sync Cluster Synchronization
* @task internal Internals
*/
final class DiffusionRepositoryClusterEngine extends Phobject {
private $repository;
private $viewer;
private $actingAsPHID;
private $logger;
private $clusterWriteLock;
private $clusterWriteVersion;
private $clusterWriteOwner;
/* -( Configuring Synchronization )---------------------------------------- */
public function setRepository(PhabricatorRepository $repository) {
$this->repository = $repository;
return $this;
}
public function getRepository() {
return $this->repository;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setLog(DiffusionRepositoryClusterEngineLogInterface $log) {
$this->logger = $log;
return $this;
}
public function setActingAsPHID($acting_as_phid) {
$this->actingAsPHID = $acting_as_phid;
return $this;
}
public function getActingAsPHID() {
return $this->actingAsPHID;
}
private function getEffectiveActingAsPHID() {
if ($this->actingAsPHID) {
return $this->actingAsPHID;
}
return $this->getViewer()->getPHID();
}
/* -( Cluster Synchronization )-------------------------------------------- */
/**
* Synchronize repository version information after creating a repository.
*
* This initializes working copy versions for all currently bound devices to
* 0, so that we don't get stuck making an ambiguous choice about which
* devices are leaders when we later synchronize before a read.
*
* @task sync
*/
public function synchronizeWorkingCopyAfterCreation() {
if (!$this->shouldEnableSynchronization(false)) {
return;
}
$repository = $this->getRepository();
$repository_phid = $repository->getPHID();
$service = $repository->loadAlmanacService();
if (!$service) {
throw new Exception(pht('Failed to load repository cluster service.'));
}
$bindings = $service->getActiveBindings();
foreach ($bindings as $binding) {
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
$repository_phid,
$binding->getDevicePHID(),
0);
}
return $this;
}
/**
* @task sync
*/
public function synchronizeWorkingCopyAfterHostingChange() {
if (!$this->shouldEnableSynchronization(false)) {
return;
}
$repository = $this->getRepository();
$repository_phid = $repository->getPHID();
$versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
$repository_phid);
$versions = mpull($versions, null, 'getDevicePHID');
// After converting a hosted repository to observed, or vice versa, we
// need to reset version numbers because the clocks for observed and hosted
// repositories run on different units.
// We identify all the cluster leaders and reset their version to 0.
// We identify all the cluster followers and demote them.
// This allows the cluster to start over again at version 0 but keep the
// same leaders.
if ($versions) {
$max_version = (int)max(mpull($versions, 'getRepositoryVersion'));
foreach ($versions as $version) {
$device_phid = $version->getDevicePHID();
if ($version->getRepositoryVersion() == $max_version) {
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
$repository_phid,
$device_phid,
0);
} else {
PhabricatorRepositoryWorkingCopyVersion::demoteDevice(
$repository_phid,
$device_phid);
}
}
}
return $this;
}
/**
* @task sync
*/
public function synchronizeWorkingCopyBeforeRead() {
if (!$this->shouldEnableSynchronization(true)) {
return;
}
$repository = $this->getRepository();
$repository_phid = $repository->getPHID();
$device = AlmanacKeys::getLiveDevice();
$device_phid = $device->getPHID();
$read_lock = PhabricatorRepositoryWorkingCopyVersion::getReadLock(
$repository_phid,
$device_phid);
$lock_wait = phutil_units('2 minutes in seconds');
$this->logLine(
pht(
'Acquiring read lock for repository "%s" on device "%s"...',
$repository->getDisplayName(),
$device->getName()));
try {
$start = PhabricatorTime::getNow();
$read_lock->lock($lock_wait);
$waited = (PhabricatorTime::getNow() - $start);
if ($waited) {
$this->logLine(
pht(
'Acquired read lock after %s second(s).',
new PhutilNumber($waited)));
} else {
$this->logLine(
pht(
'Acquired read lock immediately.'));
}
} catch (PhutilLockException $ex) {
throw new PhutilProxyException(
pht(
'Failed to acquire read lock after waiting %s second(s). You '.
'may be able to retry later. (%s)',
new PhutilNumber($lock_wait),
$ex->getHint()),
$ex);
}
$versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
$repository_phid);
$versions = mpull($versions, null, 'getDevicePHID');
$this_version = idx($versions, $device_phid);
if ($this_version) {
$this_version = (int)$this_version->getRepositoryVersion();
} else {
$this_version = null;
}
if ($versions) {
// This is the normal case, where we have some version information and
// can identify which nodes are leaders. If the current node is not a
// leader, we want to fetch from a leader and then update our version.
$max_version = (int)max(mpull($versions, 'getRepositoryVersion'));
if (($this_version === null) || ($max_version > $this_version)) {
if ($repository->isHosted()) {
$fetchable = array();
foreach ($versions as $version) {
if ($version->getRepositoryVersion() == $max_version) {
$fetchable[] = $version->getDevicePHID();
}
}
$this->synchronizeWorkingCopyFromDevices(
$fetchable,
$this_version,
$max_version);
} else {
$this->synchronizeWorkingCopyFromRemote();
}
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
$repository_phid,
$device_phid,
$max_version);
} else {
$this->logLine(
pht(
'Device "%s" is already a cluster leader and does not need '.
'to be synchronized.',
$device->getName()));
}
$result_version = $max_version;
} else {
// If no version records exist yet, we need to be careful, because we
// can not tell which nodes are leaders.
// There might be several nodes with arbitrary existing data, and we have
// no way to tell which one has the "right" data. If we pick wrong, we
// might erase some or all of the data in the repository.
// Since this is dangerous, we refuse to guess unless there is only one
// device. If we're the only device in the group, we obviously must be
// a leader.
$service = $repository->loadAlmanacService();
if (!$service) {
throw new Exception(pht('Failed to load repository cluster service.'));
}
$bindings = $service->getActiveBindings();
$device_map = array();
foreach ($bindings as $binding) {
$device_map[$binding->getDevicePHID()] = true;
}
if (count($device_map) > 1) {
throw new Exception(
pht(
'Repository "%s" exists on more than one device, but no device '.
- 'has any repository version information. Phabricator can not '.
- 'guess which copy of the existing data is authoritative. Promote '.
- 'a device or see "Ambiguous Leaders" in the documentation.',
+ 'has any repository version information. There is no way for the '.
+ 'software to determine which copy of the existing data is '.
+ 'authoritative. Promote a device or see "Ambiguous Leaders" in '.
+ 'the documentation.',
$repository->getDisplayName()));
}
if (empty($device_map[$device->getPHID()])) {
throw new Exception(
pht(
'Repository "%s" is being synchronized on device "%s", but '.
'this device is not bound to the corresponding cluster '.
'service ("%s").',
$repository->getDisplayName(),
$device->getName(),
$service->getName()));
}
// The current device is the only device in service, so it must be a
// leader. We can safely have any future nodes which come online read
// from it.
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
$repository_phid,
$device_phid,
0);
$result_version = 0;
}
$read_lock->unlock();
return $result_version;
}
/**
* @task sync
*/
public function synchronizeWorkingCopyBeforeWrite() {
if (!$this->shouldEnableSynchronization(true)) {
return;
}
$repository = $this->getRepository();
$viewer = $this->getViewer();
$repository_phid = $repository->getPHID();
$device = AlmanacKeys::getLiveDevice();
$device_phid = $device->getPHID();
$table = new PhabricatorRepositoryWorkingCopyVersion();
$locked_connection = $table->establishConnection('w');
$write_lock = PhabricatorRepositoryWorkingCopyVersion::getWriteLock(
$repository_phid);
$write_lock->setExternalConnection($locked_connection);
$this->logLine(
pht(
'Acquiring write lock for repository "%s"...',
$repository->getDisplayName()));
// See T13590. On the HTTP pathway, it's possible for us to hit the script
// time limit while holding the durable write lock if a user makes a big
// push. Remove the time limit before we acquire the durable lock.
set_time_limit(0);
$lock_wait = phutil_units('2 minutes in seconds');
try {
$write_wait_start = microtime(true);
$start = PhabricatorTime::getNow();
$step_wait = 1;
while (true) {
try {
$write_lock->lock((int)floor($step_wait));
$write_wait_end = microtime(true);
break;
} catch (PhutilLockException $ex) {
$waited = (PhabricatorTime::getNow() - $start);
if ($waited > $lock_wait) {
throw $ex;
}
$this->logActiveWriter($viewer, $repository);
}
// Wait a little longer before the next message we print.
$step_wait = $step_wait + 0.5;
$step_wait = min($step_wait, 3);
}
$waited = (PhabricatorTime::getNow() - $start);
if ($waited) {
$this->logLine(
pht(
'Acquired write lock after %s second(s).',
new PhutilNumber($waited)));
} else {
$this->logLine(
pht(
'Acquired write lock immediately.'));
}
} catch (PhutilLockException $ex) {
throw new PhutilProxyException(
pht(
'Failed to acquire write lock after waiting %s second(s). You '.
'may be able to retry later. (%s)',
new PhutilNumber($lock_wait),
$ex->getHint()),
$ex);
}
$versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
$repository_phid);
foreach ($versions as $version) {
if (!$version->getIsWriting()) {
continue;
}
throw new Exception(
pht(
'An previous write to this repository was interrupted; refusing '.
'new writes. This issue requires operator intervention to resolve, '.
'see "Write Interruptions" in the "Cluster: Repositories" in the '.
'documentation for instructions.'));
}
$read_wait_start = microtime(true);
try {
$max_version = $this->synchronizeWorkingCopyBeforeRead();
} catch (Exception $ex) {
$write_lock->unlock();
throw $ex;
}
$read_wait_end = microtime(true);
$pid = getmypid();
$hash = Filesystem::readRandomCharacters(12);
$this->clusterWriteOwner = "{$pid}.{$hash}";
PhabricatorRepositoryWorkingCopyVersion::willWrite(
$locked_connection,
$repository_phid,
$device_phid,
array(
'userPHID' => $this->getEffectiveActingAsPHID(),
'epoch' => PhabricatorTime::getNow(),
'devicePHID' => $device_phid,
),
$this->clusterWriteOwner);
$this->clusterWriteVersion = $max_version;
$this->clusterWriteLock = $write_lock;
$write_wait = ($write_wait_end - $write_wait_start);
$read_wait = ($read_wait_end - $read_wait_start);
$log = $this->logger;
if ($log) {
$log->writeClusterEngineLogProperty('writeWait', $write_wait);
$log->writeClusterEngineLogProperty('readWait', $read_wait);
}
}
public function synchronizeWorkingCopyAfterDiscovery($new_version) {
if (!$this->shouldEnableSynchronization(true)) {
return;
}
$repository = $this->getRepository();
$repository_phid = $repository->getPHID();
if ($repository->isHosted()) {
return;
}
$device = AlmanacKeys::getLiveDevice();
$device_phid = $device->getPHID();
// NOTE: We are not holding a lock here because this method is only called
// from PhabricatorRepositoryDiscoveryEngine, which already holds a device
// lock. Even if we do race here and record an older version, the
// consequences are mild: we only do extra work to correct it later.
$versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
$repository_phid);
$versions = mpull($versions, null, 'getDevicePHID');
$this_version = idx($versions, $device_phid);
if ($this_version) {
$this_version = (int)$this_version->getRepositoryVersion();
} else {
$this_version = null;
}
if (($this_version === null) || ($new_version > $this_version)) {
PhabricatorRepositoryWorkingCopyVersion::updateVersion(
$repository_phid,
$device_phid,
$new_version);
}
}
/**
* @task sync
*/
public function synchronizeWorkingCopyAfterWrite() {
if (!$this->shouldEnableSynchronization(true)) {
return;
}
if (!$this->clusterWriteLock) {
throw new Exception(
pht(
'Trying to synchronize after write, but not holding a write '.
'lock!'));
}
$repository = $this->getRepository();
$repository_phid = $repository->getPHID();
$device = AlmanacKeys::getLiveDevice();
$device_phid = $device->getPHID();
// It is possible that we've lost the global lock while receiving the push.
// For example, the master database may have been restarted between the
// time we acquired the global lock and now, when the push has finished.
// We wrote a durable lock while we were holding the the global lock,
// essentially upgrading our lock. We can still safely release this upgraded
// lock even if we're no longer holding the global lock.
// If we fail to release the lock, the repository will be frozen until
// an operator can figure out what happened, so we try pretty hard to
// reconnect to the database and release the lock.
$now = PhabricatorTime::getNow();
$duration = phutil_units('5 minutes in seconds');
$try_until = $now + $duration;
$did_release = false;
$already_failed = false;
while (PhabricatorTime::getNow() <= $try_until) {
try {
// NOTE: This means we're still bumping the version when pushes fail. We
// could select only un-rejected events instead to bump a little less
// often.
$new_log = id(new PhabricatorRepositoryPushEventQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withRepositoryPHIDs(array($repository_phid))
->setLimit(1)
->executeOne();
$old_version = $this->clusterWriteVersion;
if ($new_log) {
$new_version = $new_log->getID();
} else {
$new_version = $old_version;
}
PhabricatorRepositoryWorkingCopyVersion::didWrite(
$repository_phid,
$device_phid,
$this->clusterWriteVersion,
$new_version,
$this->clusterWriteOwner);
$did_release = true;
break;
} catch (AphrontConnectionQueryException $ex) {
$connection_exception = $ex;
} catch (AphrontConnectionLostQueryException $ex) {
$connection_exception = $ex;
}
if (!$already_failed) {
$already_failed = true;
$this->logLine(
pht('CRITICAL. Failed to release cluster write lock!'));
$this->logLine(
pht(
'The connection to the master database was lost while receiving '.
'the write.'));
$this->logLine(
pht(
'This process will spend %s more second(s) attempting to '.
'recover, then give up.',
new PhutilNumber($duration)));
}
sleep(1);
}
if ($did_release) {
if ($already_failed) {
$this->logLine(
pht('RECOVERED. Link to master database was restored.'));
}
$this->logLine(pht('Released cluster write lock.'));
} else {
throw new Exception(
pht(
'Failed to reconnect to master database and release held write '.
'lock ("%s") on device "%s" for repository "%s" after trying '.
'for %s seconds(s). This repository will be frozen.',
$this->clusterWriteOwner,
$device->getName(),
$this->getDisplayName(),
new PhutilNumber($duration)));
}
// We can continue even if we've lost this lock, everything is still
// consistent.
try {
$this->clusterWriteLock->unlock();
} catch (Exception $ex) {
// Ignore.
}
$this->clusterWriteLock = null;
$this->clusterWriteOwner = null;
}
/* -( Internals )---------------------------------------------------------- */
/**
* @task internal
*/
private function shouldEnableSynchronization($require_device) {
$repository = $this->getRepository();
$service_phid = $repository->getAlmanacServicePHID();
if (!$service_phid) {
return false;
}
if (!$repository->supportsSynchronization()) {
return false;
}
if ($require_device) {
$device = AlmanacKeys::getLiveDevice();
if (!$device) {
return false;
}
}
return true;
}
/**
* @task internal
*/
private function synchronizeWorkingCopyFromRemote() {
$repository = $this->getRepository();
$device = AlmanacKeys::getLiveDevice();
$local_path = $repository->getLocalPath();
$fetch_uri = $repository->getRemoteURIEnvelope();
if ($repository->isGit()) {
$this->requireWorkingCopy();
$argv = array(
'fetch --prune -- %P %s',
$fetch_uri,
'+refs/*:refs/*',
);
} else {
throw new Exception(pht('Remote sync only supported for git!'));
}
$future = DiffusionCommandEngine::newCommandEngine($repository)
->setArgv($argv)
->setSudoAsDaemon(true)
->setCredentialPHID($repository->getCredentialPHID())
->setURI($repository->getRemoteURIObject())
->newFuture();
$future->setCWD($local_path);
try {
$future->resolvex();
} catch (Exception $ex) {
$this->logLine(
pht(
'Synchronization of "%s" from remote failed: %s',
$device->getName(),
$ex->getMessage()));
throw $ex;
}
}
/**
* @task internal
*/
private function synchronizeWorkingCopyFromDevices(
array $device_phids,
$local_version,
$remote_version) {
$repository = $this->getRepository();
$service = $repository->loadAlmanacService();
if (!$service) {
throw new Exception(pht('Failed to load repository cluster service.'));
}
$device_map = array_fuse($device_phids);
$bindings = $service->getActiveBindings();
$fetchable = array();
foreach ($bindings as $binding) {
// We can't fetch from nodes which don't have the newest version.
$device_phid = $binding->getDevicePHID();
if (empty($device_map[$device_phid])) {
continue;
}
// TODO: For now, only fetch over SSH. We could support fetching over
// HTTP eventually.
if ($binding->getAlmanacPropertyValue('protocol') != 'ssh') {
continue;
}
$fetchable[] = $binding;
}
if (!$fetchable) {
throw new Exception(
pht(
'Leader lost: no up-to-date nodes in repository cluster are '.
'fetchable.'));
}
// If we can synchronize from multiple sources, choose one at random.
shuffle($fetchable);
$caught = null;
foreach ($fetchable as $binding) {
try {
$this->synchronizeWorkingCopyFromBinding(
$binding,
$local_version,
$remote_version);
$caught = null;
break;
} catch (Exception $ex) {
$caught = $ex;
}
}
if ($caught) {
throw $caught;
}
}
/**
* @task internal
*/
private function synchronizeWorkingCopyFromBinding(
AlmanacBinding $binding,
$local_version,
$remote_version) {
$repository = $this->getRepository();
$device = AlmanacKeys::getLiveDevice();
$this->logLine(
pht(
'Synchronizing this device ("%s") from cluster leader ("%s").',
$device->getName(),
$binding->getDevice()->getName()));
$fetch_uri = $repository->getClusterRepositoryURIFromBinding($binding);
$local_path = $repository->getLocalPath();
if ($repository->isGit()) {
$this->requireWorkingCopy();
$argv = array(
'fetch --prune -- %s %s',
$fetch_uri,
'+refs/*:refs/*',
);
} else {
throw new Exception(pht('Binding sync only supported for git!'));
}
$future = DiffusionCommandEngine::newCommandEngine($repository)
->setArgv($argv)
->setConnectAsDevice(true)
->setSudoAsDaemon(true)
->setURI($fetch_uri)
->newFuture();
$future->setCWD($local_path);
$log = PhabricatorRepositorySyncEvent::initializeNewEvent()
->setRepositoryPHID($repository->getPHID())
->setEpoch(PhabricatorTime::getNow())
->setDevicePHID($device->getPHID())
->setFromDevicePHID($binding->getDevice()->getPHID())
->setDeviceVersion($local_version)
->setFromDeviceVersion($remote_version);
$sync_start = microtime(true);
try {
$future->resolvex();
} catch (Exception $ex) {
$log->setSyncWait(phutil_microseconds_since($sync_start));
if ($ex instanceof CommandException) {
if ($future->getWasKilledByTimeout()) {
$result_type = PhabricatorRepositorySyncEvent::RESULT_TIMEOUT;
} else {
$result_type = PhabricatorRepositorySyncEvent::RESULT_ERROR;
}
$log
->setResultCode($ex->getError())
->setResultType($result_type)
->setProperty('stdout', $ex->getStdout())
->setProperty('stderr', $ex->getStderr());
} else {
$log
->setResultCode(1)
->setResultType(PhabricatorRepositorySyncEvent::RESULT_EXCEPTION)
->setProperty('message', $ex->getMessage());
}
$log->save();
$this->logLine(
pht(
'Synchronization of "%s" from leader "%s" failed: %s',
$device->getName(),
$binding->getDevice()->getName(),
$ex->getMessage()));
throw $ex;
}
$log
->setSyncWait(phutil_microseconds_since($sync_start))
->setResultCode(0)
->setResultType(PhabricatorRepositorySyncEvent::RESULT_SYNC)
->save();
}
/**
* @task internal
*/
private function logLine($message) {
return $this->logText("# {$message}\n");
}
/**
* @task internal
*/
private function logText($message) {
$log = $this->logger;
if ($log) {
$log->writeClusterEngineLogMessage($message);
}
return $this;
}
private function requireWorkingCopy() {
$repository = $this->getRepository();
$local_path = $repository->getLocalPath();
if (!Filesystem::pathExists($local_path)) {
$device = AlmanacKeys::getLiveDevice();
throw new Exception(
pht(
'Repository "%s" does not have a working copy on this device '.
'yet, so it can not be synchronized. Wait for the daemons to '.
'construct one or run `bin/repository update %s` on this host '.
'("%s") to build it explicitly.',
$repository->getDisplayName(),
$repository->getMonogram(),
$device->getName()));
}
}
private function logActiveWriter(
PhabricatorUser $viewer,
PhabricatorRepository $repository) {
$writer = PhabricatorRepositoryWorkingCopyVersion::loadWriter(
$repository->getPHID());
if (!$writer) {
$this->logLine(pht('Waiting on another user to finish writing...'));
return;
}
$user_phid = $writer->getWriteProperty('userPHID');
$device_phid = $writer->getWriteProperty('devicePHID');
$epoch = $writer->getWriteProperty('epoch');
$phids = array($user_phid, $device_phid);
$handles = $viewer->loadHandles($phids);
$duration = (PhabricatorTime::getNow() - $epoch) + 1;
$this->logLine(
pht(
'Waiting for %s to finish writing (on device "%s" for %ss)...',
$handles[$user_phid]->getName(),
$handles[$device_phid]->getName(),
new PhutilNumber($duration)));
}
public function newMaintenanceEvent() {
$viewer = $this->getViewer();
$repository = $this->getRepository();
$now = PhabricatorTime::getNow();
$event = PhabricatorRepositoryPushEvent::initializeNewEvent($viewer)
->setRepositoryPHID($repository->getPHID())
->setEpoch($now)
->setPusherPHID($this->getEffectiveActingAsPHID())
->setRejectCode(PhabricatorRepositoryPushLog::REJECT_ACCEPT);
return $event;
}
public function newMaintenanceLog() {
$viewer = $this->getViewer();
$repository = $this->getRepository();
$now = PhabricatorTime::getNow();
$device = AlmanacKeys::getLiveDevice();
if ($device) {
$device_phid = $device->getPHID();
} else {
$device_phid = null;
}
return PhabricatorRepositoryPushLog::initializeNewLog($viewer)
->setDevicePHID($device_phid)
->setRepositoryPHID($repository->getPHID())
->attachRepository($repository)
->setEpoch($now)
->setPusherPHID($this->getEffectiveActingAsPHID())
->setChangeFlags(PhabricatorRepositoryPushLog::CHANGEFLAG_MAINTENANCE)
->setRefType(PhabricatorRepositoryPushLog::REFTYPE_MAINTENANCE)
->setRefNew('');
}
}
diff --git a/src/applications/diffusion/query/DiffusionCommitHintQuery.php b/src/applications/diffusion/query/DiffusionCommitHintQuery.php
index bfd8045131..c0794a4387 100644
--- a/src/applications/diffusion/query/DiffusionCommitHintQuery.php
+++ b/src/applications/diffusion/query/DiffusionCommitHintQuery.php
@@ -1,116 +1,112 @@
<?php
final class DiffusionCommitHintQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $repositoryPHIDs;
private $oldCommitIdentifiers;
private $commits;
private $commitMap;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withRepositoryPHIDs(array $phids) {
$this->repositoryPHIDs = $phids;
return $this;
}
public function withOldCommitIdentifiers(array $identifiers) {
$this->oldCommitIdentifiers = $identifiers;
return $this;
}
public function withCommits(array $commits) {
assert_instances_of($commits, 'PhabricatorRepositoryCommit');
$repository_phids = array();
foreach ($commits as $commit) {
$repository_phids[] = $commit->getRepository()->getPHID();
}
$this->repositoryPHIDs = $repository_phids;
$this->oldCommitIdentifiers = mpull($commits, 'getCommitIdentifier');
$this->commits = $commits;
return $this;
}
public function getCommitMap() {
if ($this->commitMap === null) {
throw new PhutilInvalidStateException('execute');
}
return $this->commitMap;
}
public function newResultObject() {
return new PhabricatorRepositoryCommitHint();
}
protected function willExecute() {
$this->commitMap = array();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->repositoryPHIDs !== null) {
$where[] = qsprintf(
$conn,
'repositoryPHID IN (%Ls)',
$this->repositoryPHIDs);
}
if ($this->oldCommitIdentifiers !== null) {
$where[] = qsprintf(
$conn,
'oldCommitIdentifier IN (%Ls)',
$this->oldCommitIdentifiers);
}
return $where;
}
protected function didFilterPage(array $hints) {
if ($this->commits) {
$map = array();
foreach ($this->commits as $commit) {
$repository_phid = $commit->getRepository()->getPHID();
$identifier = $commit->getCommitIdentifier();
$map[$repository_phid][$identifier] = $commit->getPHID();
}
foreach ($hints as $hint) {
$repository_phid = $hint->getRepositoryPHID();
$identifier = $hint->getOldCommitIdentifier();
if (isset($map[$repository_phid][$identifier])) {
$commit_phid = $map[$repository_phid][$identifier];
$this->commitMap[$commit_phid] = $hint;
}
}
}
return $hints;
}
public function getQueryApplicationClass() {
return 'PhabricatorDiffusionApplication';
}
}
diff --git a/src/applications/diffusion/query/pathchange/DiffusionPathChangeQuery.php b/src/applications/diffusion/query/pathchange/DiffusionPathChangeQuery.php
index 850c06f105..98798582a7 100644
--- a/src/applications/diffusion/query/pathchange/DiffusionPathChangeQuery.php
+++ b/src/applications/diffusion/query/pathchange/DiffusionPathChangeQuery.php
@@ -1,117 +1,122 @@
<?php
final class DiffusionPathChangeQuery extends Phobject {
private $request;
private $limit;
public function setLimit($limit) {
$this->limit = $limit;
return $this;
}
public function getLimit() {
return $this->limit;
}
private function __construct() {
// <private>
}
public static function newFromDiffusionRequest(
DiffusionRequest $request) {
$query = new DiffusionPathChangeQuery();
$query->request = $request;
return $query;
}
protected function getRequest() {
return $this->request;
}
public function loadChanges() {
return $this->executeQuery();
}
protected function executeQuery() {
$drequest = $this->getRequest();
$repository = $drequest->getRepository();
$commit = $drequest->loadCommit();
$conn_r = $repository->establishConnection('r');
if ($this->limit) {
$limit = qsprintf(
$conn_r,
'LIMIT %d',
$this->limit + 1);
} else {
$limit = qsprintf($conn_r, '');
}
$raw_changes = queryfx_all(
$conn_r,
'SELECT c.*, p.path pathName, t.path targetPathName,
i.commitIdentifier targetCommitIdentifier
FROM %T c
LEFT JOIN %T p ON c.pathID = p.id
LEFT JOIN %T t ON c.targetPathID = t.id
LEFT JOIN %T i ON c.targetCommitID = i.id
WHERE c.commitID = %d AND isDirect = 1 %Q',
PhabricatorRepository::TABLE_PATHCHANGE,
PhabricatorRepository::TABLE_PATH,
PhabricatorRepository::TABLE_PATH,
$commit->getTableName(),
$commit->getID(),
$limit);
$limited = $this->limit && (count($raw_changes) > $this->limit);
if ($limited) {
$raw_changes = array_slice($raw_changes, 0, $this->limit);
}
$changes = array();
$raw_changes = isort($raw_changes, 'pathName');
foreach ($raw_changes as $raw_change) {
$type = $raw_change['changeType'];
if ($type == DifferentialChangeType::TYPE_CHILD) {
continue;
}
$change = new DiffusionPathChange();
$change->setPath(ltrim($raw_change['pathName'], '/'));
$change->setChangeType($raw_change['changeType']);
$change->setFileType($raw_change['fileType']);
$change->setCommitIdentifier($commit->getCommitIdentifier());
- $change->setTargetPath(ltrim($raw_change['targetPathName'], '/'));
+ $target_path = $raw_change['targetPathName'];
+ if ($target_path !== null) {
+ $target_path = ltrim($target_path, '/');
+ }
+ $change->setTargetPath($target_path);
+
$change->setTargetCommitIdentifier($raw_change['targetCommitIdentifier']);
$id = $raw_change['pathID'];
$changes[$id] = $change;
}
// Deduce the away paths by examining all the changes, if we loaded them
// all.
if (!$limited) {
$away = array();
foreach ($changes as $change) {
if ($change->getTargetPath()) {
$away[$change->getTargetPath()][] = $change->getPath();
}
}
foreach ($changes as $change) {
if (isset($away[$change->getPath()])) {
$change->setAwayPaths($away[$change->getPath()]);
}
}
}
return $changes;
}
}
diff --git a/src/applications/diffusion/request/DiffusionRequest.php b/src/applications/diffusion/request/DiffusionRequest.php
index 49ea305908..4bcb4d3bc5 100644
--- a/src/applications/diffusion/request/DiffusionRequest.php
+++ b/src/applications/diffusion/request/DiffusionRequest.php
@@ -1,698 +1,700 @@
<?php
/**
* Contains logic to parse Diffusion requests, which have a complicated URI
* structure.
*
* @task new Creating Requests
* @task uri Managing Diffusion URIs
*/
abstract class DiffusionRequest extends Phobject {
protected $path;
protected $line;
protected $branch;
protected $lint;
protected $symbolicCommit;
protected $symbolicType;
protected $stableCommit;
protected $repository;
protected $repositoryCommit;
protected $repositoryCommitData;
private $isClusterRequest = false;
private $initFromConduit = true;
private $user;
private $branchObject = false;
private $refAlternatives;
final public function supportsBranches() {
return $this->getRepository()->supportsRefs();
}
abstract protected function isStableCommit($symbol);
protected function didInitialize() {
return null;
}
/* -( Creating Requests )-------------------------------------------------- */
/**
* Create a new synthetic request from a parameter dictionary. If you need
* a @{class:DiffusionRequest} object in order to issue a DiffusionQuery, you
* can use this method to build one.
*
* Parameters are:
*
* - `repository` Repository object or identifier.
* - `user` Viewing user. Required if `repository` is an identifier.
* - `branch` Optional, branch name.
* - `path` Optional, file path.
* - `commit` Optional, commit identifier.
* - `line` Optional, line range.
*
* @param map See documentation.
* @return DiffusionRequest New request object.
* @task new
*/
final public static function newFromDictionary(array $data) {
$repository_key = 'repository';
$identifier_key = 'callsign';
$viewer_key = 'user';
$repository = idx($data, $repository_key);
$identifier = idx($data, $identifier_key);
$have_repository = ($repository !== null);
$have_identifier = ($identifier !== null);
if ($have_repository && $have_identifier) {
throw new Exception(
pht(
'Specify "%s" or "%s", but not both.',
$repository_key,
$identifier_key));
}
if (!$have_repository && !$have_identifier) {
throw new Exception(
pht(
'One of "%s" and "%s" is required.',
$repository_key,
$identifier_key));
}
if ($have_repository) {
if (!($repository instanceof PhabricatorRepository)) {
if (empty($data[$viewer_key])) {
throw new Exception(
pht(
'Parameter "%s" is required if "%s" is provided.',
$viewer_key,
$identifier_key));
}
$identifier = $repository;
$repository = null;
}
}
if ($identifier !== null) {
$object = self::newFromIdentifier(
$identifier,
$data[$viewer_key],
idx($data, 'edit'));
} else {
$object = self::newFromRepository($repository);
}
if (!$object) {
return null;
}
$object->initializeFromDictionary($data);
return $object;
}
/**
* Internal.
*
* @task new
*/
private function __construct() {
// <private>
}
/**
* Internal. Use @{method:newFromDictionary}, not this method.
*
* @param string Repository identifier.
* @param PhabricatorUser Viewing user.
* @return DiffusionRequest New request object.
* @task new
*/
private static function newFromIdentifier(
$identifier,
PhabricatorUser $viewer,
$need_edit = false) {
$query = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->withIdentifiers(array($identifier))
->needProfileImage(true)
->needURIs(true);
if ($need_edit) {
$query->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
));
}
$repository = $query->executeOne();
if (!$repository) {
return null;
}
return self::newFromRepository($repository);
}
/**
* Internal. Use @{method:newFromDictionary}, not this method.
*
* @param PhabricatorRepository Repository object.
* @return DiffusionRequest New request object.
* @task new
*/
private static function newFromRepository(
PhabricatorRepository $repository) {
$map = array(
PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => 'DiffusionGitRequest',
PhabricatorRepositoryType::REPOSITORY_TYPE_SVN => 'DiffusionSvnRequest',
PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL =>
'DiffusionMercurialRequest',
);
$class = idx($map, $repository->getVersionControlSystem());
if (!$class) {
throw new Exception(pht('Unknown version control system!'));
}
$object = new $class();
$object->repository = $repository;
return $object;
}
/**
* Internal. Use @{method:newFromDictionary}, not this method.
*
* @param map Map of parsed data.
* @return void
* @task new
*/
private function initializeFromDictionary(array $data) {
$blob = idx($data, 'blob');
- if (strlen($blob)) {
+ if (phutil_nonempty_string($blob)) {
$blob = self::parseRequestBlob($blob, $this->supportsBranches());
$data = $blob + $data;
}
$this->path = idx($data, 'path');
$this->line = idx($data, 'line');
$this->initFromConduit = idx($data, 'initFromConduit', true);
$this->lint = idx($data, 'lint');
$this->symbolicCommit = idx($data, 'commit');
if ($this->supportsBranches()) {
$this->branch = idx($data, 'branch');
}
if (!$this->getUser()) {
$user = idx($data, 'user');
if (!$user) {
throw new Exception(
pht(
'You must provide a %s in the dictionary!',
'PhabricatorUser'));
}
$this->setUser($user);
}
$this->didInitialize();
}
final public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
final public function getUser() {
return $this->user;
}
public function getRepository() {
return $this->repository;
}
public function setPath($path) {
$this->path = $path;
return $this;
}
public function getPath() {
return $this->path;
}
public function getLine() {
return $this->line;
}
public function getCommit() {
// TODO: Probably remove all of this.
if ($this->getSymbolicCommit() !== null) {
return $this->getSymbolicCommit();
}
return $this->getStableCommit();
}
/**
* Get the symbolic commit associated with this request.
*
* A symbolic commit may be a commit hash, an abbreviated commit hash, a
* branch name, a tag name, or an expression like "HEAD^^^". The symbolic
* commit may also be absent.
*
* This method always returns the symbol present in the original request,
* in unmodified form.
*
* See also @{method:getStableCommit}.
*
* @return string|null Symbolic commit, if one was present in the request.
*/
public function getSymbolicCommit() {
return $this->symbolicCommit;
}
/**
* Modify the request to move the symbolic commit elsewhere.
*
* @param string New symbolic commit.
* @return this
*/
public function updateSymbolicCommit($symbol) {
$this->symbolicCommit = $symbol;
$this->symbolicType = null;
$this->stableCommit = null;
return $this;
}
/**
* Get the ref type (`commit` or `tag`) of the location associated with this
* request.
*
* If a symbolic commit is present in the request, this method identifies
* the type of the symbol. Otherwise, it identifies the type of symbol of
* the location the request is implicitly associated with. This will probably
* always be `commit`.
*
* @return string Symbolic commit type (`commit` or `tag`).
*/
public function getSymbolicType() {
if ($this->symbolicType === null) {
// As a side effect, this resolves the symbolic type.
$this->getStableCommit();
}
return $this->symbolicType;
}
/**
* Retrieve the stable, permanent commit name identifying the repository
* location associated with this request.
*
* This returns a non-symbolic identifier for the current commit: in Git and
* Mercurial, a 40-character SHA1; in SVN, a revision number.
*
* See also @{method:getSymbolicCommit}.
*
* @return string Stable commit name, like a git hash or SVN revision. Not
* a symbolic commit reference.
*/
public function getStableCommit() {
if (!$this->stableCommit) {
if ($this->isStableCommit($this->symbolicCommit)) {
$this->stableCommit = $this->symbolicCommit;
$this->symbolicType = 'commit';
} else {
$this->queryStableCommit();
}
}
return $this->stableCommit;
}
public function getBranch() {
return $this->branch;
}
public function getLint() {
return $this->lint;
}
protected function getArcanistBranch() {
return $this->getBranch();
}
public function loadBranch() {
// TODO: Get rid of this and do real Queries on real objects.
if ($this->branchObject === false) {
$this->branchObject = PhabricatorRepositoryBranch::loadBranch(
$this->getRepository()->getID(),
$this->getArcanistBranch());
}
return $this->branchObject;
}
public function loadCoverage() {
// TODO: This should also die.
$branch = $this->loadBranch();
if (!$branch) {
return;
}
$path = $this->getPath();
$path_map = id(new DiffusionPathIDQuery(array($path)))->loadPathIDs();
$coverage_row = queryfx_one(
id(new PhabricatorRepository())->establishConnection('r'),
'SELECT * FROM %T WHERE branchID = %d AND pathID = %d
ORDER BY commitID DESC LIMIT 1',
'repository_coverage',
$branch->getID(),
$path_map[$path]);
if (!$coverage_row) {
return null;
}
return idx($coverage_row, 'coverage');
}
public function loadCommit() {
if (empty($this->repositoryCommit)) {
$repository = $this->getRepository();
$commit = id(new DiffusionCommitQuery())
->setViewer($this->getUser())
->withRepository($repository)
->withIdentifiers(array($this->getStableCommit()))
->executeOne();
if ($commit) {
$commit->attachRepository($repository);
}
$this->repositoryCommit = $commit;
}
return $this->repositoryCommit;
}
public function loadCommitData() {
if (empty($this->repositoryCommitData)) {
$commit = $this->loadCommit();
$data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$commit->getID());
if (!$data) {
$data = new PhabricatorRepositoryCommitData();
$data->setCommitMessage(
pht('(This commit has not been fully parsed yet.)'));
}
$this->repositoryCommitData = $data;
}
return $this->repositoryCommitData;
}
/* -( Managing Diffusion URIs )-------------------------------------------- */
public function generateURI(array $params) {
if (empty($params['stable'])) {
$default_commit = $this->getSymbolicCommit();
} else {
$default_commit = $this->getStableCommit();
}
$defaults = array(
'path' => $this->getPath(),
'branch' => $this->getBranch(),
'commit' => $default_commit,
'lint' => idx($params, 'lint', $this->getLint()),
);
foreach ($defaults as $key => $val) {
if (!isset($params[$key])) { // Overwrite NULL.
$params[$key] = $val;
}
}
return $this->getRepository()->generateURI($params);
}
/**
* Internal. Public only for unit tests.
*
* Parse the request URI into components.
*
* @param string URI blob.
* @param bool True if this VCS supports branches.
* @return map Parsed URI.
*
* @task uri
*/
public static function parseRequestBlob($blob, $supports_branches) {
$result = array(
'branch' => null,
'path' => null,
'commit' => null,
'line' => null,
);
$matches = null;
if ($supports_branches) {
// Consume the front part of the URI, up to the first "/". This is the
// path-component encoded branch name.
if (preg_match('@^([^/]+)/@', $blob, $matches)) {
$result['branch'] = phutil_unescape_uri_path_component($matches[1]);
$blob = substr($blob, strlen($matches[1]) + 1);
}
}
// Consume the back part of the URI, up to the first "$". Use a negative
// lookbehind to prevent matching '$$'. We double the '$' symbol when
// encoding so that files with names like "money/$100" will survive.
$pattern = '@(?:(?:^|[^$])(?:[$][$])*)[$]([\d,-]+)$@';
if (preg_match($pattern, $blob, $matches)) {
$result['line'] = $matches[1];
$blob = substr($blob, 0, -(strlen($matches[1]) + 1));
}
// We've consumed the line number if it exists, so unescape "$" in the
// rest of the string.
$blob = str_replace('$$', '$', $blob);
// Consume the commit name, stopping on ';;'. We allow any character to
// appear in commits names, as they can sometimes be symbolic names (like
// tag names or refs).
if (preg_match('@(?:(?:^|[^;])(?:;;)*);([^;].*)$@', $blob, $matches)) {
$result['commit'] = $matches[1];
$blob = substr($blob, 0, -(strlen($matches[1]) + 1));
}
// We've consumed the commit if it exists, so unescape ";" in the rest
// of the string.
$blob = str_replace(';;', ';', $blob);
if (strlen($blob)) {
$result['path'] = $blob;
}
- $parts = explode('/', $result['path']);
- foreach ($parts as $part) {
- // Prevent any hyjinx since we're ultimately shipping this to the
- // filesystem under a lot of workflows.
- if ($part == '..') {
- throw new Exception(pht('Invalid path URI.'));
+ if ($result['path'] !== null) {
+ $parts = explode('/', $result['path']);
+ foreach ($parts as $part) {
+ // Prevent any hyjinx since we're ultimately shipping this to the
+ // filesystem under a lot of workflows.
+ if ($part == '..') {
+ throw new Exception(pht('Invalid path URI.'));
+ }
}
}
return $result;
}
/**
* Check that the working copy of the repository is present and readable.
*
* @param string Path to the working copy.
*/
protected function validateWorkingCopy($path) {
if (!is_readable(dirname($path))) {
$this->raisePermissionException();
}
if (!Filesystem::pathExists($path)) {
$this->raiseCloneException();
}
}
protected function raisePermissionException() {
$host = php_uname('n');
throw new DiffusionSetupException(
pht(
'The clone of this repository ("%s") on the local machine ("%s") '.
'could not be read. Ensure that the repository is in a '.
'location where the web server has read permissions.',
$this->getRepository()->getDisplayName(),
$host));
}
protected function raiseCloneException() {
$host = php_uname('n');
throw new DiffusionSetupException(
pht(
'The working copy for this repository ("%s") has not been cloned yet '.
- 'on this machine ("%s"). Make sure you havestarted the Phabricator '.
+ 'on this machine ("%s"). Make sure you have started the '.
'daemons. If this problem persists for longer than a clone should '.
'take, check the daemon logs (in the Daemon Console) to see if there '.
'were errors cloning the repository. Consult the "Diffusion User '.
'Guide" in the documentation for help setting up repositories.',
$this->getRepository()->getDisplayName(),
$host));
}
private function queryStableCommit() {
$types = array();
if ($this->symbolicCommit) {
$ref = $this->symbolicCommit;
} else {
if ($this->supportsBranches()) {
$ref = $this->getBranch();
$types = array(
PhabricatorRepositoryRefCursor::TYPE_BRANCH,
);
} else {
$ref = 'HEAD';
}
}
$results = $this->resolveRefs(array($ref), $types);
$matches = idx($results, $ref, array());
if (!$matches) {
$message = pht(
'Ref "%s" does not exist in this repository.',
$ref);
throw id(new DiffusionRefNotFoundException($message))
->setRef($ref);
}
if (count($matches) > 1) {
$match = $this->chooseBestRefMatch($ref, $matches);
} else {
$match = head($matches);
}
$this->stableCommit = $match['identifier'];
$this->symbolicType = $match['type'];
}
public function getRefAlternatives() {
// Make sure we've resolved the reference into a stable commit first.
try {
$this->getStableCommit();
} catch (DiffusionRefNotFoundException $ex) {
// If we have a bad reference, just return the empty set of
// alternatives.
}
return $this->refAlternatives;
}
private function chooseBestRefMatch($ref, array $results) {
// First, filter out less-desirable matches.
$candidates = array();
foreach ($results as $result) {
// Exclude closed heads.
if ($result['type'] == 'branch') {
if (idx($result, 'closed')) {
continue;
}
}
$candidates[] = $result;
}
// If we filtered everything, undo the filtering.
if (!$candidates) {
$candidates = $results;
}
// TODO: Do a better job of selecting the best match?
$match = head($candidates);
// After choosing the best alternative, save all the alternatives so the
// UI can show them to the user.
if (count($candidates) > 1) {
$this->refAlternatives = $candidates;
}
return $match;
}
public function resolveRefs(array $refs, array $types = array()) {
// First, try to resolve refs from fast cache sources.
$cached_query = id(new DiffusionCachedResolveRefsQuery())
->setRepository($this->getRepository())
->withRefs($refs);
if ($types) {
$cached_query->withTypes($types);
}
$cached_results = $cached_query->execute();
// Throw away all the refs we resolved. Hopefully, we'll throw away
// everything here.
foreach ($refs as $key => $ref) {
if (isset($cached_results[$ref])) {
unset($refs[$key]);
}
}
// If we couldn't pull everything out of the cache, execute the underlying
// VCS operation.
if ($refs) {
$vcs_results = DiffusionQuery::callConduitWithDiffusionRequest(
$this->getUser(),
$this,
'diffusion.resolverefs',
array(
'types' => $types,
'refs' => $refs,
));
} else {
$vcs_results = array();
}
return $vcs_results + $cached_results;
}
public function setIsClusterRequest($is_cluster_request) {
$this->isClusterRequest = $is_cluster_request;
return $this;
}
public function getIsClusterRequest() {
return $this->isClusterRequest;
}
}
diff --git a/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php b/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php
index 358418f44c..0645e76356 100644
--- a/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php
+++ b/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php
@@ -1,329 +1,329 @@
<?php
abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
private $args;
private $repository;
private $hasWriteAccess;
private $shouldProxy;
private $baseRequestPath;
public function getRepository() {
if (!$this->repository) {
throw new Exception(pht('Repository is not available yet!'));
}
return $this->repository;
}
private function setRepository(PhabricatorRepository $repository) {
$this->repository = $repository;
return $this;
}
public function getArgs() {
return $this->args;
}
public function getEnvironment() {
$env = array(
DiffusionCommitHookEngine::ENV_USER => $this->getSSHUser()->getUsername(),
DiffusionCommitHookEngine::ENV_REMOTE_PROTOCOL => 'ssh',
);
$identifier = $this->getRequestIdentifier();
if ($identifier !== null) {
$env[DiffusionCommitHookEngine::ENV_REQUEST] = $identifier;
}
$remote_address = $this->getSSHRemoteAddress();
if ($remote_address !== null) {
$env[DiffusionCommitHookEngine::ENV_REMOTE_ADDRESS] = $remote_address;
}
return $env;
}
/**
* Identify and load the affected repository.
*/
abstract protected function identifyRepository();
abstract protected function executeRepositoryOperations();
abstract protected function raiseWrongVCSException(
PhabricatorRepository $repository);
protected function getBaseRequestPath() {
return $this->baseRequestPath;
}
protected function writeError($message) {
$this->getErrorChannel()->write($message);
return $this;
}
protected function getCurrentDeviceName() {
$device = AlmanacKeys::getLiveDevice();
if ($device) {
return $device->getName();
}
return php_uname('n');
}
protected function shouldProxy() {
return $this->shouldProxy;
}
final protected function getAlmanacServiceRefs($for_write) {
$viewer = $this->getSSHUser();
$repository = $this->getRepository();
$is_cluster_request = $this->getIsClusterRequest();
$refs = $repository->getAlmanacServiceRefs(
$viewer,
array(
'neverProxy' => $is_cluster_request,
'protocols' => array(
'ssh',
),
'writable' => $for_write,
));
if (!$refs) {
throw new Exception(
pht(
'Failed to generate an intracluster proxy URI even though this '.
'request was routed as a proxy request.'));
}
return $refs;
}
final protected function getProxyCommand($for_write) {
$refs = $this->getAlmanacServiceRefs($for_write);
$ref = head($refs);
return $this->getProxyCommandForServiceRef($ref);
}
final protected function getProxyCommandForServiceRef(
DiffusionServiceRef $ref) {
$uri = new PhutilURI($ref->getURI());
$username = AlmanacKeys::getClusterSSHUser();
if ($username === null) {
throw new Exception(
pht(
'Unable to determine the username to connect with when trying '.
- 'to proxy an SSH request within the Phabricator cluster.'));
+ 'to proxy an SSH request within the cluster.'));
}
$port = $uri->getPort();
$host = $uri->getDomain();
$key_path = AlmanacKeys::getKeyPath('device.key');
if (!Filesystem::pathExists($key_path)) {
throw new Exception(
pht(
'Unable to proxy this SSH request within the cluster: this device '.
'is not registered and has a missing device key (expected to '.
'find key at "%s").',
$key_path));
}
$options = array();
$options[] = '-o';
$options[] = 'StrictHostKeyChecking=no';
$options[] = '-o';
$options[] = 'UserKnownHostsFile=/dev/null';
// This is suppressing "added <address> to the list of known hosts"
// messages, which are confusing and irrelevant when they arise from
// proxied requests. It might also be suppressing lots of useful errors,
// of course. Ideally, we would enforce host keys eventually. See T13121.
$options[] = '-o';
$options[] = 'LogLevel=ERROR';
// NOTE: We prefix the command with "@username", which the far end of the
// connection will parse in order to act as the specified user. This
// behavior is only available to cluster requests signed by a trusted
// device key.
return csprintf(
'ssh %Ls -l %s -i %s -p %s %s -- %s %Ls',
$options,
$username,
$key_path,
$port,
$host,
'@'.$this->getSSHUser()->getUsername(),
$this->getOriginalArguments());
}
final public function execute(PhutilArgumentParser $args) {
$this->args = $args;
$viewer = $this->getSSHUser();
$have_diffusion = PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorDiffusionApplication',
$viewer);
if (!$have_diffusion) {
throw new Exception(
pht(
'You do not have permission to access the Diffusion application, '.
'so you can not interact with repositories over SSH.'));
}
$repository = $this->identifyRepository();
$this->setRepository($repository);
// NOTE: Here, we're just figuring out if this is a proxyable request to
// a clusterized repository or not. We don't (and can't) use the URI we get
// back directly.
// For example, we may get a read-only URI here but be handling a write
// request. We only care if we get back `null` (which means we should
// handle the request locally) or anything else (which means we should
// proxy it to an appropriate device).
$is_cluster_request = $this->getIsClusterRequest();
$uri = $repository->getAlmanacServiceURI(
$viewer,
array(
'neverProxy' => $is_cluster_request,
'protocols' => array(
'ssh',
),
));
$this->shouldProxy = (bool)$uri;
try {
return $this->executeRepositoryOperations();
} catch (Exception $ex) {
$this->writeError(get_class($ex).': '.$ex->getMessage());
return 1;
}
}
protected function loadRepositoryWithPath($path, $vcs) {
$viewer = $this->getSSHUser();
$info = PhabricatorRepository::parseRepositoryServicePath($path, $vcs);
if ($info === null) {
throw new Exception(
pht(
'Unrecognized repository path "%s". Expected a path like "%s", '.
'"%s", or "%s".',
$path,
'/diffusion/X/',
'/diffusion/123/',
'/source/thaumaturgy.git'));
}
$identifier = $info['identifier'];
$base = $info['base'];
$this->baseRequestPath = $base;
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->withIdentifiers(array($identifier))
->needURIs(true)
->executeOne();
if (!$repository) {
throw new Exception(
pht('No repository "%s" exists!', $identifier));
}
$is_cluster = $this->getIsClusterRequest();
$protocol = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH;
if (!$repository->canServeProtocol($protocol, false, $is_cluster)) {
throw new Exception(
pht(
'This repository ("%s") is not available over SSH.',
$repository->getDisplayName()));
}
if ($repository->getVersionControlSystem() != $vcs) {
$this->raiseWrongVCSException($repository);
}
return $repository;
}
protected function requireWriteAccess($protocol_command = null) {
if ($this->hasWriteAccess === true) {
return;
}
$repository = $this->getRepository();
$viewer = $this->getSSHUser();
if ($viewer->isOmnipotent()) {
throw new Exception(
pht(
'This request is authenticated as a cluster device, but is '.
'performing a write. Writes must be performed with a real '.
'user account.'));
}
if ($repository->isReadOnly()) {
throw new Exception($repository->getReadOnlyMessageForDisplay());
}
$protocol = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH;
if ($repository->canServeProtocol($protocol, true)) {
$can_push = PhabricatorPolicyFilter::hasCapability(
$viewer,
$repository,
DiffusionPushCapability::CAPABILITY);
if (!$can_push) {
throw new Exception(
pht('You do not have permission to push to this repository.'));
}
} else {
if ($protocol_command !== null) {
throw new Exception(
pht(
'This repository is read-only over SSH (tried to execute '.
'protocol command "%s").',
$protocol_command));
} else {
throw new Exception(
pht('This repository is read-only over SSH.'));
}
}
$this->hasWriteAccess = true;
return $this->hasWriteAccess;
}
protected function shouldSkipReadSynchronization() {
$viewer = $this->getSSHUser();
// Currently, the only case where devices interact over SSH without
// assuming user credentials is when synchronizing before a read. These
// synchronizing reads do not themselves need to be synchronized.
if ($viewer->isOmnipotent()) {
return true;
}
return false;
}
protected function newPullEvent() {
$viewer = $this->getSSHUser();
$repository = $this->getRepository();
$remote_address = $this->getSSHRemoteAddress();
return id(new PhabricatorRepositoryPullEvent())
->setEpoch(PhabricatorTime::getNow())
->setRemoteAddress($remote_address)
->setRemoteProtocol(PhabricatorRepositoryPullEvent::PROTOCOL_SSH)
->setPullerPHID($viewer->getPHID())
->setRepositoryPHID($repository->getPHID());
}
}
diff --git a/src/applications/diviner/controller/DivinerMainController.php b/src/applications/diviner/controller/DivinerMainController.php
index 400fb48d0f..21450698e6 100644
--- a/src/applications/diviner/controller/DivinerMainController.php
+++ b/src/applications/diviner/controller/DivinerMainController.php
@@ -1,75 +1,77 @@
<?php
final class DivinerMainController extends DivinerController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$books = id(new DivinerBookQuery())
->setViewer($viewer)
->execute();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setBorder(true);
$crumbs->addTextCrumb(pht('Books'));
$query_button = id(new PHUIButtonView())
->setTag('a')
->setHref($this->getApplicationURI('query/'))
->setText(pht('Advanced Search'))
->setIcon('fa-search');
$header = id(new PHUIHeaderView())
->setHeader(pht('Documentation Books'))
->addActionLink($query_button);
$document = new PHUIDocumentView();
$document->setHeader($header);
$document->addClass('diviner-view');
if ($books) {
$books = msort($books, 'getTitle');
$list = array();
foreach ($books as $book) {
$item = id(new DivinerBookItemView())
->setTitle($book->getTitle())
->setHref('/book/'.$book->getName().'/')
->setSubtitle($book->getPreface());
$list[] = $item;
}
$list = id(new PHUIBoxView())
->addPadding(PHUI::PADDING_MEDIUM_TOP)
->appendChild($list);
$document->appendChild($list);
} else {
$text = pht(
- "(NOTE) **Looking for Phabricator documentation?** ".
- "If you're looking for help and information about Phabricator, ".
+ "(NOTE) **Looking for documentation?** ".
+ "If you're looking for help and information about %s, ".
"you can [[https://secure.phabricator.com/diviner/ | ".
- "browse the public Phabricator documentation]] on the live site.\n\n".
- "Diviner is the documentation generator used to build the ".
- "Phabricator documentation.\n\n".
+ "browse the public %s documentation]] on the live site.\n\n".
+ "Diviner is the documentation generator used to build this ".
+ "documentation.\n\n".
"You haven't generated any Diviner documentation books yet, so ".
"there's nothing to show here. If you'd like to generate your own ".
- "local copy of the Phabricator documentation and have it appear ".
+ "local copy of the documentation and have it appear ".
"here, run this command:\n\n".
" %s\n\n",
- 'phabricator/ $ ./bin/diviner generate');
+ PlatformSymbols::getPlatformServerName(),
+ PlatformSymbols::getPlatformServerName(),
+ '$ ./bin/diviner generate');
$text = new PHUIRemarkupView($viewer, $text);
$document->appendChild($text);
}
return $this->newPage()
->setTitle(pht('Documentation Books'))
->setCrumbs($crumbs)
->appendChild(array(
$document,
));
}
}
diff --git a/src/applications/doorkeeper/option/PhabricatorAsanaConfigOptions.php b/src/applications/doorkeeper/option/PhabricatorAsanaConfigOptions.php
index e93c818378..64f2f17dd3 100644
--- a/src/applications/doorkeeper/option/PhabricatorAsanaConfigOptions.php
+++ b/src/applications/doorkeeper/option/PhabricatorAsanaConfigOptions.php
@@ -1,163 +1,165 @@
<?php
final class PhabricatorAsanaConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Integration with Asana');
}
public function getDescription() {
return pht('Asana integration options.');
}
public function getIcon() {
return 'fa-exchange';
}
public function getGroup() {
return 'core';
}
public function getOptions() {
return array(
$this->newOption('asana.workspace-id', 'string', null)
->setSummary(pht('Asana Workspace ID to publish into.'))
->setDescription(
pht(
'To enable synchronization into Asana, enter an Asana Workspace '.
'ID here.'.
"\n\n".
"NOTE: This feature is new and experimental.")),
$this->newOption('asana.project-ids', 'wild', null)
->setSummary(pht('Optional Asana projects to use as application tags.'))
->setDescription(
pht(
- 'When Phabricator creates tasks in Asana, it can add the tasks '.
+ 'When %s creates tasks in Asana, it can add the tasks '.
'to Asana projects based on which application the corresponding '.
- 'object in Phabricator comes from. For example, you can add code '.
+ 'object in %s comes from. For example, you can add code '.
'reviews in Asana to a "Differential" project.'.
"\n\n".
- 'NOTE: This feature is new and experimental.')),
+ 'NOTE: This feature is new and experimental.',
+ PlatformSymbols::getPlatformServerName(),
+ PlatformSymbols::getPlatformServerName())),
);
}
public function renderContextualDescription(
PhabricatorConfigOption $option,
AphrontRequest $request) {
switch ($option->getKey()) {
case 'asana.workspace-id':
break;
case 'asana.project-ids':
return $this->renderContextualProjectDescription($option, $request);
default:
return parent::renderContextualDescription($option, $request);
}
$viewer = $request->getUser();
$provider = PhabricatorAsanaAuthProvider::getAsanaProvider();
if (!$provider) {
return null;
}
$account = id(new PhabricatorExternalAccountQuery())
->setViewer($viewer)
->withUserPHIDs(array($viewer->getPHID()))
->withProviderConfigPHIDs(
array(
$provider->getProviderConfigPHID(),
))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$account) {
return null;
}
$token = $provider->getOAuthAccessToken($account);
if (!$token) {
return null;
}
try {
$workspaces = id(new PhutilAsanaFuture())
->setAccessToken($token)
->setRawAsanaQuery('workspaces')
->resolve();
} catch (Exception $ex) {
return null;
}
if (!$workspaces) {
return null;
}
$out = array();
$out[] = sprintf(
'| %s | %s |',
pht('Workspace ID'),
pht('Workspace Name'));
$out[] = '| ------------ | -------------- |';
foreach ($workspaces as $workspace) {
$out[] = sprintf(
'| `%s` | `%s` |',
$workspace['gid'],
$workspace['name']);
}
$out = implode("\n", $out);
$out = pht(
"The Asana Workspaces your linked account has access to are:\n\n%s",
$out);
return new PHUIRemarkupView($viewer, $out);
}
private function renderContextualProjectDescription(
PhabricatorConfigOption $option,
AphrontRequest $request) {
$viewer = $request->getUser();
$publishers = id(new PhutilClassMapQuery())
->setAncestorClass('DoorkeeperFeedStoryPublisher')
->execute();
$out = array();
$out[] = pht(
'To specify projects to add tasks to, enter a JSON map with publisher '.
'class names as keys and a list of project IDs as values. For example, '.
'to put Differential tasks into Asana projects with IDs `123` and '.
'`456`, enter:'.
"\n\n".
" lang=txt\n".
" {\n".
" \"DifferentialDoorkeeperRevisionFeedStoryPublisher\" : [123, 456]\n".
" }\n");
$out[] = pht('Available publishers class names are:');
foreach ($publishers as $publisher) {
$out[] = ' - `'.get_class($publisher).'`';
}
$out[] = pht(
'You can find an Asana project ID by clicking the project in Asana and '.
'then examining the URL:'.
"\n\n".
" lang=txt\n".
" https://app.asana.com/0/12345678901234567890/111111111111111111\n".
" ^^^^^^^^^^^^^^^^^^^^\n".
" This is the ID to use.\n");
$out = implode("\n", $out);
return new PHUIRemarkupView($viewer, $out);
}
}
diff --git a/src/applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php b/src/applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php
index bc794cc629..2a33206831 100644
--- a/src/applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php
+++ b/src/applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php
@@ -1,51 +1,47 @@
<?php
final class DoorkeeperExternalObjectQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
protected $phids;
protected $objectKeys;
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withObjectKeys(array $keys) {
$this->objectKeys = $keys;
return $this;
}
public function newResultObject() {
return new DoorkeeperExternalObject();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->objectKeys !== null) {
$where[] = qsprintf(
$conn,
'objectKey IN (%Ls)',
$this->objectKeys);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorDoorkeeperApplication';
}
}
diff --git a/src/applications/doorkeeper/worker/DoorkeeperAsanaFeedWorker.php b/src/applications/doorkeeper/worker/DoorkeeperAsanaFeedWorker.php
index bbc619ae51..8648f5d7f9 100644
--- a/src/applications/doorkeeper/worker/DoorkeeperAsanaFeedWorker.php
+++ b/src/applications/doorkeeper/worker/DoorkeeperAsanaFeedWorker.php
@@ -1,729 +1,730 @@
<?php
/**
* Publishes tasks representing work that needs to be done into Asana, and
* updates the tasks as the corresponding Phabricator objects are updated.
*/
final class DoorkeeperAsanaFeedWorker extends DoorkeeperFeedWorker {
private $provider;
/* -( Publishing Stories )------------------------------------------------- */
/**
* This worker is enabled when an Asana workspace ID is configured with
* `asana.workspace-id`.
*/
public function isEnabled() {
return (bool)$this->getWorkspaceID();
}
/**
* Publish stories into Asana using the Asana API.
*/
protected function publishFeedStory() {
$story = $this->getFeedStory();
$data = $story->getStoryData();
$viewer = $this->getViewer();
$provider = $this->getProvider();
$workspace_id = $this->getWorkspaceID();
$object = $this->getStoryObject();
$src_phid = $object->getPHID();
$publisher = $this->getPublisher();
// Figure out all the users related to the object. Users go into one of
// four buckets:
//
// - Owner: the owner of the object. This user becomes the assigned owner
// of the parent task.
// - Active: users who are responsible for the object and need to act on
// it. For example, reviewers of a "needs review" revision.
// - Passive: users who are responsible for the object, but do not need
// to act on it right now. For example, reviewers of a "needs revision"
// revision.
// - Follow: users who are following the object; generally CCs.
$owner_phid = $publisher->getOwnerPHID($object);
$active_phids = $publisher->getActiveUserPHIDs($object);
$passive_phids = $publisher->getPassiveUserPHIDs($object);
$follow_phids = $publisher->getCCUserPHIDs($object);
$all_phids = array();
$all_phids = array_merge(
array($owner_phid),
$active_phids,
$passive_phids,
$follow_phids);
$all_phids = array_unique(array_filter($all_phids));
$phid_aid_map = $this->lookupAsanaUserIDs($all_phids);
if (!$phid_aid_map) {
throw new PhabricatorWorkerPermanentFailureException(
pht('No related users have linked Asana accounts.'));
}
$owner_asana_id = idx($phid_aid_map, $owner_phid);
$all_asana_ids = array_select_keys($phid_aid_map, $all_phids);
$all_asana_ids = array_values($all_asana_ids);
// Even if the actor isn't a reviewer, etc., try to use their account so
// we can post in the correct voice. If we miss, we'll try all the other
// related users.
$try_users = array_merge(
array($data->getAuthorPHID()),
array_keys($phid_aid_map));
$try_users = array_filter($try_users);
$access_info = $this->findAnyValidAsanaAccessToken($try_users);
list($possessed_user, $possessed_asana_id, $oauth_token) = $access_info;
if (!$oauth_token) {
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Unable to find any Asana user with valid credentials to '.
'pull an OAuth token out of.'));
}
$etype_main = PhabricatorObjectHasAsanaTaskEdgeType::EDGECONST;
$etype_sub = PhabricatorObjectHasAsanaSubtaskEdgeType::EDGECONST;
$equery = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($src_phid))
->withEdgeTypes(
array(
$etype_main,
$etype_sub,
))
->needEdgeData(true);
$edges = $equery->execute();
$main_edge = head($edges[$src_phid][$etype_main]);
$main_data = $this->getAsanaTaskData($object) + array(
'assignee' => $owner_asana_id,
);
$projects = $this->getAsanaProjectIDs();
$extra_data = array();
if ($main_edge) {
$extra_data = $main_edge['data'];
$refs = id(new DoorkeeperImportEngine())
->setViewer($possessed_user)
->withPHIDs(array($main_edge['dst']))
->execute();
$parent_ref = head($refs);
if (!$parent_ref) {
throw new PhabricatorWorkerPermanentFailureException(
pht('%s could not be loaded.', 'DoorkeeperExternalObject'));
}
if ($parent_ref->getSyncFailed()) {
throw new Exception(
pht('Synchronization of parent task from Asana failed!'));
} else if (!$parent_ref->getIsVisible()) {
$this->log(
"%s\n",
pht('Skipping main task update, object is no longer visible.'));
$extra_data['gone'] = true;
} else {
$edge_cursor = idx($main_edge['data'], 'cursor', 0);
// TODO: This probably breaks, very rarely, on 32-bit systems.
if ($edge_cursor <= $story->getChronologicalKey()) {
$this->log("%s\n", pht('Updating main task.'));
$task_id = $parent_ref->getObjectID();
$this->makeAsanaAPICall(
$oauth_token,
'tasks/'.$parent_ref->getObjectID(),
'PUT',
$main_data);
} else {
$this->log(
"%s\n",
pht('Skipping main task update, cursor is ahead of the story.'));
}
}
} else {
// If there are no followers (CCs), and no active or passive users
// (reviewers or auditors), and we haven't synchronized the object before,
// don't synchronize the object.
if (!$active_phids && !$passive_phids && !$follow_phids) {
$this->log(
"%s\n",
pht('Object has no followers or active/passive users.'));
return;
}
$parent = $this->makeAsanaAPICall(
$oauth_token,
'tasks',
'POST',
array(
'workspace' => $workspace_id,
'projects' => $projects,
// NOTE: We initially create parent tasks in the "Later" state but
// don't update it afterward, even if the corresponding object
// becomes actionable. The expectation is that users will prioritize
// tasks in responses to notifications of state changes, and that
// we should not overwrite their choices.
'assignee_status' => 'later',
) + $main_data);
$parent_ref = $this->newRefFromResult(
DoorkeeperBridgeAsana::OBJTYPE_TASK,
$parent);
$extra_data = array(
'workspace' => $workspace_id,
);
}
// Synchronize main task followers.
$task_id = $parent_ref->getObjectID();
// Reviewers are added as followers of the parent task silently, because
// they receive a notification when they are assigned as the owner of their
// subtask, so the follow notification is redundant / non-actionable.
$silent_followers = array_select_keys($phid_aid_map, $active_phids) +
array_select_keys($phid_aid_map, $passive_phids);
$silent_followers = array_values($silent_followers);
// CCs are added as followers of the parent task with normal notifications,
// since they won't get a secondary subtask notification.
$noisy_followers = array_select_keys($phid_aid_map, $follow_phids);
$noisy_followers = array_values($noisy_followers);
// To synchronize follower data, just add all the followers. The task might
// have additional followers, but we can't really tell how they got there:
// were they CC'd and then unsubscribed, or did they manually follow the
// task? Assume the latter since it's easier and less destructive and the
// former is rare. To be fully consistent, we should enumerate followers
// and remove unknown followers, but that's a fair amount of work for little
// benefit, and creates a wider window for race conditions.
// Add the silent followers first so that a user who is both a reviewer and
// a CC gets silently added and then implicitly skipped by then noisy add.
// They will get a subtask notification.
// We only do this if the task still exists.
if (empty($extra_data['gone'])) {
$this->addFollowers($oauth_token, $task_id, $silent_followers, true);
$this->addFollowers($oauth_token, $task_id, $noisy_followers);
// We're also going to synchronize project data here.
$this->addProjects($oauth_token, $task_id, $projects);
}
$dst_phid = $parent_ref->getExternalObject()->getPHID();
// Update the main edge.
$edge_data = array(
'cursor' => $story->getChronologicalKey(),
) + $extra_data;
$edge_options = array(
'data' => $edge_data,
);
id(new PhabricatorEdgeEditor())
->addEdge($src_phid, $etype_main, $dst_phid, $edge_options)
->save();
if (!$parent_ref->getIsVisible()) {
throw new PhabricatorWorkerPermanentFailureException(
pht(
'%s has no visible object on the other side; this '.
'likely indicates the Asana task has been deleted.',
'DoorkeeperExternalObject'));
}
// Now, handle the subtasks.
$sub_editor = new PhabricatorEdgeEditor();
// First, find all the object references in Phabricator for tasks that we
// know about and import their objects from Asana.
$sub_edges = $edges[$src_phid][$etype_sub];
$sub_refs = array();
$subtask_data = $this->getAsanaSubtaskData($object);
$have_phids = array();
if ($sub_edges) {
$refs = id(new DoorkeeperImportEngine())
->setViewer($possessed_user)
->withPHIDs(array_keys($sub_edges))
->execute();
foreach ($refs as $ref) {
if ($ref->getSyncFailed()) {
throw new Exception(
pht('Synchronization of child task from Asana failed!'));
}
if (!$ref->getIsVisible()) {
$ref->getExternalObject()->delete();
continue;
}
$have_phids[$ref->getExternalObject()->getPHID()] = $ref;
}
}
// Remove any edges in Phabricator which don't have valid tasks in Asana.
// These are likely tasks which have been deleted. We're going to respawn
// them.
foreach ($sub_edges as $sub_phid => $sub_edge) {
if (isset($have_phids[$sub_phid])) {
continue;
}
$this->log(
"%s\n",
pht(
'Removing subtask edge to %s, foreign object is not visible.',
$sub_phid));
$sub_editor->removeEdge($src_phid, $etype_sub, $sub_phid);
unset($sub_edges[$sub_phid]);
}
// For each active or passive user, we're looking for an existing, valid
// task. If we find one we're going to update it; if we don't, we'll
// create one. We ignore extra subtasks that we didn't create (we gain
// nothing by deleting them and might be nuking something important) and
// ignore subtasks which have been moved across workspaces or replanted
// under new parents (this stuff is too edge-casey to bother checking for
// and complicated to fix, as it needs extra API calls). However, we do
// clean up subtasks we created whose owners are no longer associated
// with the object.
$subtask_states = array_fill_keys($active_phids, false) +
array_fill_keys($passive_phids, true);
// Continue with only those users who have Asana credentials.
$subtask_states = array_select_keys(
$subtask_states,
array_keys($phid_aid_map));
$need_subtasks = $subtask_states;
$user_to_ref_map = array();
$nuke_refs = array();
foreach ($sub_edges as $sub_phid => $sub_edge) {
$user_phid = idx($sub_edge['data'], 'userPHID');
if (isset($need_subtasks[$user_phid])) {
unset($need_subtasks[$user_phid]);
$user_to_ref_map[$user_phid] = $have_phids[$sub_phid];
} else {
// This user isn't associated with the object anymore, so get rid
// of their task and edge.
$nuke_refs[$sub_phid] = $have_phids[$sub_phid];
}
}
// These are tasks we know about but which are no longer relevant -- for
// example, because a user has been removed as a reviewer. Remove them and
// their edges.
foreach ($nuke_refs as $sub_phid => $ref) {
$sub_editor->removeEdge($src_phid, $etype_sub, $sub_phid);
$this->makeAsanaAPICall(
$oauth_token,
'tasks/'.$ref->getObjectID(),
'DELETE',
array());
$ref->getExternalObject()->delete();
}
// For each user that we don't have a subtask for, create a new subtask.
foreach ($need_subtasks as $user_phid => $is_completed) {
$subtask = $this->makeAsanaAPICall(
$oauth_token,
'tasks',
'POST',
$subtask_data + array(
'assignee' => $phid_aid_map[$user_phid],
'completed' => (int)$is_completed,
'parent' => $parent_ref->getObjectID(),
));
$subtask_ref = $this->newRefFromResult(
DoorkeeperBridgeAsana::OBJTYPE_TASK,
$subtask);
$user_to_ref_map[$user_phid] = $subtask_ref;
// We don't need to synchronize this subtask's state because we just
// set it when we created it.
unset($subtask_states[$user_phid]);
// Add an edge to track this subtask.
$sub_editor->addEdge(
$src_phid,
$etype_sub,
$subtask_ref->getExternalObject()->getPHID(),
array(
'data' => array(
'userPHID' => $user_phid,
),
));
}
// Synchronize all the previously-existing subtasks.
foreach ($subtask_states as $user_phid => $is_completed) {
$this->makeAsanaAPICall(
$oauth_token,
'tasks/'.$user_to_ref_map[$user_phid]->getObjectID(),
'PUT',
$subtask_data + array(
'assignee' => $phid_aid_map[$user_phid],
'completed' => (int)$is_completed,
));
}
foreach ($user_to_ref_map as $user_phid => $ref) {
// For each subtask, if the acting user isn't the same user as the subtask
// owner, remove the acting user as a follower. Currently, the acting user
// will be added as a follower only when they create the task, but this
// may change in the future (e.g., closing the task may also mark them
// as a follower). Wipe every subtask to be sure. The intent here is to
// leave only the owner as a follower so that the acting user doesn't
// receive notifications about changes to subtask state. Note that
// removing followers is silent in all cases in Asana and never produces
// any kind of notification, so this isn't self-defeating.
if ($user_phid != $possessed_user->getPHID()) {
$this->makeAsanaAPICall(
$oauth_token,
'tasks/'.$ref->getObjectID().'/removeFollowers',
'POST',
array(
'followers' => array($possessed_asana_id),
));
}
}
// Update edges on our side.
$sub_editor->save();
// Don't publish the "create" story, since pushing the object into Asana
// naturally generates a notification which effectively serves the same
// purpose as the "create" story. Similarly, "close" stories generate a
// close notification.
if (!$publisher->isStoryAboutObjectCreation($object) &&
!$publisher->isStoryAboutObjectClosure($object)) {
// Post the feed story itself to the main Asana task. We do this last
// because everything else is idempotent, so this is the only effect we
// can't safely run more than once.
$text = $publisher
->setRenderWithImpliedContext(true)
->getStoryText($object);
$this->makeAsanaAPICall(
$oauth_token,
'tasks/'.$parent_ref->getObjectID().'/stories',
'POST',
array(
'text' => $text,
));
}
}
/* -( Internals )---------------------------------------------------------- */
private function getWorkspaceID() {
return PhabricatorEnv::getEnvConfig('asana.workspace-id');
}
private function getProvider() {
if (!$this->provider) {
$provider = PhabricatorAsanaAuthProvider::getAsanaProvider();
if (!$provider) {
throw new PhabricatorWorkerPermanentFailureException(
pht('No Asana provider configured.'));
}
$this->provider = $provider;
}
return $this->provider;
}
private function getAsanaTaskData($object) {
$publisher = $this->getPublisher();
$title = $publisher->getObjectTitle($object);
$uri = $publisher->getObjectURI($object);
$description = $publisher->getObjectDescription($object);
$is_completed = $publisher->isObjectClosed($object);
$notes = array(
$description,
$uri,
$this->getSynchronizationWarning(),
);
$notes = implode("\n\n", $notes);
return array(
'name' => $title,
'notes' => $notes,
'completed' => (int)$is_completed,
);
}
private function getAsanaSubtaskData($object) {
$publisher = $this->getPublisher();
$title = $publisher->getResponsibilityTitle($object);
$uri = $publisher->getObjectURI($object);
$description = $publisher->getObjectDescription($object);
$notes = array(
$description,
$uri,
$this->getSynchronizationWarning(),
);
$notes = implode("\n\n", $notes);
return array(
'name' => $title,
'notes' => $notes,
);
}
private function getSynchronizationWarning() {
return pht(
"\xE2\x9A\xA0 DO NOT EDIT THIS TASK \xE2\x9A\xA0\n".
- "\xE2\x98\xA0 Your changes will not be reflected in Phabricator.\n".
+ "\xE2\x98\xA0 Your changes will not be reflected in %s.\n".
"\xE2\x98\xA0 Your changes will be destroyed the next time state ".
- "is synchronized.");
+ "is synchronized.",
+ PlatformSymbols::getPlatformServerName());
}
private function lookupAsanaUserIDs($all_phids) {
$phid_map = array();
$all_phids = array_unique(array_filter($all_phids));
if (!$all_phids) {
return $phid_map;
}
$accounts = $this->loadAsanaExternalAccounts($all_phids);
foreach ($accounts as $account) {
$phid_map[$account->getUserPHID()] = $this->getAsanaAccountID($account);
}
// Put this back in input order.
$phid_map = array_select_keys($phid_map, $all_phids);
return $phid_map;
}
private function loadAsanaExternalAccounts(array $user_phids) {
$provider = $this->getProvider();
$viewer = $this->getViewer();
if (!$user_phids) {
return array();
}
$accounts = id(new PhabricatorExternalAccountQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withUserPHIDs($user_phids)
->withProviderConfigPHIDs(
array(
$provider->getProviderConfigPHID(),
))
->needAccountIdentifiers(true)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->execute();
return $accounts;
}
private function findAnyValidAsanaAccessToken(array $user_phids) {
$provider = $this->getProvider();
$viewer = $this->getViewer();
if (!$user_phids) {
return array(null, null, null);
}
$accounts = $this->loadAsanaExternalAccounts($user_phids);
// Reorder accounts in the original order.
// TODO: This needs to be adjusted if/when we allow you to link multiple
// accounts.
$accounts = mpull($accounts, null, 'getUserPHID');
$accounts = array_select_keys($accounts, $user_phids);
$workspace_id = $this->getWorkspaceID();
foreach ($accounts as $account) {
// Get a token if possible.
$token = $provider->getOAuthAccessToken($account);
if (!$token) {
continue;
}
// Verify we can actually make a call with the token, and that the user
// has access to the workspace in question.
try {
id(new PhutilAsanaFuture())
->setAccessToken($token)
->setRawAsanaQuery("workspaces/{$workspace_id}")
->resolve();
} catch (Exception $ex) {
// This token didn't make it through; try the next account.
continue;
}
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withPHIDs(array($account->getUserPHID()))
->executeOne();
if ($user) {
return array($user, $this->getAsanaAccountID($account), $token);
}
}
return array(null, null, null);
}
private function makeAsanaAPICall($token, $action, $method, array $params) {
foreach ($params as $key => $value) {
if ($value === null) {
unset($params[$key]);
} else if (is_array($value)) {
unset($params[$key]);
foreach ($value as $skey => $svalue) {
$params[$key.'['.$skey.']'] = $svalue;
}
}
}
return id(new PhutilAsanaFuture())
->setAccessToken($token)
->setMethod($method)
->setRawAsanaQuery($action, $params)
->resolve();
}
private function newRefFromResult($type, $result) {
$ref = id(new DoorkeeperObjectRef())
->setApplicationType(DoorkeeperBridgeAsana::APPTYPE_ASANA)
->setApplicationDomain(DoorkeeperBridgeAsana::APPDOMAIN_ASANA)
->setObjectType($type)
->setObjectID($result['gid'])
->setIsVisible(true);
$xobj = $ref->newExternalObject();
$ref->attachExternalObject($xobj);
$bridge = new DoorkeeperBridgeAsana();
$bridge->fillObjectFromData($xobj, $result);
$xobj->save();
return $ref;
}
private function addFollowers(
$oauth_token,
$task_id,
array $followers,
$silent = false) {
if (!$followers) {
return;
}
$data = array(
'followers' => $followers,
);
// NOTE: This uses a currently-undocumented API feature to suppress the
// follow notifications.
if ($silent) {
$data['silent'] = true;
}
$this->makeAsanaAPICall(
$oauth_token,
"tasks/{$task_id}/addFollowers",
'POST',
$data);
}
private function getAsanaProjectIDs() {
$project_ids = array();
$publisher = $this->getPublisher();
$config = PhabricatorEnv::getEnvConfig('asana.project-ids');
if (is_array($config)) {
$ids = idx($config, get_class($publisher));
if (is_array($ids)) {
foreach ($ids as $id) {
if (is_scalar($id)) {
$project_ids[] = $id;
}
}
}
}
return $project_ids;
}
private function addProjects(
$oauth_token,
$task_id,
array $project_ids) {
foreach ($project_ids as $project_id) {
$data = array('project' => $project_id);
$this->makeAsanaAPICall(
$oauth_token,
"tasks/{$task_id}/addProject",
'POST',
$data);
}
}
private function getAsanaAccountID(PhabricatorExternalAccount $account) {
$identifiers = $account->getAccountIdentifiers();
if (count($identifiers) !== 1) {
throw new Exception(
pht(
'Expected external Asana account to have exactly one external '.
'account identifier, found %s.',
phutil_count($identifiers)));
}
return head($identifiers)->getIdentifierRaw();
}
}
diff --git a/src/applications/doorkeeper/worker/DoorkeeperFeedWorker.php b/src/applications/doorkeeper/worker/DoorkeeperFeedWorker.php
index 404ee348f7..8bbcc0f4fa 100644
--- a/src/applications/doorkeeper/worker/DoorkeeperFeedWorker.php
+++ b/src/applications/doorkeeper/worker/DoorkeeperFeedWorker.php
@@ -1,203 +1,203 @@
<?php
/**
* Publish events (like comments on a revision) to external objects which are
* linked through Doorkeeper (like a linked JIRA or Asana task).
*
* These workers are invoked by feed infrastructure during normal task queue
* operations. They read feed stories and publish information about them to
* external systems, generally mirroring comments and updates in Phabricator
* into remote systems by making API calls.
*
* @task publish Publishing Stories
* @task context Story Context
* @task internal Internals
*/
abstract class DoorkeeperFeedWorker extends FeedPushWorker {
private $publisher;
private $feedStory;
private $storyObject;
/* -( Publishing Stories )------------------------------------------------- */
/**
* Actually publish the feed story. Subclasses will generally make API calls
* to publish some version of the story into external systems.
*
* @return void
* @task publish
*/
abstract protected function publishFeedStory();
/**
* Enable or disable the worker. Normally, this checks configuration to
* see if Phabricator is linked to applicable external systems.
*
* @return bool True if this worker should try to publish stories.
* @task publish
*/
abstract public function isEnabled();
/* -( Story Context )------------------------------------------------------ */
/**
* Get the @{class:PhabricatorFeedStory} that should be published.
*
* @return PhabricatorFeedStory The story to publish.
* @task context
*/
protected function getFeedStory() {
if (!$this->feedStory) {
$story = $this->loadFeedStory();
$this->feedStory = $story;
}
return $this->feedStory;
}
/**
* Get the viewer for the act of publishing.
*
* NOTE: Publishing currently uses the omnipotent viewer because it depends
* on loading external accounts. Possibly we should tailor this. See T3732.
* Using the actor for most operations might make more sense.
*
* @return PhabricatorUser Viewer.
* @task context
*/
protected function getViewer() {
return PhabricatorUser::getOmnipotentUser();
}
/**
* Get the @{class:DoorkeeperFeedStoryPublisher} which handles this object.
*
* @return DoorkeeperFeedStoryPublisher Object publisher.
* @task context
*/
protected function getPublisher() {
return $this->publisher;
}
/**
* Get the primary object the story is about, like a
* @{class:DifferentialRevision} or @{class:ManiphestTask}.
*
* @return object Object which the story is about.
* @task context
*/
protected function getStoryObject() {
if (!$this->storyObject) {
$story = $this->getFeedStory();
try {
$object = $story->getPrimaryObject();
} catch (Exception $ex) {
throw new PhabricatorWorkerPermanentFailureException(
$ex->getMessage());
}
$this->storyObject = $object;
}
return $this->storyObject;
}
/* -( Internals )---------------------------------------------------------- */
/**
* Load the @{class:DoorkeeperFeedStoryPublisher} which corresponds to this
* object. Publishers provide a common API for pushing object updates into
* foreign systems.
*
* @return DoorkeeperFeedStoryPublisher Publisher for the story's object.
* @task internal
*/
private function loadPublisher() {
$story = $this->getFeedStory();
$viewer = $this->getViewer();
$object = $this->getStoryObject();
$publishers = id(new PhutilClassMapQuery())
->setAncestorClass('DoorkeeperFeedStoryPublisher')
->execute();
foreach ($publishers as $publisher) {
if (!$publisher->canPublishStory($story, $object)) {
continue;
}
$publisher
->setViewer($viewer)
->setFeedStory($story);
$object = $publisher->willPublishStory($object);
$this->storyObject = $object;
$this->publisher = $publisher;
break;
}
return $this->publisher;
}
/* -( Inherited )---------------------------------------------------------- */
/**
* Doorkeeper workers set up some context, then call
* @{method:publishFeedStory}.
*/
final protected function doWork() {
if (PhabricatorEnv::getEnvConfig('phabricator.silent')) {
- $this->log("%s\n", pht('Phabricator is running in silent mode.'));
+ $this->log("%s\n", pht('This software is running in silent mode.'));
return;
}
if (!$this->isEnabled()) {
$this->log(
"%s\n",
pht("Doorkeeper worker '%s' is not enabled.", get_class($this)));
return;
}
$publisher = $this->loadPublisher();
if (!$publisher) {
$this->log("%s\n", pht('Story is about an unsupported object type.'));
return;
} else {
$this->log("%s\n", pht("Using publisher '%s'.", get_class($publisher)));
}
$this->publishFeedStory();
}
/**
* By default, Doorkeeper workers perform a small number of retries with
* exponential backoff. A consideration in this policy is that many of these
* workers are laden with side effects.
*/
public function getMaximumRetryCount() {
return 4;
}
/**
* See @{method:getMaximumRetryCount} for a description of Doorkeeper
* retry defaults.
*/
public function getWaitBeforeRetry(PhabricatorWorkerTask $task) {
$count = $task->getFailureCount();
return (5 * 60) * pow(8, $count);
}
}
diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
index 88bc4d935a..27ba6e623f 100644
--- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
+++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
@@ -1,526 +1,543 @@
<?php
/**
* @task lease Lease Acquisition
* @task resource Resource Allocation
* @task interface Resource Interfaces
* @task log Logging
*/
abstract class DrydockBlueprintImplementation extends Phobject {
abstract public function getType();
abstract public function isEnabled();
abstract public function getBlueprintName();
abstract public function getDescription();
public function getBlueprintIcon() {
return 'fa-map-o';
}
public function getFieldSpecifications() {
$fields = array();
$fields += $this->getCustomFieldSpecifications();
if ($this->shouldUseConcurrentResourceLimit()) {
$fields += array(
'allocator.limit' => array(
'name' => pht('Limit'),
'caption' => pht(
'Maximum number of resources this blueprint can have active '.
'concurrently.'),
'type' => 'int',
),
);
}
return $fields;
}
protected function getCustomFieldSpecifications() {
return array();
}
public function getViewer() {
return PhabricatorUser::getOmnipotentUser();
}
/* -( Lease Acquisition )-------------------------------------------------- */
/**
* Enforce basic checks on lease/resource compatibility. Allows resources to
* reject leases if they are incompatible, even if the resource types match.
*
* For example, if a resource represents a 32-bit host, this method might
* reject leases that need a 64-bit host. The blueprint might also reject
* a resource if the lease needs 8GB of RAM and the resource only has 6GB
* free.
*
* This method should not acquire locks or expect anything to be locked. This
* is a coarse compatibility check between a lease and a resource.
*
* @param DrydockBlueprint Concrete blueprint to allocate for.
* @param DrydockResource Candidate resource to allocate the lease on.
* @param DrydockLease Pending lease that wants to allocate here.
* @return bool True if the resource and lease are compatible.
* @task lease
*/
abstract public function canAcquireLeaseOnResource(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease);
/**
* Acquire a lease. Allows resources to perform setup as leases are brought
* online.
*
* If acquisition fails, throw an exception.
*
* @param DrydockBlueprint Blueprint which built the resource.
* @param DrydockResource Resource to acquire a lease on.
* @param DrydockLease Requested lease.
* @return void
* @task lease
*/
abstract public function acquireLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease);
/**
* @return void
* @task lease
*/
public function activateLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
throw new PhutilMethodNotImplementedException();
}
/**
* React to a lease being released.
*
* This callback is primarily useful for automatically releasing resources
* once all leases are released.
*
* @param DrydockBlueprint Blueprint which built the resource.
* @param DrydockResource Resource a lease was released on.
* @param DrydockLease Recently released lease.
* @return void
* @task lease
*/
abstract public function didReleaseLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease);
/**
* Destroy any temporary data associated with a lease.
*
* If a lease creates temporary state while held, destroy it here.
*
* @param DrydockBlueprint Blueprint which built the resource.
* @param DrydockResource Resource the lease is acquired on.
* @param DrydockLease The lease being destroyed.
* @return void
* @task lease
*/
abstract public function destroyLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease);
/**
* Return true to try to allocate a new resource and expand the resource
* pool instead of permitting an otherwise valid acquisition on an existing
* resource.
*
* This allows the blueprint to provide a soft hint about when the resource
* pool should grow.
*
* Returning "true" in all cases generally makes sense when a blueprint
* controls a fixed pool of resources, like a particular number of physical
* hosts: you want to put all the hosts in service, so whenever it is
* possible to allocate a new host you want to do this.
*
* Returning "false" in all cases generally make sense when a blueprint
* has a flexible pool of expensive resources and you want to pack leases
* onto them as tightly as possible.
*
* @param DrydockBlueprint The blueprint for an existing resource being
* acquired.
* @param DrydockResource The resource being acquired, which we may want to
* build a supplemental resource for.
* @param DrydockLease The current lease performing acquisition.
* @return bool True to prefer allocating a supplemental resource.
*
* @task lease
*/
public function shouldAllocateSupplementalResource(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
return false;
}
/* -( Resource Allocation )------------------------------------------------ */
/**
* Enforce fundamental implementation/lease checks. Allows implementations to
* reject a lease which no concrete blueprint can ever satisfy.
*
* For example, if a lease only builds ARM hosts and the lease needs a
* PowerPC host, it may be rejected here.
*
* This is the earliest rejection phase, and followed by
* @{method:canEverAllocateResourceForLease}.
*
* This method should not actually check if a resource can be allocated
* right now, or even if a blueprint which can allocate a suitable resource
* really exists, only if some blueprint may conceivably exist which could
* plausibly be able to build a suitable resource.
*
* @param DrydockLease Requested lease.
* @return bool True if some concrete blueprint of this implementation's
* type might ever be able to build a resource for the lease.
* @task resource
*/
abstract public function canAnyBlueprintEverAllocateResourceForLease(
DrydockLease $lease);
/**
* Enforce basic blueprint/lease checks. Allows blueprints to reject a lease
* which they can not build a resource for.
*
* This is the second rejection phase. It follows
* @{method:canAnyBlueprintEverAllocateResourceForLease} and is followed by
* @{method:canAllocateResourceForLease}.
*
* This method should not check if a resource can be built right now, only
* if the blueprint as configured may, at some time, be able to build a
* suitable resource.
*
* @param DrydockBlueprint Blueprint which may be asked to allocate a
* resource.
* @param DrydockLease Requested lease.
* @return bool True if this blueprint can eventually build a suitable
* resource for the lease, as currently configured.
* @task resource
*/
abstract public function canEverAllocateResourceForLease(
DrydockBlueprint $blueprint,
DrydockLease $lease);
/**
* Enforce basic availability limits. Allows blueprints to reject resource
* allocation if they are currently overallocated.
*
* This method should perform basic capacity/limit checks. For example, if
* it has a limit of 6 resources and currently has 6 resources allocated,
* it might reject new leases.
*
* This method should not acquire locks or expect locks to be acquired. This
* is a coarse check to determine if the operation is likely to succeed
* right now without needing to acquire locks.
*
* It is expected that this method will sometimes return `true` (indicating
* that a resource can be allocated) but find that another allocator has
* eaten up free capacity by the time it actually tries to build a resource.
* This is normal and the allocator will recover from it.
*
* @param DrydockBlueprint The blueprint which may be asked to allocate a
* resource.
* @param DrydockLease Requested lease.
* @return bool True if this blueprint appears likely to be able to allocate
* a suitable resource.
* @task resource
*/
abstract public function canAllocateResourceForLease(
DrydockBlueprint $blueprint,
DrydockLease $lease);
/**
* Allocate a suitable resource for a lease.
*
* This method MUST acquire, hold, and manage locks to prevent multiple
* allocations from racing. World state is not locked before this method is
* called. Blueprints are entirely responsible for any lock handling they
* need to perform.
*
* @param DrydockBlueprint The blueprint which should allocate a resource.
* @param DrydockLease Requested lease.
* @return DrydockResource Allocated resource.
* @task resource
*/
abstract public function allocateResource(
DrydockBlueprint $blueprint,
DrydockLease $lease);
/**
* @task resource
*/
public function activateResource(
DrydockBlueprint $blueprint,
DrydockResource $resource) {
throw new PhutilMethodNotImplementedException();
}
/**
* Destroy any temporary data associated with a resource.
*
* If a resource creates temporary state when allocated, destroy that state
* here. For example, you might shut down a virtual host or destroy a working
* copy on disk.
*
* @param DrydockBlueprint Blueprint which built the resource.
* @param DrydockResource Resource being destroyed.
* @return void
* @task resource
*/
abstract public function destroyResource(
DrydockBlueprint $blueprint,
DrydockResource $resource);
/**
* Get a human readable name for a resource.
*
* @param DrydockBlueprint Blueprint which built the resource.
* @param DrydockResource Resource to get the name of.
* @return string Human-readable resource name.
* @task resource
*/
abstract public function getResourceName(
DrydockBlueprint $blueprint,
DrydockResource $resource);
/* -( Resource Interfaces )------------------------------------------------ */
abstract public function getInterface(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease,
$type);
/* -( Logging )------------------------------------------------------------ */
public static function getAllBlueprintImplementations() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->execute();
}
+
+ /**
+ * Get all the @{class:DrydockBlueprintImplementation}s which can possibly
+ * build a resource to satisfy a lease.
+ *
+ * This method returns blueprints which might, at some time, be able to
+ * build a resource which can satisfy the lease. They may not be able to
+ * build that resource right now.
+ *
+ * @param DrydockLease Requested lease.
+ * @return list<DrydockBlueprintImplementation> List of qualifying blueprint
+ * implementations.
+ */
+ public static function getAllForAllocatingLease(
+ DrydockLease $lease) {
+
+ $impls = self::getAllBlueprintImplementations();
+
+ $keep = array();
+ foreach ($impls as $key => $impl) {
+ // Don't use disabled blueprint types.
+ if (!$impl->isEnabled()) {
+ continue;
+ }
+
+ // Don't use blueprint types which can't allocate the correct kind of
+ // resource.
+ if ($impl->getType() != $lease->getResourceType()) {
+ continue;
+ }
+
+ if (!$impl->canAnyBlueprintEverAllocateResourceForLease($lease)) {
+ continue;
+ }
+
+ $keep[$key] = $impl;
+ }
+
+ return $keep;
+ }
+
public static function getNamedImplementation($class) {
return idx(self::getAllBlueprintImplementations(), $class);
}
protected function newResourceTemplate(DrydockBlueprint $blueprint) {
$resource = id(new DrydockResource())
->setBlueprintPHID($blueprint->getPHID())
->attachBlueprint($blueprint)
->setType($this->getType())
->setStatus(DrydockResourceStatus::STATUS_PENDING);
// Pre-allocate the resource PHID.
$resource->setPHID($resource->generatePHID());
return $resource;
}
protected function newLease(DrydockBlueprint $blueprint) {
return DrydockLease::initializeNewLease()
->setAuthorizingPHID($blueprint->getPHID());
}
protected function requireActiveLease(DrydockLease $lease) {
$lease_status = $lease->getStatus();
switch ($lease_status) {
case DrydockLeaseStatus::STATUS_PENDING:
case DrydockLeaseStatus::STATUS_ACQUIRED:
throw new PhabricatorWorkerYieldException(15);
case DrydockLeaseStatus::STATUS_ACTIVE:
return;
default:
throw new Exception(
pht(
'Lease ("%s") is in bad state ("%s"), expected "%s".',
$lease->getPHID(),
$lease_status,
DrydockLeaseStatus::STATUS_ACTIVE));
}
}
/**
* Does this implementation use concurrent resource limits?
*
* Implementations can override this method to opt into standard limit
* behavior, which provides a simple concurrent resource limit.
*
* @return bool True to use limits.
*/
protected function shouldUseConcurrentResourceLimit() {
return false;
}
/**
* Get the effective concurrent resource limit for this blueprint.
*
* @param DrydockBlueprint Blueprint to get the limit for.
* @return int|null Limit, or `null` for no limit.
*/
protected function getConcurrentResourceLimit(DrydockBlueprint $blueprint) {
if ($this->shouldUseConcurrentResourceLimit()) {
$limit = $blueprint->getFieldValue('allocator.limit');
$limit = (int)$limit;
if ($limit > 0) {
return $limit;
} else {
return null;
}
}
return null;
}
protected function getConcurrentResourceLimitSlotLock(
DrydockBlueprint $blueprint) {
$limit = $this->getConcurrentResourceLimit($blueprint);
if ($limit === null) {
return;
}
$blueprint_phid = $blueprint->getPHID();
// TODO: This logic shouldn't do anything awful, but is a little silly. It
// would be nice to unify the "huge limit" and "small limit" cases
// eventually but it's a little tricky.
// If the limit is huge, just pick a random slot. This is just stopping
// us from exploding if someone types a billion zillion into the box.
if ($limit > 1024) {
$slot = mt_rand(0, $limit - 1);
return "allocator({$blueprint_phid}).limit({$slot})";
}
// For reasonable limits, actually check for an available slot.
$slots = range(0, $limit - 1);
shuffle($slots);
$lock_names = array();
foreach ($slots as $slot) {
$lock_names[] = "allocator({$blueprint_phid}).limit({$slot})";
}
$locks = DrydockSlotLock::loadHeldLocks($lock_names);
$locks = mpull($locks, null, 'getLockKey');
foreach ($lock_names as $lock_name) {
if (empty($locks[$lock_name])) {
return $lock_name;
}
}
// If we found no free slot, just return whatever we checked last (which
// is just a random slot). There's a small chance we'll get lucky and the
// lock will be free by the time we try to take it, but usually we'll just
// fail to grab the lock, throw an appropriate lock exception, and get back
// on the right path to retry later.
return $lock_name;
}
/**
* Apply standard limits on resource allocation rate.
*
* @param DrydockBlueprint The blueprint requesting an allocation.
* @return bool True if further allocations should be limited.
*/
protected function shouldLimitAllocatingPoolSize(
DrydockBlueprint $blueprint) {
- // TODO: If this mechanism sticks around, these values should be
- // configurable by the blueprint implementation.
-
// Limit on total number of active resources.
$total_limit = $this->getConcurrentResourceLimit($blueprint);
-
- // Always allow at least this many allocations to be in flight at once.
- $min_allowed = 1;
-
- // Allow this fraction of allocating resources as a fraction of active
- // resources.
- $growth_factor = 0.25;
+ if ($total_limit === null) {
+ return false;
+ }
$resource = new DrydockResource();
- $conn_r = $resource->establishConnection('r');
+ $conn = $resource->establishConnection('r');
$counts = queryfx_all(
- $conn_r,
- 'SELECT status, COUNT(*) N FROM %T
+ $conn,
+ 'SELECT status, COUNT(*) N FROM %R
WHERE blueprintPHID = %s AND status != %s
GROUP BY status',
- $resource->getTableName(),
+ $resource,
$blueprint->getPHID(),
DrydockResourceStatus::STATUS_DESTROYED);
$counts = ipull($counts, 'N', 'status');
$n_alloc = idx($counts, DrydockResourceStatus::STATUS_PENDING, 0);
$n_active = idx($counts, DrydockResourceStatus::STATUS_ACTIVE, 0);
$n_broken = idx($counts, DrydockResourceStatus::STATUS_BROKEN, 0);
$n_released = idx($counts, DrydockResourceStatus::STATUS_RELEASED, 0);
// If we're at the limit on total active resources, limit additional
// allocations.
- if ($total_limit !== null) {
- $n_total = ($n_alloc + $n_active + $n_broken + $n_released);
- if ($n_total >= $total_limit) {
- return true;
- }
+ $n_total = ($n_alloc + $n_active + $n_broken + $n_released);
+ if ($n_total >= $total_limit) {
+ return true;
}
- // If the number of in-flight allocations is fewer than the minimum number
- // of allowed allocations, don't impose a limit.
- if ($n_alloc < $min_allowed) {
- return false;
- }
-
- $allowed_alloc = (int)ceil($n_active * $growth_factor);
-
- // If the number of in-flight allocation is fewer than the number of
- // allowed allocations according to the pool growth factor, don't impose
- // a limit.
- if ($n_alloc < $allowed_alloc) {
- return false;
- }
-
- return true;
+ return false;
}
}
diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
index d82f5c2c15..2824444040 100644
--- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
+++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
@@ -1,542 +1,568 @@
<?php
final class DrydockWorkingCopyBlueprintImplementation
extends DrydockBlueprintImplementation {
const PHASE_SQUASHMERGE = 'squashmerge';
const PHASE_REMOTEFETCH = 'blueprint.workingcopy.fetch.remote';
const PHASE_MERGEFETCH = 'blueprint.workingcopy.fetch.staging';
public function isEnabled() {
return true;
}
public function getBlueprintName() {
return pht('Working Copy');
}
public function getBlueprintIcon() {
return 'fa-folder-open';
}
public function getDescription() {
return pht('Allows Drydock to check out working copies of repositories.');
}
public function canAnyBlueprintEverAllocateResourceForLease(
DrydockLease $lease) {
return true;
}
public function canEverAllocateResourceForLease(
DrydockBlueprint $blueprint,
DrydockLease $lease) {
return true;
}
public function canAllocateResourceForLease(
DrydockBlueprint $blueprint,
DrydockLease $lease) {
$viewer = $this->getViewer();
if ($this->shouldLimitAllocatingPoolSize($blueprint)) {
return false;
}
- // TODO: If we have a pending resource which is compatible with the
- // configuration for this lease, prevent a new allocation? Otherwise the
- // queue can fill up with copies of requests from the same lease. But
- // maybe we can deal with this with "pre-leasing"?
-
return true;
}
public function canAcquireLeaseOnResource(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
// Don't hand out leases on working copies which have not activated, since
// it may take an arbitrarily long time for them to acquire a host.
if (!$resource->isActive()) {
return false;
}
$need_map = $lease->getAttribute('repositories.map');
if (!is_array($need_map)) {
return false;
}
$have_map = $resource->getAttribute('repositories.map');
if (!is_array($have_map)) {
return false;
}
$have_as = ipull($have_map, 'phid');
$need_as = ipull($need_map, 'phid');
foreach ($need_as as $need_directory => $need_phid) {
if (empty($have_as[$need_directory])) {
// This resource is missing a required working copy.
return false;
}
if ($have_as[$need_directory] != $need_phid) {
// This resource has a required working copy, but it contains
// the wrong repository.
return false;
}
unset($have_as[$need_directory]);
}
if ($have_as && $lease->getAttribute('repositories.strict')) {
// This resource has extra repositories, but the lease is strict about
// which repositories are allowed to exist.
return false;
}
if (!DrydockSlotLock::isLockFree($this->getLeaseSlotLock($resource))) {
return false;
}
return true;
}
public function acquireLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
$lease
->needSlotLock($this->getLeaseSlotLock($resource))
->acquireOnResource($resource);
}
private function getLeaseSlotLock(DrydockResource $resource) {
$resource_phid = $resource->getPHID();
return "workingcopy.lease({$resource_phid})";
}
public function allocateResource(
DrydockBlueprint $blueprint,
DrydockLease $lease) {
$resource = $this->newResourceTemplate($blueprint);
$resource_phid = $resource->getPHID();
$blueprint_phids = $blueprint->getFieldValue('blueprintPHIDs');
$host_lease = $this->newLease($blueprint)
->setResourceType('host')
->setOwnerPHID($resource_phid)
->setAttribute('workingcopy.resourcePHID', $resource_phid)
->setAllowedBlueprintPHIDs($blueprint_phids);
$resource->setAttribute('host.leasePHID', $host_lease->getPHID());
- $map = $lease->getAttribute('repositories.map');
- foreach ($map as $key => $value) {
- $map[$key] = array_select_keys(
- $value,
- array(
- 'phid',
- ));
- }
+ $map = $this->getWorkingCopyRepositoryMap($lease);
$resource->setAttribute('repositories.map', $map);
$slot_lock = $this->getConcurrentResourceLimitSlotLock($blueprint);
if ($slot_lock !== null) {
$resource->needSlotLock($slot_lock);
}
$resource->allocateResource();
$host_lease->queueForActivation();
return $resource;
}
+ private function getWorkingCopyRepositoryMap(DrydockLease $lease) {
+ $attribute = 'repositories.map';
+ $map = $lease->getAttribute($attribute);
+
+ // TODO: Leases should validate their attributes more formally.
+
+ if (!is_array($map) || !$map) {
+ $message = array();
+ if ($map === null) {
+ $message[] = pht(
+ 'Working copy lease is missing required attribute "%s".',
+ $attribute);
+ } else {
+ $message[] = pht(
+ 'Working copy lease has invalid attribute "%s".',
+ $attribute);
+ }
+
+ $message[] = pht(
+ 'Attribute "repositories.map" should be a map of repository '.
+ 'specifications.');
+
+ $message = implode("\n\n", $message);
+
+ throw new Exception($message);
+ }
+
+ foreach ($map as $key => $value) {
+ $map[$key] = array_select_keys(
+ $value,
+ array(
+ 'phid',
+ ));
+ }
+
+ return $map;
+ }
+
public function activateResource(
DrydockBlueprint $blueprint,
DrydockResource $resource) {
$lease = $this->loadHostLease($resource);
$this->requireActiveLease($lease);
$command_type = DrydockCommandInterface::INTERFACE_TYPE;
$interface = $lease->getInterface($command_type);
// TODO: Make this configurable.
$resource_id = $resource->getID();
$root = "/var/drydock/workingcopy-{$resource_id}";
$map = $resource->getAttribute('repositories.map');
$futures = array();
$repositories = $this->loadRepositories(ipull($map, 'phid'));
foreach ($map as $directory => $spec) {
// TODO: Validate directory isn't goofy like "/etc" or "../../lol"
// somewhere?
$repository = $repositories[$spec['phid']];
$path = "{$root}/repo/{$directory}/";
$future = $interface->getExecFuture(
'git clone -- %s %s',
(string)$repository->getCloneURIObject(),
$path);
$future->setTimeout($repository->getEffectiveCopyTimeLimit());
$futures[$directory] = $future;
}
foreach (new FutureIterator($futures) as $key => $future) {
$future->resolvex();
}
$resource
->setAttribute('workingcopy.root', $root)
->activateResource();
}
public function destroyResource(
DrydockBlueprint $blueprint,
DrydockResource $resource) {
try {
$lease = $this->loadHostLease($resource);
} catch (Exception $ex) {
// If we can't load the lease, assume we don't need to take any actions
// to destroy it.
return;
}
// Destroy the lease on the host.
$lease->setReleaseOnDestruction(true);
if ($lease->isActive()) {
// Destroy the working copy on disk.
$command_type = DrydockCommandInterface::INTERFACE_TYPE;
$interface = $lease->getInterface($command_type);
$root_key = 'workingcopy.root';
$root = $resource->getAttribute($root_key);
if (strlen($root)) {
$interface->execx('rm -rf -- %s', $root);
}
}
}
public function getResourceName(
DrydockBlueprint $blueprint,
DrydockResource $resource) {
return pht('Working Copy');
}
public function activateLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
$host_lease = $this->loadHostLease($resource);
$command_type = DrydockCommandInterface::INTERFACE_TYPE;
$interface = $host_lease->getInterface($command_type);
$map = $lease->getAttribute('repositories.map');
$root = $resource->getAttribute('workingcopy.root');
$repositories = $this->loadRepositories(ipull($map, 'phid'));
$default = null;
foreach ($map as $directory => $spec) {
$repository = $repositories[$spec['phid']];
$interface->pushWorkingDirectory("{$root}/repo/{$directory}/");
$cmd = array();
$arg = array();
$cmd[] = 'git clean -d --force';
$cmd[] = 'git fetch';
$commit = idx($spec, 'commit');
$branch = idx($spec, 'branch');
$ref = idx($spec, 'ref');
// Reset things first, in case previous builds left anything staged or
// dirty. Note that we don't reset to "HEAD" because that does not work
// in empty repositories.
$cmd[] = 'git reset --hard';
if ($commit !== null) {
$cmd[] = 'git checkout %s --';
$arg[] = $commit;
} else if ($branch !== null) {
$cmd[] = 'git checkout %s --';
$arg[] = $branch;
$cmd[] = 'git reset --hard origin/%s';
$arg[] = $branch;
}
$this->newExecvFuture($interface, $cmd, $arg)
->setTimeout($repository->getEffectiveCopyTimeLimit())
->resolvex();
if (idx($spec, 'default')) {
$default = $directory;
}
// If we're fetching a ref from a remote, do that separately so we can
// raise a more tailored error.
if ($ref) {
$cmd = array();
$arg = array();
$ref_uri = $ref['uri'];
$ref_ref = $ref['ref'];
$cmd[] = 'git fetch --no-tags -- %s +%s:%s';
$arg[] = $ref_uri;
$arg[] = $ref_ref;
$arg[] = $ref_ref;
$cmd[] = 'git checkout %s --';
$arg[] = $ref_ref;
try {
$this->newExecvFuture($interface, $cmd, $arg)
->setTimeout($repository->getEffectiveCopyTimeLimit())
->resolvex();
} catch (CommandException $ex) {
$display_command = csprintf(
'git fetch %R %R',
$ref_uri,
$ref_ref);
$error = DrydockCommandError::newFromCommandException($ex)
->setPhase(self::PHASE_REMOTEFETCH)
->setDisplayCommand($display_command);
$lease->setAttribute(
'workingcopy.vcs.error',
$error->toDictionary());
throw $ex;
}
}
$merges = idx($spec, 'merges');
if ($merges) {
foreach ($merges as $merge) {
$this->applyMerge($lease, $interface, $merge);
}
}
$interface->popWorkingDirectory();
}
if ($default === null) {
$default = head_key($map);
}
// TODO: Use working storage?
$lease->setAttribute('workingcopy.default', "{$root}/repo/{$default}/");
$lease->activateOnResource($resource);
}
public function didReleaseLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
// We leave working copies around even if there are no leases on them,
// since the cost to maintain them is nearly zero but rebuilding them is
// moderately expensive and it's likely that they'll be reused.
return;
}
public function destroyLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
// When we activate a lease we just reset the working copy state and do
// not create any new state, so we don't need to do anything special when
// destroying a lease.
return;
}
public function getType() {
return 'working-copy';
}
public function getInterface(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease,
$type) {
switch ($type) {
case DrydockCommandInterface::INTERFACE_TYPE:
$host_lease = $this->loadHostLease($resource);
$command_interface = $host_lease->getInterface($type);
$path = $lease->getAttribute('workingcopy.default');
$command_interface->pushWorkingDirectory($path);
return $command_interface;
}
}
private function loadRepositories(array $phids) {
$viewer = $this->getViewer();
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->withPHIDs($phids)
->execute();
$repositories = mpull($repositories, null, 'getPHID');
foreach ($phids as $phid) {
if (empty($repositories[$phid])) {
throw new Exception(
pht(
'Repository PHID "%s" does not exist.',
$phid));
}
}
foreach ($repositories as $repository) {
$repository_vcs = $repository->getVersionControlSystem();
switch ($repository_vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
break;
default:
throw new Exception(
pht(
'Repository ("%s") has unsupported VCS ("%s").',
$repository->getPHID(),
$repository_vcs));
}
}
return $repositories;
}
private function loadHostLease(DrydockResource $resource) {
$viewer = $this->getViewer();
$lease_phid = $resource->getAttribute('host.leasePHID');
$lease = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withPHIDs(array($lease_phid))
->executeOne();
if (!$lease) {
throw new Exception(
pht(
'Unable to load lease ("%s").',
$lease_phid));
}
return $lease;
}
protected function getCustomFieldSpecifications() {
return array(
'blueprintPHIDs' => array(
'name' => pht('Use Blueprints'),
'type' => 'blueprints',
'required' => true,
),
);
}
protected function shouldUseConcurrentResourceLimit() {
return true;
}
private function applyMerge(
DrydockLease $lease,
DrydockCommandInterface $interface,
array $merge) {
$src_uri = $merge['src.uri'];
$src_ref = $merge['src.ref'];
try {
$interface->execx(
'git fetch --no-tags -- %s +%s:%s',
$src_uri,
$src_ref,
$src_ref);
} catch (CommandException $ex) {
$display_command = csprintf(
'git fetch %R +%R:%R',
$src_uri,
$src_ref,
$src_ref);
$error = DrydockCommandError::newFromCommandException($ex)
->setPhase(self::PHASE_MERGEFETCH)
->setDisplayCommand($display_command);
$lease->setAttribute('workingcopy.vcs.error', $error->toDictionary());
throw $ex;
}
// NOTE: This can never actually generate a commit because we pass
// "--squash", but git sometimes runs code to check that a username and
// email are configured anyway.
$real_command = csprintf(
'git -c user.name=%s -c user.email=%s merge --no-stat --squash -- %R',
'drydock',
'drydock@phabricator',
$src_ref);
try {
$interface->execx('%C', $real_command);
} catch (CommandException $ex) {
$display_command = csprintf(
'git merge --squash %R',
$src_ref);
$error = DrydockCommandError::newFromCommandException($ex)
->setPhase(self::PHASE_SQUASHMERGE)
->setDisplayCommand($display_command);
$lease->setAttribute('workingcopy.vcs.error', $error->toDictionary());
throw $ex;
}
}
public function getCommandError(DrydockLease $lease) {
return $lease->getAttribute('workingcopy.vcs.error');
}
private function execxv(
DrydockCommandInterface $interface,
array $commands,
array $arguments) {
return $this->newExecvFuture($interface, $commands, $arguments)->resolvex();
}
private function newExecvFuture(
DrydockCommandInterface $interface,
array $commands,
array $arguments) {
$commands = implode(' && ', $commands);
$argv = array_merge(array($commands), $arguments);
return call_user_func_array(array($interface, 'getExecFuture'), $argv);
}
}
diff --git a/src/applications/drydock/logtype/DrydockLeaseWaitingForActivationLogType.php b/src/applications/drydock/logtype/DrydockLeaseWaitingForActivationLogType.php
new file mode 100644
index 0000000000..7ab2ee7208
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockLeaseWaitingForActivationLogType.php
@@ -0,0 +1,23 @@
+<?php
+
+final class DrydockLeaseWaitingForActivationLogType extends DrydockLogType {
+
+ const LOGCONST = 'core.lease.waiting-for-activation';
+
+ public function getLogTypeName() {
+ return pht('Waiting For Activation');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return 'fa-clock-o yellow';
+ }
+
+ public function renderLog(array $data) {
+ $resource_phids = idx($data, 'resourcePHIDs', array());
+
+ return pht(
+ 'Waiting for activation of resources: %s.',
+ $this->renderHandleList($resource_phids));
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockLeaseWaitingForReclamationLogType.php b/src/applications/drydock/logtype/DrydockLeaseWaitingForReclamationLogType.php
new file mode 100644
index 0000000000..9f99ad720b
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockLeaseWaitingForReclamationLogType.php
@@ -0,0 +1,23 @@
+<?php
+
+final class DrydockLeaseWaitingForReclamationLogType extends DrydockLogType {
+
+ const LOGCONST = 'core.lease.waiting-for-reclamation';
+
+ public function getLogTypeName() {
+ return pht('Waiting For Reclamation');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return 'fa-clock-o yellow';
+ }
+
+ public function renderLog(array $data) {
+ $resource_phids = idx($data, 'resourcePHIDs', array());
+
+ return pht(
+ 'Waiting for reclamation of resources: %s.',
+ $this->renderHandleList($resource_phids));
+ }
+
+}
diff --git a/src/applications/drydock/management/DrydockManagementCommandWorkflow.php b/src/applications/drydock/management/DrydockManagementCommandWorkflow.php
index ae0bd711b2..1047553883 100644
--- a/src/applications/drydock/management/DrydockManagementCommandWorkflow.php
+++ b/src/applications/drydock/management/DrydockManagementCommandWorkflow.php
@@ -1,66 +1,65 @@
<?php
final class DrydockManagementCommandWorkflow
extends DrydockManagementWorkflow {
protected function didConstruct() {
$this
->setName('command')
->setSynopsis(pht('Run a command on a leased resource.'))
->setArguments(
array(
array(
'name' => 'lease',
'param' => 'id',
'help' => pht('Lease ID.'),
),
array(
'name' => 'argv',
'wildcard' => true,
'help' => pht('Command to execute.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$lease_id = $args->getArg('lease');
if (!$lease_id) {
throw new PhutilArgumentUsageException(
pht(
- 'Use %s to specify a lease.',
- '--lease'));
+ 'Use "--lease" to specify a lease.'));
}
$argv = $args->getArg('argv');
if (!$argv) {
throw new PhutilArgumentUsageException(
pht(
'Specify a command to run.'));
}
$lease = id(new DrydockLeaseQuery())
->setViewer($this->getViewer())
->withIDs(array($lease_id))
->executeOne();
if (!$lease) {
throw new Exception(
pht(
'Unable to load lease with ID "%s"!',
$lease_id));
}
// TODO: Check lease state, etc.
$interface = $lease->getInterface(DrydockCommandInterface::INTERFACE_TYPE);
list($stdout, $stderr) = call_user_func_array(
array($interface, 'execx'),
array('%Ls', $argv));
fwrite(STDOUT, $stdout);
fwrite(STDERR, $stderr);
return 0;
}
}
diff --git a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php
index 08f33c6b5f..af85f6bbec 100644
--- a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php
+++ b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php
@@ -1,221 +1,364 @@
<?php
final class DrydockManagementLeaseWorkflow
extends DrydockManagementWorkflow {
protected function didConstruct() {
$this
->setName('lease')
->setSynopsis(pht('Lease a resource.'))
->setArguments(
array(
array(
'name' => 'type',
'param' => 'resource_type',
'help' => pht('Resource type.'),
),
array(
'name' => 'until',
'param' => 'time',
'help' => pht('Set lease expiration time.'),
),
array(
'name' => 'attributes',
'param' => 'file',
'help' => pht(
'JSON file with lease attributes. Use "-" to read attributes '.
'from stdin.'),
),
+ array(
+ 'name' => 'count',
+ 'param' => 'N',
+ 'default' => 1,
+ 'help' => pht('Lease a given number of identical resources.'),
+ ),
+ array(
+ 'name' => 'blueprint',
+ 'param' => 'identifier',
+ 'repeat' => true,
+ 'help' => pht('Lease resources from a specific blueprint.'),
+ ),
));
}
public function execute(PhutilArgumentParser $args) {
$viewer = $this->getViewer();
$resource_type = $args->getArg('type');
- if (!$resource_type) {
+ if (!phutil_nonempty_string($resource_type)) {
throw new PhutilArgumentUsageException(
pht(
- 'Specify a resource type with `%s`.',
- '--type'));
+ 'Specify a resource type with "--type".'));
}
$until = $args->getArg('until');
- if (strlen($until)) {
+ if (phutil_nonempty_string($until)) {
$until = strtotime($until);
if ($until <= 0) {
throw new PhutilArgumentUsageException(
pht(
- 'Unable to parse argument to "%s".',
- '--until'));
+ 'Unable to parse argument to "--until".'));
}
}
+ $count = $args->getArgAsInteger('count');
+ if ($count < 1) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Value provided to "--count" must be a nonzero, positive '.
+ 'number.'));
+ }
+
$attributes_file = $args->getArg('attributes');
- if (strlen($attributes_file)) {
+ if (phutil_nonempty_string($attributes_file)) {
if ($attributes_file == '-') {
echo tsprintf(
"%s\n",
- 'Reading JSON attributes from stdin...');
+ pht('Reading JSON attributes from stdin...'));
$data = file_get_contents('php://stdin');
} else {
$data = Filesystem::readFile($attributes_file);
}
$attributes = phutil_json_decode($data);
} else {
$attributes = array();
}
- $lease = id(new DrydockLease())
- ->setResourceType($resource_type);
+ $filter_identifiers = $args->getArg('blueprint');
+ if ($filter_identifiers) {
+ $filter_blueprints = $this->getBlueprintFilterMap($filter_identifiers);
+ } else {
+ $filter_blueprints = array();
+ }
- $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
- $lease->setAuthorizingPHID($drydock_phid);
+ $blueprint_phids = null;
- if ($attributes) {
- $lease->setAttributes($attributes);
- }
+ $leases = array();
+ for ($idx = 0; $idx < $count; $idx++) {
+ $lease = id(new DrydockLease())
+ ->setResourceType($resource_type);
- // TODO: This is not hugely scalable, although this is a debugging workflow
- // so maybe it's fine. Do we even need `bin/drydock lease` in the long run?
- $all_blueprints = id(new DrydockBlueprintQuery())
- ->setViewer($viewer)
- ->execute();
- $allowed_phids = mpull($all_blueprints, 'getPHID');
- if (!$allowed_phids) {
- throw new Exception(
- pht(
- 'No blueprints exist which can plausibly allocate resources to '.
- 'satisfy the requested lease.'));
- }
- $lease->setAllowedBlueprintPHIDs($allowed_phids);
+ $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
+ $lease->setAuthorizingPHID($drydock_phid);
- if ($until) {
- $lease->setUntil($until);
- }
+ if ($attributes) {
+ $lease->setAttributes($attributes);
+ }
+
+ if ($blueprint_phids === null) {
+ $blueprint_phids = $this->newAllowedBlueprintPHIDs(
+ $lease,
+ $filter_blueprints);
+ }
- // If something fatals or the user interrupts the process (for example,
- // with "^C"), release the lease. We'll cancel this below, if the lease
- // actually activates.
- $lease->setReleaseOnDestruction(true);
+ $lease->setAllowedBlueprintPHIDs($blueprint_phids);
+
+ if ($until) {
+ $lease->setUntil($until);
+ }
+
+ // If something fatals or the user interrupts the process (for example,
+ // with "^C"), release the lease. We'll cancel this below, if the lease
+ // actually activates.
+ $lease->setReleaseOnDestruction(true);
+
+ $leases[] = $lease;
+ }
// TODO: This would probably be better handled with PhutilSignalRouter,
// but it currently doesn't route SIGINT. We're initializing it to setup
// SIGTERM handling and make eventual migration easier.
$router = PhutilSignalRouter::getRouter();
pcntl_signal(SIGINT, array($this, 'didReceiveInterrupt'));
$t_start = microtime(true);
- $lease->queueForActivation();
+
+
+ echo tsprintf(
+ "%s\n\n",
+ pht('Leases queued for activation:'));
+
+ foreach ($leases as $lease) {
+ $lease->queueForActivation();
+
+ echo tsprintf(
+ " __%s__\n",
+ PhabricatorEnv::getProductionURI($lease->getURI()));
+ }
echo tsprintf(
- "%s\n\n __%s__\n\n%s\n",
- pht('Queued lease for activation:'),
- PhabricatorEnv::getProductionURI($lease->getURI()),
- pht('Waiting for daemons to activate lease...'));
+ "\n%s\n\n",
+ pht('Waiting for daemons to activate leases...'));
- $this->waitUntilActive($lease);
+ foreach ($leases as $lease) {
+ $this->waitUntilActive($lease);
+ }
// Now that we've survived activation and the lease is good, make it
// durable.
- $lease->setReleaseOnDestruction(false);
+ foreach ($leases as $lease) {
+ $lease->setReleaseOnDestruction(false);
+ }
+
$t_end = microtime(true);
echo tsprintf(
- "%s\n\n %s\n\n%s\n",
+ "\n%s\n\n",
pht(
- 'Activation complete. This lease is permanent until manually '.
- 'released with:'),
- pht('$ ./bin/drydock release-lease --id %d', $lease->getID()),
+ 'Activation complete. Leases are permanent until manually '.
+ 'released with:'));
+
+ foreach ($leases as $lease) {
+ echo tsprintf(
+ " %s\n",
+ pht('$ ./bin/drydock release-lease --id %d', $lease->getID()));
+ }
+
+ echo tsprintf(
+ "\n%s\n",
pht(
- 'Lease activated in %sms.',
+ 'Leases activated in %sms.',
new PhutilNumber((int)(($t_end - $t_start) * 1000))));
return 0;
}
public function didReceiveInterrupt($signo) {
// Doing this makes us run destructors, particularly the "release on
// destruction" trigger on the lease.
exit(128 + $signo);
}
private function waitUntilActive(DrydockLease $lease) {
$viewer = $this->getViewer();
$log_cursor = 0;
$log_types = DrydockLogType::getAllLogTypes();
$is_active = false;
while (!$is_active) {
$lease->reload();
$pager = id(new AphrontCursorPagerView())
->setBeforeID($log_cursor);
// While we're waiting, show the user any logs which the daemons have
// generated to give them some clue about what's going on.
$logs = id(new DrydockLogQuery())
->setViewer($viewer)
->withLeasePHIDs(array($lease->getPHID()))
->executeWithCursorPager($pager);
if ($logs) {
$logs = mpull($logs, null, 'getID');
ksort($logs);
$log_cursor = last_key($logs);
}
foreach ($logs as $log) {
$type_key = $log->getType();
if (isset($log_types[$type_key])) {
$type_object = id(clone $log_types[$type_key])
->setLog($log)
->setViewer($viewer);
$log_data = $log->getData();
$type = $type_object->getLogTypeName();
$data = $type_object->renderLogForText($log_data);
} else {
$type = pht('Unknown ("%s")', $type_key);
$data = null;
}
echo tsprintf(
- "<%s> %B\n",
+ "(Lease #%d) <%s> %B\n",
+ $lease->getID(),
$type,
$data);
}
$status = $lease->getStatus();
switch ($status) {
case DrydockLeaseStatus::STATUS_ACTIVE:
$is_active = true;
break;
case DrydockLeaseStatus::STATUS_RELEASED:
throw new Exception(pht('Lease has already been released!'));
case DrydockLeaseStatus::STATUS_DESTROYED:
throw new Exception(pht('Lease has already been destroyed!'));
case DrydockLeaseStatus::STATUS_BROKEN:
throw new Exception(pht('Lease has been broken!'));
case DrydockLeaseStatus::STATUS_PENDING:
case DrydockLeaseStatus::STATUS_ACQUIRED:
break;
default:
throw new Exception(
pht(
'Lease has unknown status "%s".',
$status));
}
if ($is_active) {
break;
} else {
sleep(1);
}
}
}
+ private function getBlueprintFilterMap(array $identifiers) {
+ $viewer = $this->getViewer();
+
+ $query = id(new DrydockBlueprintQuery())
+ ->setViewer($viewer)
+ ->withIdentifiers($identifiers);
+
+ $blueprints = $query->execute();
+ $blueprints = mpull($blueprints, null, 'getPHID');
+
+ $map = $query->getIdentifierMap();
+
+ $seen = array();
+ foreach ($identifiers as $identifier) {
+ if (!isset($map[$identifier])) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Blueprint "%s" could not be loaded. Try a blueprint ID or '.
+ 'PHID.',
+ $identifier));
+ }
+
+ $blueprint = $map[$identifier];
+
+ $blueprint_phid = $blueprint->getPHID();
+ if (isset($seen[$blueprint_phid])) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Blueprint "%s" is specified more than once (as "%s" and "%s").',
+ $blueprint->getBlueprintName(),
+ $seen[$blueprint_phid],
+ $identifier));
+ }
+
+ $seen[$blueprint_phid] = true;
+ }
+
+ return mpull($map, null, 'getPHID');
+ }
+
+ private function newAllowedBlueprintPHIDs(
+ DrydockLease $lease,
+ array $filter_blueprints) {
+ assert_instances_of($filter_blueprints, 'DrydockBlueprint');
+
+ $viewer = $this->getViewer();
+
+ $impls = DrydockBlueprintImplementation::getAllForAllocatingLease($lease);
+
+ if (!$impls) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'No known blueprint class can ever allocate the specified '.
+ 'lease. Check that the resource type is spelled correctly.'));
+ }
+
+ $classes = array_keys($impls);
+
+ $blueprints = id(new DrydockBlueprintQuery())
+ ->setViewer($viewer)
+ ->withBlueprintClasses($classes)
+ ->withDisabled(false)
+ ->execute();
+
+ if (!$blueprints) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'No enabled blueprints exist with a blueprint class that can '.
+ 'plausibly allocate resources to satisfy the requested lease.'));
+ }
+
+ $phids = mpull($blueprints, 'getPHID');
+
+ if ($filter_blueprints) {
+ $allowed_map = array_fuse($phids);
+ $filter_map = mpull($filter_blueprints, null, 'getPHID');
+
+ foreach ($filter_map as $filter_phid => $blueprint) {
+ if (!isset($allowed_map[$filter_phid])) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Specified blueprint "%s" is not capable of satisfying the '.
+ 'configured lease.',
+ $blueprint->getBlueprintName()));
+ }
+ }
+
+ $phids = mpull($filter_blueprints, 'getPHID');
+ }
+
+ return $phids;
+ }
+
}
diff --git a/src/applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php b/src/applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php
index 20af18ec21..0628f57b38 100644
--- a/src/applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php
+++ b/src/applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php
@@ -1,70 +1,118 @@
<?php
final class DrydockManagementReleaseLeaseWorkflow
extends DrydockManagementWorkflow {
protected function didConstruct() {
$this
->setName('release-lease')
->setSynopsis(pht('Release a lease.'))
->setArguments(
array(
array(
'name' => 'id',
'param' => 'id',
'repeat' => true,
'help' => pht('Lease ID to release.'),
),
+ array(
+ 'name' => 'all',
+ 'help' => pht('Release all leases. Dangerous!'),
+ ),
));
}
public function execute(PhutilArgumentParser $args) {
+ $is_all = $args->getArg('all');
$ids = $args->getArg('id');
- if (!$ids) {
+
+ if (!$ids && !$is_all) {
throw new PhutilArgumentUsageException(
pht(
- 'Specify one or more lease IDs to release with "%s".',
- '--id'));
+ 'Select which leases you want to release. See "--help" for '.
+ 'guidance.'));
}
$viewer = $this->getViewer();
- $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
- $leases = id(new DrydockLeaseQuery())
+ $statuses = $this->getReleaseableLeaseStatuses();
+
+ $query = id(new DrydockLeaseQuery())
->setViewer($viewer)
- ->withIDs($ids)
- ->execute();
+ ->withStatuses(mpull($statuses, 'getKey'));
- PhabricatorWorker::setRunAllTasksInProcess(true);
- foreach ($ids as $id) {
- $lease = idx($leases, $id);
- if (!$lease) {
- echo tsprintf(
- "%s\n",
- pht('Lease "%s" does not exist.', $id));
- continue;
+ if ($ids) {
+ $query->withIDs($ids);
+ }
+
+ $leases = $query->execute();
+
+ if ($ids) {
+ $id_map = mpull($leases, null, 'getID');
+
+ foreach ($ids as $id) {
+ $lease = idx($id_map, $id);
+ if (!$lease) {
+ throw new PhutilArgumentUsageException(
+ pht('Lease "%s" does not exist.', $id));
+ }
}
+ $leases = array_select_keys($id_map, $ids);
+ }
+
+ if (!$leases) {
+ echo tsprintf(
+ "%s\n",
+ pht('No leases selected for release.'));
+
+ return 0;
+ }
+
+ $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
+
+ PhabricatorWorker::setRunAllTasksInProcess(true);
+
+ foreach ($leases as $lease) {
if (!$lease->canRelease()) {
echo tsprintf(
"%s\n",
- pht('Lease "%s" is not releasable.', $id));
+ pht(
+ 'Lease "%s" is not releasable.',
+ $lease->getDisplayName()));
continue;
}
$command = DrydockCommand::initializeNewCommand($viewer)
->setTargetPHID($lease->getPHID())
->setAuthorPHID($drydock_phid)
->setCommand(DrydockCommand::COMMAND_RELEASE)
->save();
$lease->scheduleUpdate();
echo tsprintf(
"%s\n",
- pht('Scheduled release of lease "%s".', $id));
+ pht(
+ 'Scheduled release of lease "%s".',
+ $lease->getDisplayName()));
+ }
+
+ }
+
+ private function getReleaseableLeaseStatuses() {
+ $statuses = DrydockLeaseStatus::getAllStatuses();
+ foreach ($statuses as $key => $status) {
+ $statuses[$key] = DrydockLeaseStatus::newStatusObject($status);
+ }
+
+ foreach ($statuses as $key => $status) {
+ if (!$status->canRelease()) {
+ unset($statuses[$key]);
+ }
}
+ return $statuses;
}
}
diff --git a/src/applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php b/src/applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php
index 01060a5325..afd826cbc0 100644
--- a/src/applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php
+++ b/src/applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php
@@ -1,71 +1,117 @@
<?php
final class DrydockManagementReleaseResourceWorkflow
extends DrydockManagementWorkflow {
protected function didConstruct() {
$this
->setName('release-resource')
->setSynopsis(pht('Release a resource.'))
->setArguments(
array(
array(
'name' => 'id',
'param' => 'id',
'repeat' => true,
'help' => pht('Resource ID to release.'),
),
+ array(
+ 'name' => 'all',
+ 'help' => pht('Release all resources. Dangerous!'),
+ ),
));
}
public function execute(PhutilArgumentParser $args) {
+ $is_all = $args->getArg('all');
$ids = $args->getArg('id');
- if (!$ids) {
+ if (!$ids && !$is_all) {
throw new PhutilArgumentUsageException(
pht(
- 'Specify one or more resource IDs to release with "%s".',
- '--id'));
+ 'Specify which resources you want to release. See "--help" for '.
+ 'guidance.'));
}
$viewer = $this->getViewer();
- $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
+ $statuses = $this->getReleaseableResourceStatuses();
- $resources = id(new DrydockResourceQuery())
+ $query = id(new DrydockResourceQuery())
->setViewer($viewer)
- ->withIDs($ids)
- ->execute();
+ ->withStatuses(mpull($statuses, 'getKey'));
- PhabricatorWorker::setRunAllTasksInProcess(true);
- foreach ($ids as $id) {
- $resource = idx($resources, $id);
+ if ($ids) {
+ $query->withIDs($ids);
+ }
- if (!$resource) {
- echo tsprintf(
- "%s\n",
- pht('Resource "%s" does not exist.', $id));
- continue;
+ $resources = $query->execute();
+
+ if ($ids) {
+ $id_map = mpull($resources, null, 'getID');
+
+ foreach ($ids as $id) {
+ $resource = idx($resources, $id);
+
+ if (!$resource) {
+ throw new PhutilArgumentUsageException(
+ pht('Resource "%s" does not exist.', $id));
+ }
}
+ $resources = array_select_keys($id_map, $ids);
+ }
+
+ if (!$resources) {
+ echo tsprintf(
+ "%s\n",
+ pht('No resources selected for release.'));
+
+ return 0;
+ }
+
+ $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
+
+ PhabricatorWorker::setRunAllTasksInProcess(true);
+
+ foreach ($resources as $resource) {
if (!$resource->canRelease()) {
echo tsprintf(
"%s\n",
- pht('Resource "%s" is not releasable.', $id));
+ pht(
+ 'Resource "%s" is not releasable.',
+ $resource->getDisplayName()));
continue;
}
$command = DrydockCommand::initializeNewCommand($viewer)
->setTargetPHID($resource->getPHID())
->setAuthorPHID($drydock_phid)
->setCommand(DrydockCommand::COMMAND_RELEASE)
->save();
$resource->scheduleUpdate();
echo tsprintf(
"%s\n",
- pht('Scheduled release of resource "%s".', $id));
+ pht(
+ 'Scheduled release of resource "%s".',
+ $resource->getDisplayName()));
}
+ return 0;
}
+ private function getReleaseableResourceStatuses() {
+ $statuses = DrydockResourceStatus::getAllStatuses();
+ foreach ($statuses as $key => $status) {
+ $statuses[$key] = DrydockResourceStatus::newStatusObject($status);
+ }
+
+ foreach ($statuses as $key => $status) {
+ if (!$status->canRelease()) {
+ unset($statuses[$key]);
+ }
+ }
+
+ return $statuses;
+ }
}
diff --git a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php
index acb48f6f0b..f44d6a1db2 100644
--- a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php
+++ b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php
@@ -1,442 +1,443 @@
<?php
final class DrydockLandRepositoryOperation
extends DrydockRepositoryOperationType {
const OPCONST = 'land';
const PHASE_PUSH = 'op.land.push';
const PHASE_COMMIT = 'op.land.commit';
public function getOperationDescription(
DrydockRepositoryOperation $operation,
PhabricatorUser $viewer) {
return pht('Land Revision');
}
public function getOperationCurrentStatus(
DrydockRepositoryOperation $operation,
PhabricatorUser $viewer) {
$target = $operation->getRepositoryTarget();
$repository = $operation->getRepository();
switch ($operation->getOperationState()) {
case DrydockRepositoryOperation::STATE_WAIT:
return pht(
'Waiting to land revision into %s on %s...',
$repository->getMonogram(),
$target);
case DrydockRepositoryOperation::STATE_WORK:
return pht(
'Landing revision into %s on %s...',
$repository->getMonogram(),
$target);
case DrydockRepositoryOperation::STATE_DONE:
return pht(
'Revision landed into %s.',
$repository->getMonogram());
}
}
public function getWorkingCopyMerges(DrydockRepositoryOperation $operation) {
$repository = $operation->getRepository();
$merges = array();
$object = $operation->getObject();
if ($object instanceof DifferentialRevision) {
$diff = $this->loadDiff($operation);
$merges[] = array(
'src.uri' => $repository->getStagingURI(),
'src.ref' => $diff->getStagingRef(),
);
} else {
throw new Exception(
pht(
'Invalid or unknown object ("%s") for land operation, expected '.
'Differential Revision.',
$operation->getObjectPHID()));
}
return $merges;
}
public function applyOperation(
DrydockRepositoryOperation $operation,
DrydockInterface $interface) {
$viewer = $this->getViewer();
$repository = $operation->getRepository();
$cmd = array();
$arg = array();
$object = $operation->getObject();
if ($object instanceof DifferentialRevision) {
$revision = $object;
$diff = $this->loadDiff($operation);
$dict = $diff->getDiffAuthorshipDict();
$author_name = idx($dict, 'authorName');
$author_email = idx($dict, 'authorEmail');
$api_method = 'differential.getcommitmessage';
$api_params = array(
'revision_id' => $revision->getID(),
);
$commit_message = id(new ConduitCall($api_method, $api_params))
->setUser($viewer)
->execute();
} else {
throw new Exception(
pht(
'Invalid or unknown object ("%s") for land operation, expected '.
'Differential Revision.',
$operation->getObjectPHID()));
}
$target = $operation->getRepositoryTarget();
list($type, $name) = explode(':', $target, 2);
switch ($type) {
case 'branch':
$push_dst = 'refs/heads/'.$name;
break;
default:
throw new Exception(
pht(
'Unknown repository operation target type "%s" (in target "%s").',
$type,
$target));
}
$committer_info = $this->getCommitterInfo($operation);
// NOTE: We're doing this commit with "-F -" so we don't run into trouble
// with enormous commit messages which might otherwise exceed the maximum
// size of a command.
$future = $interface->getExecFuture(
'git -c user.name=%s -c user.email=%s commit --author %s -F - --',
$committer_info['name'],
$committer_info['email'],
"{$author_name} <{$author_email}>");
$future->write($commit_message);
try {
$future->resolvex();
} catch (CommandException $ex) {
$display_command = csprintf('git commit');
// TODO: One reason this can fail is if the changes have already been
// merged. We could try to detect that.
$error = DrydockCommandError::newFromCommandException($ex)
->setPhase(self::PHASE_COMMIT)
->setDisplayCommand($display_command);
$operation->setCommandError($error->toDictionary());
throw $ex;
}
try {
$interface->execx(
'git push origin -- %s:%s',
'HEAD',
$push_dst);
} catch (CommandException $ex) {
$display_command = csprintf(
'git push origin %R:%R',
'HEAD',
$push_dst);
$error = DrydockCommandError::newFromCommandException($ex)
->setPhase(self::PHASE_PUSH)
->setDisplayCommand($display_command);
$operation->setCommandError($error->toDictionary());
throw $ex;
}
}
private function getCommitterInfo(DrydockRepositoryOperation $operation) {
$viewer = $this->getViewer();
$committer_name = null;
$author_phid = $operation->getAuthorPHID();
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(array($author_phid))
->executeOne();
if ($object) {
if ($object instanceof PhabricatorUser) {
$committer_name = $object->getUsername();
}
}
if (!strlen($committer_name)) {
$committer_name = pht('autocommitter');
}
// TODO: Probably let users choose a VCS email address in settings. For
// now just make something up so we don't leak anyone's stuff.
return array(
'name' => $committer_name,
'email' => 'autocommitter@example.com',
);
}
private function loadDiff(DrydockRepositoryOperation $operation) {
$viewer = $this->getViewer();
$revision = $operation->getObject();
$diff_phid = $operation->getProperty('differential.diffPHID');
$diff = id(new DifferentialDiffQuery())
->setViewer($viewer)
->withPHIDs(array($diff_phid))
->executeOne();
if (!$diff) {
throw new Exception(
pht(
'Unable to load diff "%s".',
$diff_phid));
}
$diff_revid = $diff->getRevisionID();
$revision_id = $revision->getID();
if ($diff_revid != $revision_id) {
throw new Exception(
pht(
'Diff ("%s") has wrong revision ID ("%s", expected "%s").',
$diff_phid,
$diff_revid,
$revision_id));
}
return $diff;
}
public function getBarrierToLanding(
PhabricatorUser $viewer,
DifferentialRevision $revision) {
$repository = $revision->getRepository();
if (!$repository) {
return array(
'title' => pht('No Repository'),
'body' => pht(
'This revision is not associated with a known repository. Only '.
'revisions associated with a tracked repository can be landed '.
'automatically.'),
);
}
if (!$repository->canPerformAutomation()) {
return array(
'title' => pht('No Repository Automation'),
'body' => pht(
'The repository this revision is associated with ("%s") is not '.
'configured to support automation. Configure automation for the '.
'repository to enable revisions to be landed automatically.',
$repository->getMonogram()),
);
}
// Check if this diff was pushed to a staging area.
$diff = id(new DifferentialDiffQuery())
->setViewer($viewer)
->withIDs(array($revision->getActiveDiff()->getID()))
->needProperties(true)
->executeOne();
// Older diffs won't have this property. They may still have been pushed.
// At least for now, assume staging changes are present if the property
// is missing. This should smooth the transition to the more formal
// approach.
$has_staging = $diff->hasDiffProperty('arc.staging');
if ($has_staging) {
$staging = $diff->getProperty('arc.staging');
if (!is_array($staging)) {
$staging = array();
}
$status = idx($staging, 'status');
if ($status != ArcanistDiffWorkflow::STAGING_PUSHED) {
return $this->getBarrierToLandingFromStagingStatus($status);
}
}
// TODO: At some point we should allow installs to give "land reviewed
// code" permission to more users than "push any commit", because it is
// a much less powerful operation. For now, just require push so this
// doesn't do anything users can't do on their own.
$can_push = PhabricatorPolicyFilter::hasCapability(
$viewer,
$repository,
DiffusionPushCapability::CAPABILITY);
if (!$can_push) {
return array(
'title' => pht('Unable to Push'),
'body' => pht(
'You do not have permission to push to the repository this '.
'revision is associated with ("%s"), so you can not land it.',
$repository->getMonogram()),
);
}
if ($revision->isAccepted()) {
// We can land accepted revisions, so continue below. Otherwise, raise
// an error with tailored messaging for the most common cases.
} else if ($revision->isAbandoned()) {
return array(
'title' => pht('Revision Abandoned'),
'body' => pht(
'This revision has been abandoned. Only accepted revisions '.
'may land.'),
);
} else if ($revision->isClosed()) {
return array(
'title' => pht('Revision Closed'),
'body' => pht(
'This revision has already been closed. Only open, accepted '.
'revisions may land.'),
);
} else {
return array(
'title' => pht('Revision Not Accepted'),
'body' => pht(
'This revision is still under review. Only revisions which '.
'have been accepted may land.'),
);
}
// Check for other operations. Eventually this should probably be more
// general (e.g., it's OK to land to multiple different branches
// simultaneously) but just put this in as a sanity check for now.
$other_operations = id(new DrydockRepositoryOperationQuery())
->setViewer($viewer)
->withObjectPHIDs(array($revision->getPHID()))
->withOperationTypes(
array(
$this->getOperationConstant(),
))
->withOperationStates(
array(
DrydockRepositoryOperation::STATE_WAIT,
DrydockRepositoryOperation::STATE_WORK,
DrydockRepositoryOperation::STATE_DONE,
))
->execute();
if ($other_operations) {
$any_done = false;
foreach ($other_operations as $operation) {
if ($operation->isDone()) {
$any_done = true;
break;
}
}
if ($any_done) {
return array(
'title' => pht('Already Complete'),
'body' => pht('This revision has already landed.'),
);
} else {
return array(
'title' => pht('Already In Flight'),
'body' => pht('This revision is already landing.'),
);
}
}
return null;
}
private function getBarrierToLandingFromStagingStatus($status) {
switch ($status) {
case ArcanistDiffWorkflow::STAGING_USER_SKIP:
return array(
'title' => pht('Staging Area Skipped'),
'body' => pht(
'The diff author used the %s flag to skip pushing this change to '.
'staging. Changes must be pushed to staging before they can be '.
'landed from the web.',
phutil_tag('tt', array(), '--skip-staging')),
);
case ArcanistDiffWorkflow::STAGING_DIFF_RAW:
return array(
'title' => pht('Raw Diff Source'),
'body' => pht(
'The diff was generated from a raw input source, so the change '.
'could not be pushed to staging. Changes must be pushed to '.
'staging before they can be landed from the web.'),
);
case ArcanistDiffWorkflow::STAGING_REPOSITORY_UNKNOWN:
return array(
'title' => pht('Unknown Repository'),
'body' => pht(
'When the diff was generated, the client was not able to '.
'determine which repository it belonged to, so the change '.
'was not pushed to staging. Changes must be pushed to staging '.
'before they can be landed from the web.'),
);
case ArcanistDiffWorkflow::STAGING_REPOSITORY_UNAVAILABLE:
return array(
'title' => pht('Staging Unavailable'),
'body' => pht(
'When this diff was generated, the server was running an older '.
- 'version of Phabricator which did not support staging areas, so '.
+ 'version of the software which did not support staging areas, so '.
'the change was not pushed to staging. Changes must be pushed '.
'to staging before they can be landed from the web.'),
);
case ArcanistDiffWorkflow::STAGING_REPOSITORY_UNSUPPORTED:
return array(
'title' => pht('Repository Unsupported'),
'body' => pht(
'When this diff was generated, the server was running an older '.
- 'version of Phabricator which did not support staging areas for '.
+ 'version of the software which did not support staging areas for '.
'this version control system, so the change was not pushed to '.
'staging. Changes must be pushed to staging before they can be '.
'landed from the web.'),
);
case ArcanistDiffWorkflow::STAGING_REPOSITORY_UNCONFIGURED:
return array(
'title' => pht('Repository Unconfigured'),
'body' => pht(
'When this diff was generated, the repository was not configured '.
'with a staging area, so the change was not pushed to staging. '.
'Changes must be pushed to staging before they can be landed '.
'from the web.'),
);
case ArcanistDiffWorkflow::STAGING_CLIENT_UNSUPPORTED:
return array(
'title' => pht('Client Support Unavailable'),
'body' => pht(
'When this diff was generated, the client did not support '.
'staging areas for this version control system, so the change '.
'was not pushed to staging. Changes must be pushed to staging '.
'before they can be landed from the web. Updating the client '.
'may resolve this issue.'),
);
default:
return array(
'title' => pht('Unknown Error'),
'body' => pht(
'When this diff was generated, it was not pushed to staging for '.
'an unknown reason (the status code was "%s"). Changes must be '.
'pushed to staging before they can be landed from the web. '.
- 'The server may be running an out-of-date version of Phabricator, '.
- 'and updating may provide more information about this error.',
+ 'The server may be running an out-of-date version of this '.
+ 'software, and updating may provide more information about this '.
+ 'error.',
$status),
);
}
}
}
diff --git a/src/applications/drydock/query/DrydockAuthorizationQuery.php b/src/applications/drydock/query/DrydockAuthorizationQuery.php
index d6436fc93a..8c5c58c853 100644
--- a/src/applications/drydock/query/DrydockAuthorizationQuery.php
+++ b/src/applications/drydock/query/DrydockAuthorizationQuery.php
@@ -1,175 +1,171 @@
<?php
final class DrydockAuthorizationQuery extends DrydockQuery {
private $ids;
private $phids;
private $blueprintPHIDs;
private $objectPHIDs;
private $blueprintStates;
private $objectStates;
public static function isFullyAuthorized(
$object_phid,
array $blueprint_phids) {
if (!$blueprint_phids) {
return true;
}
$authorizations = id(new self())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withObjectPHIDs(array($object_phid))
->withBlueprintPHIDs($blueprint_phids)
->execute();
$authorizations = mpull($authorizations, null, 'getBlueprintPHID');
foreach ($blueprint_phids as $phid) {
$authorization = idx($authorizations, $phid);
if (!$authorization) {
return false;
}
if (!$authorization->isAuthorized()) {
return false;
}
}
return true;
}
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withBlueprintPHIDs(array $phids) {
$this->blueprintPHIDs = $phids;
return $this;
}
public function withObjectPHIDs(array $phids) {
$this->objectPHIDs = $phids;
return $this;
}
public function withBlueprintStates(array $states) {
$this->blueprintStates = $states;
return $this;
}
public function withObjectStates(array $states) {
$this->objectStates = $states;
return $this;
}
public function newResultObject() {
return new DrydockAuthorization();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $authorizations) {
$blueprint_phids = mpull($authorizations, 'getBlueprintPHID');
if ($blueprint_phids) {
$blueprints = id(new DrydockBlueprintQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($blueprint_phids)
->execute();
$blueprints = mpull($blueprints, null, 'getPHID');
} else {
$blueprints = array();
}
foreach ($authorizations as $key => $authorization) {
$blueprint = idx($blueprints, $authorization->getBlueprintPHID());
if (!$blueprint) {
$this->didRejectResult($authorization);
unset($authorizations[$key]);
continue;
}
$authorization->attachBlueprint($blueprint);
}
$object_phids = mpull($authorizations, 'getObjectPHID');
if ($object_phids) {
$objects = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($object_phids)
->execute();
$objects = mpull($objects, null, 'getPHID');
} else {
$objects = array();
}
foreach ($authorizations as $key => $authorization) {
$object = idx($objects, $authorization->getObjectPHID());
if (!$object) {
$this->didRejectResult($authorization);
unset($authorizations[$key]);
continue;
}
$authorization->attachObject($object);
}
return $authorizations;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->blueprintPHIDs !== null) {
$where[] = qsprintf(
$conn,
'blueprintPHID IN (%Ls)',
$this->blueprintPHIDs);
}
if ($this->objectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'objectPHID IN (%Ls)',
$this->objectPHIDs);
}
if ($this->blueprintStates !== null) {
$where[] = qsprintf(
$conn,
'blueprintAuthorizationState IN (%Ls)',
$this->blueprintStates);
}
if ($this->objectStates !== null) {
$where[] = qsprintf(
$conn,
'objectAuthorizationState IN (%Ls)',
$this->objectStates);
}
return $where;
}
}
diff --git a/src/applications/drydock/query/DrydockBlueprintQuery.php b/src/applications/drydock/query/DrydockBlueprintQuery.php
index 6c92927bb8..c163103d16 100644
--- a/src/applications/drydock/query/DrydockBlueprintQuery.php
+++ b/src/applications/drydock/query/DrydockBlueprintQuery.php
@@ -1,144 +1,237 @@
<?php
final class DrydockBlueprintQuery extends DrydockQuery {
private $ids;
private $phids;
private $blueprintClasses;
private $datasourceQuery;
private $disabled;
private $authorizedPHIDs;
+ private $identifiers;
+ private $identifierIDs;
+ private $identifierPHIDs;
+ private $identifierMap;
+
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withBlueprintClasses(array $classes) {
$this->blueprintClasses = $classes;
return $this;
}
public function withDatasourceQuery($query) {
$this->datasourceQuery = $query;
return $this;
}
public function withDisabled($disabled) {
$this->disabled = $disabled;
return $this;
}
public function withAuthorizedPHIDs(array $phids) {
$this->authorizedPHIDs = $phids;
return $this;
}
public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint(
new DrydockBlueprintNameNgrams(),
$ngrams);
}
+ public function withIdentifiers(array $identifiers) {
+ if (!$identifiers) {
+ throw new Exception(
+ pht(
+ 'Can not issue a query with an empty identifier list.'));
+ }
+
+ $this->identifiers = $identifiers;
+
+ $ids = array();
+ $phids = array();
+
+ foreach ($identifiers as $identifier) {
+ if (ctype_digit($identifier)) {
+ $ids[] = $identifier;
+ } else {
+ $phids[] = $identifier;
+ }
+ }
+
+ $this->identifierIDs = $ids;
+ $this->identifierPHIDs = $phids;
+
+ return $this;
+ }
+
+ public function getIdentifierMap() {
+ if ($this->identifierMap === null) {
+ throw new Exception(
+ pht(
+ 'Execute a query with identifiers before getting the '.
+ 'identifier map.'));
+ }
+
+ return $this->identifierMap;
+ }
+
public function newResultObject() {
return new DrydockBlueprint();
}
protected function getPrimaryTableAlias() {
return 'blueprint';
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
+ protected function willExecute() {
+ if ($this->identifiers) {
+ $this->identifierMap = array();
+ } else {
+ $this->identifierMap = null;
+ }
}
protected function willFilterPage(array $blueprints) {
$impls = DrydockBlueprintImplementation::getAllBlueprintImplementations();
foreach ($blueprints as $key => $blueprint) {
$impl = idx($impls, $blueprint->getClassName());
if (!$impl) {
$this->didRejectResult($blueprint);
unset($blueprints[$key]);
continue;
}
$impl = clone $impl;
$blueprint->attachImplementation($impl);
}
+ if ($this->identifiers) {
+ $id_map = mpull($blueprints, null, 'getID');
+ $phid_map = mpull($blueprints, null, 'getPHID');
+
+ $map = $this->identifierMap;
+
+ foreach ($this->identifierIDs as $id) {
+ if (isset($id_map[$id])) {
+ $map[$id] = $id_map[$id];
+ }
+ }
+
+ foreach ($this->identifierPHIDs as $phid) {
+ if (isset($phid_map[$phid])) {
+ $map[$phid] = $phid_map[$phid];
+ }
+ }
+
+ // Just for consistency, reorder the map to match input order.
+ $map = array_select_keys($map, $this->identifiers);
+
+ $this->identifierMap = $map;
+ }
+
return $blueprints;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'blueprint.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'blueprint.phid IN (%Ls)',
$this->phids);
}
if ($this->datasourceQuery !== null) {
$where[] = qsprintf(
$conn,
'blueprint.blueprintName LIKE %>',
$this->datasourceQuery);
}
if ($this->blueprintClasses !== null) {
$where[] = qsprintf(
$conn,
'blueprint.className IN (%Ls)',
$this->blueprintClasses);
}
if ($this->disabled !== null) {
$where[] = qsprintf(
$conn,
'blueprint.isDisabled = %d',
(int)$this->disabled);
}
+ if ($this->identifiers !== null) {
+ $parts = array();
+
+ if ($this->identifierIDs) {
+ $parts[] = qsprintf(
+ $conn,
+ 'blueprint.id IN (%Ld)',
+ $this->identifierIDs);
+ }
+
+ if ($this->identifierPHIDs) {
+ $parts[] = qsprintf(
+ $conn,
+ 'blueprint.phid IN (%Ls)',
+ $this->identifierPHIDs);
+ }
+
+ $where[] = qsprintf(
+ $conn,
+ '%LO',
+ $parts);
+ }
+
return $where;
}
protected function shouldGroupQueryResultRows() {
if ($this->authorizedPHIDs !== null) {
return true;
}
return parent::shouldGroupQueryResultRows();
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->authorizedPHIDs !== null) {
$joins[] = qsprintf(
$conn,
'JOIN %T authorization
ON authorization.blueprintPHID = blueprint.phid
AND authorization.objectPHID IN (%Ls)
AND authorization.objectAuthorizationState = %s
AND authorization.blueprintAuthorizationState = %s',
id(new DrydockAuthorization())->getTableName(),
$this->authorizedPHIDs,
DrydockAuthorization::OBJECTAUTH_ACTIVE,
DrydockAuthorization::BLUEPRINTAUTH_AUTHORIZED);
}
return $joins;
}
}
diff --git a/src/applications/drydock/query/DrydockCommandQuery.php b/src/applications/drydock/query/DrydockCommandQuery.php
index 0d71288a85..fef6170ec9 100644
--- a/src/applications/drydock/query/DrydockCommandQuery.php
+++ b/src/applications/drydock/query/DrydockCommandQuery.php
@@ -1,82 +1,78 @@
<?php
final class DrydockCommandQuery extends DrydockQuery {
private $ids;
private $targetPHIDs;
private $consumed;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withTargetPHIDs(array $phids) {
$this->targetPHIDs = $phids;
return $this;
}
public function withConsumed($consumed) {
$this->consumed = $consumed;
return $this;
}
public function newResultObject() {
return new DrydockCommand();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $commands) {
$target_phids = mpull($commands, 'getTargetPHID');
$targets = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($target_phids)
->execute();
$targets = mpull($targets, null, 'getPHID');
foreach ($commands as $key => $command) {
$target = idx($targets, $command->getTargetPHID());
if (!$target) {
$this->didRejectResult($command);
unset($commands[$key]);
continue;
}
$command->attachCommandTarget($target);
}
return $commands;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->targetPHIDs !== null) {
$where[] = qsprintf(
$conn,
'targetPHID IN (%Ls)',
$this->targetPHIDs);
}
if ($this->consumed !== null) {
$where[] = qsprintf(
$conn,
'isConsumed = %d',
(int)$this->consumed);
}
return $where;
}
}
diff --git a/src/applications/drydock/query/DrydockLeaseQuery.php b/src/applications/drydock/query/DrydockLeaseQuery.php
index e524c1f9c5..4950764486 100644
--- a/src/applications/drydock/query/DrydockLeaseQuery.php
+++ b/src/applications/drydock/query/DrydockLeaseQuery.php
@@ -1,152 +1,170 @@
<?php
final class DrydockLeaseQuery extends DrydockQuery {
private $ids;
private $phids;
private $resourcePHIDs;
private $ownerPHIDs;
private $statuses;
private $datasourceQuery;
private $needUnconsumedCommands;
+ private $minModified;
+ private $maxModified;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withResourcePHIDs(array $phids) {
$this->resourcePHIDs = $phids;
return $this;
}
public function withOwnerPHIDs(array $phids) {
$this->ownerPHIDs = $phids;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function withDatasourceQuery($query) {
$this->datasourceQuery = $query;
return $this;
}
+ public function withDateModifiedBetween($min_epoch, $max_epoch) {
+ $this->minModified = $min_epoch;
+ $this->maxModified = $max_epoch;
+ return $this;
+ }
+
public function needUnconsumedCommands($need) {
$this->needUnconsumedCommands = $need;
return $this;
}
public function newResultObject() {
return new DrydockLease();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $leases) {
$resource_phids = array_filter(mpull($leases, 'getResourcePHID'));
if ($resource_phids) {
$resources = id(new DrydockResourceQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs(array_unique($resource_phids))
->execute();
$resources = mpull($resources, null, 'getPHID');
} else {
$resources = array();
}
foreach ($leases as $key => $lease) {
$resource = null;
if ($lease->getResourcePHID()) {
$resource = idx($resources, $lease->getResourcePHID());
if (!$resource) {
$this->didRejectResult($lease);
unset($leases[$key]);
continue;
}
}
$lease->attachResource($resource);
}
return $leases;
}
protected function didFilterPage(array $leases) {
if ($this->needUnconsumedCommands) {
$commands = id(new DrydockCommandQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withTargetPHIDs(mpull($leases, 'getPHID'))
->withConsumed(false)
->execute();
$commands = mgroup($commands, 'getTargetPHID');
foreach ($leases as $lease) {
$list = idx($commands, $lease->getPHID(), array());
$lease->attachUnconsumedCommands($list);
}
}
return $leases;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->resourcePHIDs !== null) {
$where[] = qsprintf(
$conn,
'resourcePHID IN (%Ls)',
$this->resourcePHIDs);
}
if ($this->ownerPHIDs !== null) {
$where[] = qsprintf(
$conn,
'ownerPHID IN (%Ls)',
$this->ownerPHIDs);
}
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'status IN (%Ls)',
$this->statuses);
}
if ($this->datasourceQuery !== null) {
$where[] = qsprintf(
$conn,
'id = %d',
(int)$this->datasourceQuery);
}
+ if ($this->minModified !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'dateModified >= %d',
+ $this->minModified);
+ }
+
+ if ($this->maxModified !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'dateModified <= %d',
+ $this->maxModified);
+ }
+
return $where;
}
}
diff --git a/src/applications/drydock/query/DrydockLogQuery.php b/src/applications/drydock/query/DrydockLogQuery.php
index 80f47f584f..d7e1f77ae6 100644
--- a/src/applications/drydock/query/DrydockLogQuery.php
+++ b/src/applications/drydock/query/DrydockLogQuery.php
@@ -1,173 +1,169 @@
<?php
final class DrydockLogQuery extends DrydockQuery {
private $ids;
private $blueprintPHIDs;
private $resourcePHIDs;
private $leasePHIDs;
private $operationPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withBlueprintPHIDs(array $phids) {
$this->blueprintPHIDs = $phids;
return $this;
}
public function withResourcePHIDs(array $phids) {
$this->resourcePHIDs = $phids;
return $this;
}
public function withLeasePHIDs(array $phids) {
$this->leasePHIDs = $phids;
return $this;
}
public function withOperationPHIDs(array $phids) {
$this->operationPHIDs = $phids;
return $this;
}
public function newResultObject() {
return new DrydockLog();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function didFilterPage(array $logs) {
$blueprint_phids = array_filter(mpull($logs, 'getBlueprintPHID'));
if ($blueprint_phids) {
$blueprints = id(new DrydockBlueprintQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($blueprint_phids)
->execute();
$blueprints = mpull($blueprints, null, 'getPHID');
} else {
$blueprints = array();
}
foreach ($logs as $key => $log) {
$blueprint = null;
$blueprint_phid = $log->getBlueprintPHID();
if ($blueprint_phid) {
$blueprint = idx($blueprints, $blueprint_phid);
}
$log->attachBlueprint($blueprint);
}
$resource_phids = array_filter(mpull($logs, 'getResourcePHID'));
if ($resource_phids) {
$resources = id(new DrydockResourceQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($resource_phids)
->execute();
$resources = mpull($resources, null, 'getPHID');
} else {
$resources = array();
}
foreach ($logs as $key => $log) {
$resource = null;
$resource_phid = $log->getResourcePHID();
if ($resource_phid) {
$resource = idx($resources, $resource_phid);
}
$log->attachResource($resource);
}
$lease_phids = array_filter(mpull($logs, 'getLeasePHID'));
if ($lease_phids) {
$leases = id(new DrydockLeaseQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($lease_phids)
->execute();
$leases = mpull($leases, null, 'getPHID');
} else {
$leases = array();
}
foreach ($logs as $key => $log) {
$lease = null;
$lease_phid = $log->getLeasePHID();
if ($lease_phid) {
$lease = idx($leases, $lease_phid);
}
$log->attachLease($lease);
}
$operation_phids = array_filter(mpull($logs, 'getOperationPHID'));
if ($operation_phids) {
$operations = id(new DrydockRepositoryOperationQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($operation_phids)
->execute();
$operations = mpull($operations, null, 'getPHID');
} else {
$operations = array();
}
foreach ($logs as $key => $log) {
$operation = null;
$operation_phid = $log->getOperationPHID();
if ($operation_phid) {
$operation = idx($operations, $operation_phid);
}
$log->attachOperation($operation);
}
return $logs;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ls)',
$this->ids);
}
if ($this->blueprintPHIDs !== null) {
$where[] = qsprintf(
$conn,
'blueprintPHID IN (%Ls)',
$this->blueprintPHIDs);
}
if ($this->resourcePHIDs !== null) {
$where[] = qsprintf(
$conn,
'resourcePHID IN (%Ls)',
$this->resourcePHIDs);
}
if ($this->leasePHIDs !== null) {
$where[] = qsprintf(
$conn,
'leasePHID IN (%Ls)',
$this->leasePHIDs);
}
if ($this->operationPHIDs !== null) {
$where[] = qsprintf(
$conn,
'operationPHID IN (%Ls)',
$this->operationPHIDs);
}
return $where;
}
}
diff --git a/src/applications/drydock/query/DrydockRepositoryOperationQuery.php b/src/applications/drydock/query/DrydockRepositoryOperationQuery.php
index a5fbe0acc9..6ef5d1b295 100644
--- a/src/applications/drydock/query/DrydockRepositoryOperationQuery.php
+++ b/src/applications/drydock/query/DrydockRepositoryOperationQuery.php
@@ -1,189 +1,185 @@
<?php
final class DrydockRepositoryOperationQuery extends DrydockQuery {
private $ids;
private $phids;
private $objectPHIDs;
private $repositoryPHIDs;
private $operationStates;
private $operationTypes;
private $isDismissed;
private $authorPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withObjectPHIDs(array $object_phids) {
$this->objectPHIDs = $object_phids;
return $this;
}
public function withRepositoryPHIDs(array $repository_phids) {
$this->repositoryPHIDs = $repository_phids;
return $this;
}
public function withOperationStates(array $states) {
$this->operationStates = $states;
return $this;
}
public function withOperationTypes(array $types) {
$this->operationTypes = $types;
return $this;
}
public function withIsDismissed($dismissed) {
$this->isDismissed = $dismissed;
return $this;
}
public function withAuthorPHIDs(array $phids) {
$this->authorPHIDs = $phids;
return $this;
}
public function newResultObject() {
return new DrydockRepositoryOperation();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $operations) {
$implementations = DrydockRepositoryOperationType::getAllOperationTypes();
$viewer = $this->getViewer();
foreach ($operations as $key => $operation) {
$impl = idx($implementations, $operation->getOperationType());
if (!$impl) {
$this->didRejectResult($operation);
unset($operations[$key]);
continue;
}
$impl = id(clone $impl)
->setViewer($viewer)
->setOperation($operation);
$operation->attachImplementation($impl);
}
$repository_phids = mpull($operations, 'getRepositoryPHID');
if ($repository_phids) {
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($repository_phids)
->execute();
$repositories = mpull($repositories, null, 'getPHID');
} else {
$repositories = array();
}
foreach ($operations as $key => $operation) {
$repository = idx($repositories, $operation->getRepositoryPHID());
if (!$repository) {
$this->didRejectResult($operation);
unset($operations[$key]);
continue;
}
$operation->attachRepository($repository);
}
return $operations;
}
protected function didFilterPage(array $operations) {
$object_phids = mpull($operations, 'getObjectPHID');
if ($object_phids) {
$objects = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($object_phids)
->execute();
$objects = mpull($objects, null, 'getPHID');
} else {
$objects = array();
}
foreach ($operations as $key => $operation) {
$object = idx($objects, $operation->getObjectPHID());
$operation->attachObject($object);
}
return $operations;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->objectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'objectPHID IN (%Ls)',
$this->objectPHIDs);
}
if ($this->repositoryPHIDs !== null) {
$where[] = qsprintf(
$conn,
'repositoryPHID IN (%Ls)',
$this->repositoryPHIDs);
}
if ($this->operationStates !== null) {
$where[] = qsprintf(
$conn,
'operationState IN (%Ls)',
$this->operationStates);
}
if ($this->operationTypes !== null) {
$where[] = qsprintf(
$conn,
'operationType IN (%Ls)',
$this->operationTypes);
}
if ($this->isDismissed !== null) {
$where[] = qsprintf(
$conn,
'isDismissed = %d',
(int)$this->isDismissed);
}
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'authorPHID IN (%Ls)',
$this->authorPHIDs);
}
return $where;
}
}
diff --git a/src/applications/drydock/query/DrydockResourceQuery.php b/src/applications/drydock/query/DrydockResourceQuery.php
index bcbff03663..395e758a23 100644
--- a/src/applications/drydock/query/DrydockResourceQuery.php
+++ b/src/applications/drydock/query/DrydockResourceQuery.php
@@ -1,149 +1,145 @@
<?php
final class DrydockResourceQuery extends DrydockQuery {
private $ids;
private $phids;
private $statuses;
private $types;
private $blueprintPHIDs;
private $datasourceQuery;
private $needUnconsumedCommands;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withTypes(array $types) {
$this->types = $types;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function withBlueprintPHIDs(array $blueprint_phids) {
$this->blueprintPHIDs = $blueprint_phids;
return $this;
}
public function withDatasourceQuery($query) {
$this->datasourceQuery = $query;
return $this;
}
public function needUnconsumedCommands($need) {
$this->needUnconsumedCommands = $need;
return $this;
}
public function newResultObject() {
return new DrydockResource();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $resources) {
$blueprint_phids = mpull($resources, 'getBlueprintPHID');
$blueprints = id(new DrydockBlueprintQuery())
->setViewer($this->getViewer())
->withPHIDs($blueprint_phids)
->execute();
$blueprints = mpull($blueprints, null, 'getPHID');
foreach ($resources as $key => $resource) {
$blueprint = idx($blueprints, $resource->getBlueprintPHID());
if (!$blueprint) {
$this->didRejectResult($resource);
unset($resources[$key]);
continue;
}
$resource->attachBlueprint($blueprint);
}
return $resources;
}
protected function didFilterPage(array $resources) {
if ($this->needUnconsumedCommands) {
$commands = id(new DrydockCommandQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withTargetPHIDs(mpull($resources, 'getPHID'))
->withConsumed(false)
->execute();
$commands = mgroup($commands, 'getTargetPHID');
foreach ($resources as $resource) {
$list = idx($commands, $resource->getPHID(), array());
$resource->attachUnconsumedCommands($list);
}
}
return $resources;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'resource.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'resource.phid IN (%Ls)',
$this->phids);
}
if ($this->types !== null) {
$where[] = qsprintf(
$conn,
'resource.type IN (%Ls)',
$this->types);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'resource.status IN (%Ls)',
$this->statuses);
}
if ($this->blueprintPHIDs !== null) {
$where[] = qsprintf(
$conn,
'resource.blueprintPHID IN (%Ls)',
$this->blueprintPHIDs);
}
if ($this->datasourceQuery !== null) {
$where[] = qsprintf(
$conn,
'resource.name LIKE %>',
$this->datasourceQuery);
}
return $where;
}
protected function getPrimaryTableAlias() {
return 'resource';
}
}
diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php
index 2b77146495..12a18b3544 100644
--- a/src/applications/drydock/storage/DrydockLease.php
+++ b/src/applications/drydock/storage/DrydockLease.php
@@ -1,605 +1,668 @@
<?php
final class DrydockLease extends DrydockDAO
implements
PhabricatorPolicyInterface,
PhabricatorConduitResultInterface {
protected $resourcePHID;
protected $resourceType;
protected $until;
protected $ownerPHID;
protected $authorizingPHID;
protected $attributes = array();
protected $status = DrydockLeaseStatus::STATUS_PENDING;
protected $acquiredEpoch;
protected $activatedEpoch;
private $resource = self::ATTACHABLE;
private $unconsumedCommands = self::ATTACHABLE;
private $releaseOnDestruction;
private $isAcquired = false;
private $isActivated = false;
private $activateWhenAcquired = false;
private $slotLocks = array();
public static function initializeNewLease() {
$lease = new DrydockLease();
// Pregenerate a PHID so that the caller can set something up to release
// this lease before queueing it for activation.
$lease->setPHID($lease->generatePHID());
return $lease;
}
/**
* Flag this lease to be released when its destructor is called. This is
* mostly useful if you have a script which acquires, uses, and then releases
* a lease, as you don't need to explicitly handle exceptions to properly
* release the lease.
*/
public function setReleaseOnDestruction($release) {
$this->releaseOnDestruction = $release;
return $this;
}
public function __destruct() {
if (!$this->releaseOnDestruction) {
return;
}
if (!$this->canRelease()) {
return;
}
$actor = PhabricatorUser::getOmnipotentUser();
$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
$command = DrydockCommand::initializeNewCommand($actor)
->setTargetPHID($this->getPHID())
->setAuthorPHID($drydock_phid)
->setCommand(DrydockCommand::COMMAND_RELEASE)
->save();
$this->scheduleUpdate();
}
public function setStatus($status) {
if ($status == DrydockLeaseStatus::STATUS_ACQUIRED) {
if (!$this->getAcquiredEpoch()) {
$this->setAcquiredEpoch(PhabricatorTime::getNow());
}
}
if ($status == DrydockLeaseStatus::STATUS_ACTIVE) {
if (!$this->getActivatedEpoch()) {
$this->setActivatedEpoch(PhabricatorTime::getNow());
}
}
return parent::setStatus($status);
}
public function getLeaseName() {
return pht('Lease %d', $this->getID());
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'attributes' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'status' => 'text32',
'until' => 'epoch?',
'resourceType' => 'text128',
'ownerPHID' => 'phid?',
'resourcePHID' => 'phid?',
'acquiredEpoch' => 'epoch?',
'activatedEpoch' => 'epoch?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_resource' => array(
'columns' => array('resourcePHID', 'status'),
),
'key_status' => array(
'columns' => array('status'),
),
'key_owner' => array(
'columns' => array('ownerPHID'),
),
+ 'key_recent' => array(
+ 'columns' => array('resourcePHID', 'dateModified'),
+ ),
),
) + parent::getConfiguration();
}
public function setAttribute($key, $value) {
$this->attributes[$key] = $value;
return $this;
}
public function getAttribute($key, $default = null) {
return idx($this->attributes, $key, $default);
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(DrydockLeasePHIDType::TYPECONST);
}
public function getInterface($type) {
return $this->getResource()->getInterface($this, $type);
}
public function getResource() {
return $this->assertAttached($this->resource);
}
public function attachResource(DrydockResource $resource = null) {
$this->resource = $resource;
return $this;
}
public function hasAttachedResource() {
return ($this->resource !== null);
}
public function getUnconsumedCommands() {
return $this->assertAttached($this->unconsumedCommands);
}
public function attachUnconsumedCommands(array $commands) {
$this->unconsumedCommands = $commands;
return $this;
}
public function isReleasing() {
foreach ($this->getUnconsumedCommands() as $command) {
if ($command->getCommand() == DrydockCommand::COMMAND_RELEASE) {
return true;
}
}
return false;
}
public function queueForActivation() {
if ($this->getID()) {
throw new Exception(
pht('Only new leases may be queued for activation!'));
}
if (!$this->getAuthorizingPHID()) {
throw new Exception(
pht(
'Trying to queue a lease for activation without an authorizing '.
'object. Use "%s" to specify the PHID of the authorizing object. '.
'The authorizing object must be approved to use the allowed '.
'blueprints.',
'setAuthorizingPHID()'));
}
if (!$this->getAllowedBlueprintPHIDs()) {
throw new Exception(
pht(
'Trying to queue a lease for activation without any allowed '.
'Blueprints. Use "%s" to specify allowed blueprints. The '.
'authorizing object must be approved to use the allowed blueprints.',
'setAllowedBlueprintPHIDs()'));
}
$this
->setStatus(DrydockLeaseStatus::STATUS_PENDING)
->save();
$this->scheduleUpdate();
$this->logEvent(DrydockLeaseQueuedLogType::LOGCONST);
return $this;
}
public function setActivateWhenAcquired($activate) {
$this->activateWhenAcquired = true;
return $this;
}
public function needSlotLock($key) {
$this->slotLocks[] = $key;
return $this;
}
public function acquireOnResource(DrydockResource $resource) {
$expect_status = DrydockLeaseStatus::STATUS_PENDING;
$actual_status = $this->getStatus();
if ($actual_status != $expect_status) {
throw new Exception(
pht(
'Trying to acquire a lease on a resource which is in the wrong '.
'state: status must be "%s", actually "%s".',
$expect_status,
$actual_status));
}
if ($this->activateWhenAcquired) {
$new_status = DrydockLeaseStatus::STATUS_ACTIVE;
} else {
$new_status = DrydockLeaseStatus::STATUS_ACQUIRED;
}
if ($new_status == DrydockLeaseStatus::STATUS_ACTIVE) {
if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) {
throw new Exception(
pht(
'Trying to acquire an active lease on a pending resource. '.
'You can not immediately activate leases on resources which '.
'need time to start up.'));
}
}
// Before we associate the lease with the resource, we lock the resource
// and reload it to make sure it is still pending or active. If we don't
// do this, the resource may have just been reclaimed. (Once we acquire
// the resource that stops it from being released, so we're nearly safe.)
$resource_phid = $resource->getPHID();
$hash = PhabricatorHash::digestForIndex($resource_phid);
$lock_key = 'drydock.resource:'.$hash;
$lock = PhabricatorGlobalLock::newLock($lock_key);
try {
$lock->lock(15);
} catch (Exception $ex) {
throw new DrydockResourceLockException(
pht(
'Failed to acquire lock for resource ("%s") while trying to '.
'acquire lease ("%s").',
$resource->getPHID(),
$this->getPHID()));
}
$resource->reload();
if (($resource->getStatus() !== DrydockResourceStatus::STATUS_ACTIVE) &&
($resource->getStatus() !== DrydockResourceStatus::STATUS_PENDING)) {
throw new DrydockAcquiredBrokenResourceException(
pht(
'Trying to acquire lease ("%s") on a resource ("%s") in the '.
'wrong status ("%s").',
$this->getPHID(),
$resource->getPHID(),
$resource->getStatus()));
}
$caught = null;
try {
$this->openTransaction();
try {
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array();
} catch (DrydockSlotLockException $ex) {
$this->killTransaction();
$this->logEvent(
DrydockSlotLockFailureLogType::LOGCONST,
array(
'locks' => $ex->getLockMap(),
));
throw $ex;
}
$this
->setResourcePHID($resource->getPHID())
->attachResource($resource)
->setStatus($new_status)
->save();
$this->saveTransaction();
} catch (Exception $ex) {
$caught = $ex;
}
$lock->unlock();
if ($caught) {
throw $caught;
}
$this->isAcquired = true;
$this->logEvent(DrydockLeaseAcquiredLogType::LOGCONST);
if ($new_status == DrydockLeaseStatus::STATUS_ACTIVE) {
$this->didActivate();
}
return $this;
}
public function isAcquiredLease() {
return $this->isAcquired;
}
public function activateOnResource(DrydockResource $resource) {
$expect_status = DrydockLeaseStatus::STATUS_ACQUIRED;
$actual_status = $this->getStatus();
if ($actual_status != $expect_status) {
throw new Exception(
pht(
'Trying to activate a lease which has the wrong status: status '.
'must be "%s", actually "%s".',
$expect_status,
$actual_status));
}
if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) {
// TODO: Be stricter about this?
throw new Exception(
pht(
'Trying to activate a lease on a pending resource.'));
}
$this->openTransaction();
try {
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array();
} catch (DrydockSlotLockException $ex) {
$this->killTransaction();
$this->logEvent(
DrydockSlotLockFailureLogType::LOGCONST,
array(
'locks' => $ex->getLockMap(),
));
throw $ex;
}
$this
->setStatus(DrydockLeaseStatus::STATUS_ACTIVE)
->save();
$this->saveTransaction();
$this->isActivated = true;
$this->didActivate();
return $this;
}
public function isActivatedLease() {
return $this->isActivated;
}
public function scheduleUpdate($epoch = null) {
PhabricatorWorker::scheduleTask(
'DrydockLeaseUpdateWorker',
array(
'leasePHID' => $this->getPHID(),
'isExpireTask' => ($epoch !== null),
),
array(
'objectPHID' => $this->getPHID(),
'delayUntil' => ($epoch ? (int)$epoch : null),
));
}
+ public function getAllocatedResourcePHIDs() {
+ return $this->getAttribute('internal.resourcePHIDs.allocated', array());
+ }
+
+ public function setAllocatedResourcePHIDs(array $phids) {
+ return $this->setAttribute('internal.resourcePHIDs.allocated', $phids);
+ }
+
+ public function addAllocatedResourcePHIDs(array $phids) {
+ $allocated_phids = $this->getAllocatedResourcePHIDs();
+
+ foreach ($phids as $phid) {
+ $allocated_phids[$phid] = $phid;
+ }
+
+ return $this->setAllocatedResourcePHIDs($allocated_phids);
+ }
+
+ public function removeAllocatedResourcePHIDs(array $phids) {
+ $allocated_phids = $this->getAllocatedResourcePHIDs();
+
+ foreach ($phids as $phid) {
+ unset($allocated_phids[$phid]);
+ }
+
+ return $this->setAllocatedResourcePHIDs($allocated_phids);
+ }
+
+ public function getReclaimedResourcePHIDs() {
+ return $this->getAttribute('internal.resourcePHIDs.reclaimed', array());
+ }
+
+ public function setReclaimedResourcePHIDs(array $phids) {
+ return $this->setAttribute('internal.resourcePHIDs.reclaimed', $phids);
+ }
+
+ public function addReclaimedResourcePHIDs(array $phids) {
+ $reclaimed_phids = $this->getReclaimedResourcePHIDs();
+
+ foreach ($phids as $phid) {
+ $reclaimed_phids[$phid] = $phid;
+ }
+
+ return $this->setReclaimedResourcePHIDs($reclaimed_phids);
+ }
+
+ public function removeReclaimedResourcePHIDs(array $phids) {
+ $reclaimed_phids = $this->getReclaimedResourcePHIDs();
+
+ foreach ($phids as $phid) {
+ unset($reclaimed_phids[$phid]);
+ }
+
+ return $this->setReclaimedResourcePHIDs($reclaimed_phids);
+ }
+
public function setAwakenTaskIDs(array $ids) {
$this->setAttribute('internal.awakenTaskIDs', $ids);
return $this;
}
public function setAllowedBlueprintPHIDs(array $phids) {
$this->setAttribute('internal.blueprintPHIDs', $phids);
return $this;
}
public function getAllowedBlueprintPHIDs() {
return $this->getAttribute('internal.blueprintPHIDs', array());
}
private function didActivate() {
$viewer = PhabricatorUser::getOmnipotentUser();
$need_update = false;
$this->logEvent(DrydockLeaseActivatedLogType::LOGCONST);
$commands = id(new DrydockCommandQuery())
->setViewer($viewer)
->withTargetPHIDs(array($this->getPHID()))
->withConsumed(false)
->execute();
if ($commands) {
$need_update = true;
}
if ($need_update) {
$this->scheduleUpdate();
}
$expires = $this->getUntil();
if ($expires) {
$this->scheduleUpdate($expires);
}
$this->awakenTasks();
}
public function logEvent($type, array $data = array()) {
$log = id(new DrydockLog())
->setEpoch(PhabricatorTime::getNow())
->setType($type)
->setData($data);
$log->setLeasePHID($this->getPHID());
$resource_phid = $this->getResourcePHID();
if ($resource_phid) {
$resource = $this->getResource();
$log->setResourcePHID($resource->getPHID());
$log->setBlueprintPHID($resource->getBlueprintPHID());
}
return $log->save();
}
/**
* Awaken yielded tasks after a state change.
*
* @return this
*/
public function awakenTasks() {
$awaken_ids = $this->getAttribute('internal.awakenTaskIDs');
if (is_array($awaken_ids) && $awaken_ids) {
PhabricatorWorker::awakenTaskIDs($awaken_ids);
}
return $this;
}
public function getURI() {
$id = $this->getID();
return "/drydock/lease/{$id}/";
}
+ public function getDisplayName() {
+ return pht('Drydock Lease %d', $this->getID());
+ }
+
/* -( Status )------------------------------------------------------------- */
public function getStatusObject() {
return DrydockLeaseStatus::newStatusObject($this->getStatus());
}
public function getStatusIcon() {
return $this->getStatusObject()->getIcon();
}
public function getStatusColor() {
return $this->getStatusObject()->getColor();
}
public function getStatusDisplayName() {
return $this->getStatusObject()->getDisplayName();
}
public function isActivating() {
return $this->getStatusObject()->isActivating();
}
public function isActive() {
return $this->getStatusObject()->isActive();
}
public function canRelease() {
if (!$this->getID()) {
return false;
}
return $this->getStatusObject()->canRelease();
}
public function canReceiveCommands() {
return $this->getStatusObject()->canReceiveCommands();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
if ($this->getResource()) {
return $this->getResource()->getPolicy($capability);
}
// TODO: Implement reasonable policies.
return PhabricatorPolicies::getMostOpenPolicy();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
if ($this->getResource()) {
return $this->getResource()->hasAutomaticCapability($capability, $viewer);
}
return false;
}
public function describeAutomaticCapability($capability) {
return pht('Leases inherit policies from the resources they lease.');
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('resourcePHID')
->setType('phid?')
->setDescription(pht('PHID of the leased resource, if any.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('resourceType')
->setType('string')
->setDescription(pht('Type of resource being leased.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('until')
->setType('int?')
->setDescription(pht('Epoch at which this lease expires, if any.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('ownerPHID')
->setType('phid?')
->setDescription(pht('The PHID of the object that owns this lease.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('authorizingPHID')
->setType('phid')
->setDescription(pht(
'The PHID of the object that authorized this lease.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('status')
->setType('map<string, wild>')
->setDescription(pht(
"The string constant and name of this lease's status.")),
);
}
public function getFieldValuesForConduit() {
$status = $this->getStatus();
$until = $this->getUntil();
if ($until) {
$until = (int)$until;
} else {
$until = null;
}
return array(
'resourcePHID' => $this->getResourcePHID(),
'resourceType' => $this->getResourceType(),
'until' => $until,
'ownerPHID' => $this->getOwnerPHID(),
'authorizingPHID' => $this->getAuthorizingPHID(),
'status' => array(
'value' => $status,
'name' => DrydockLeaseStatus::getNameForStatus($status),
),
);
}
public function getConduitSearchAttachments() {
return array();
}
}
diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php
index bc672dba3c..38e6660f7e 100644
--- a/src/applications/drydock/storage/DrydockResource.php
+++ b/src/applications/drydock/storage/DrydockResource.php
@@ -1,379 +1,383 @@
<?php
final class DrydockResource extends DrydockDAO
implements
PhabricatorPolicyInterface,
PhabricatorConduitResultInterface {
protected $id;
protected $phid;
protected $blueprintPHID;
protected $status;
protected $until;
protected $type;
protected $attributes = array();
protected $capabilities = array();
protected $ownerPHID;
private $blueprint = self::ATTACHABLE;
private $unconsumedCommands = self::ATTACHABLE;
private $isAllocated = false;
private $isActivated = false;
private $activateWhenAllocated = false;
private $slotLocks = array();
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'attributes' => self::SERIALIZATION_JSON,
'capabilities' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'ownerPHID' => 'phid?',
'status' => 'text32',
'type' => 'text64',
'until' => 'epoch?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_type' => array(
'columns' => array('type', 'status'),
),
'key_blueprint' => array(
'columns' => array('blueprintPHID', 'status'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(DrydockResourcePHIDType::TYPECONST);
}
public function getResourceName() {
return $this->getBlueprint()->getResourceName($this);
}
public function getAttribute($key, $default = null) {
return idx($this->attributes, $key, $default);
}
public function getAttributesForTypeSpec(array $attribute_names) {
return array_select_keys($this->attributes, $attribute_names);
}
public function setAttribute($key, $value) {
$this->attributes[$key] = $value;
return $this;
}
public function getCapability($key, $default = null) {
return idx($this->capbilities, $key, $default);
}
public function getInterface(DrydockLease $lease, $type) {
return $this->getBlueprint()->getInterface($this, $lease, $type);
}
public function getBlueprint() {
return $this->assertAttached($this->blueprint);
}
public function attachBlueprint(DrydockBlueprint $blueprint) {
$this->blueprint = $blueprint;
return $this;
}
public function getUnconsumedCommands() {
return $this->assertAttached($this->unconsumedCommands);
}
public function attachUnconsumedCommands(array $commands) {
$this->unconsumedCommands = $commands;
return $this;
}
public function isReleasing() {
foreach ($this->getUnconsumedCommands() as $command) {
if ($command->getCommand() == DrydockCommand::COMMAND_RELEASE) {
return true;
}
}
return false;
}
public function setActivateWhenAllocated($activate) {
$this->activateWhenAllocated = $activate;
return $this;
}
public function needSlotLock($key) {
$this->slotLocks[] = $key;
return $this;
}
public function allocateResource() {
// We expect resources to have a pregenerated PHID, as they should have
// been created by a call to DrydockBlueprint->newResourceTemplate().
if (!$this->getPHID()) {
throw new Exception(
pht(
'Trying to allocate a resource with no generated PHID. Use "%s" to '.
'create new resource templates.',
'newResourceTemplate()'));
}
$expect_status = DrydockResourceStatus::STATUS_PENDING;
$actual_status = $this->getStatus();
if ($actual_status != $expect_status) {
throw new Exception(
pht(
'Trying to allocate a resource from the wrong status. Status must '.
'be "%s", actually "%s".',
$expect_status,
$actual_status));
}
if ($this->activateWhenAllocated) {
$new_status = DrydockResourceStatus::STATUS_ACTIVE;
} else {
$new_status = DrydockResourceStatus::STATUS_PENDING;
}
$this->openTransaction();
try {
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array();
} catch (DrydockSlotLockException $ex) {
$this->killTransaction();
if ($this->getID()) {
$log_target = $this;
} else {
// If we don't have an ID, we have to log this on the blueprint, as the
// resource is not going to be saved so the PHID will vanish.
$log_target = $this->getBlueprint();
}
$log_target->logEvent(
DrydockSlotLockFailureLogType::LOGCONST,
array(
'locks' => $ex->getLockMap(),
));
throw $ex;
}
$this
->setStatus($new_status)
->save();
$this->saveTransaction();
$this->isAllocated = true;
if ($new_status == DrydockResourceStatus::STATUS_ACTIVE) {
$this->didActivate();
}
return $this;
}
public function isAllocatedResource() {
return $this->isAllocated;
}
public function activateResource() {
if (!$this->getID()) {
throw new Exception(
pht(
'Trying to activate a resource which has not yet been persisted.'));
}
$expect_status = DrydockResourceStatus::STATUS_PENDING;
$actual_status = $this->getStatus();
if ($actual_status != $expect_status) {
throw new Exception(
pht(
'Trying to activate a resource from the wrong status. Status must '.
'be "%s", actually "%s".',
$expect_status,
$actual_status));
}
$this->openTransaction();
try {
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array();
} catch (DrydockSlotLockException $ex) {
$this->killTransaction();
$this->logEvent(
DrydockSlotLockFailureLogType::LOGCONST,
array(
'locks' => $ex->getLockMap(),
));
throw $ex;
}
$this
->setStatus(DrydockResourceStatus::STATUS_ACTIVE)
->save();
$this->saveTransaction();
$this->isActivated = true;
$this->didActivate();
return $this;
}
public function isActivatedResource() {
return $this->isActivated;
}
public function scheduleUpdate($epoch = null) {
PhabricatorWorker::scheduleTask(
'DrydockResourceUpdateWorker',
array(
'resourcePHID' => $this->getPHID(),
'isExpireTask' => ($epoch !== null),
),
array(
'objectPHID' => $this->getPHID(),
'delayUntil' => ($epoch ? (int)$epoch : null),
));
}
private function didActivate() {
$viewer = PhabricatorUser::getOmnipotentUser();
$need_update = false;
$commands = id(new DrydockCommandQuery())
->setViewer($viewer)
->withTargetPHIDs(array($this->getPHID()))
->withConsumed(false)
->execute();
if ($commands) {
$need_update = true;
}
if ($need_update) {
$this->scheduleUpdate();
}
$expires = $this->getUntil();
if ($expires) {
$this->scheduleUpdate($expires);
}
}
public function logEvent($type, array $data = array()) {
$log = id(new DrydockLog())
->setEpoch(PhabricatorTime::getNow())
->setType($type)
->setData($data);
$log->setResourcePHID($this->getPHID());
$log->setBlueprintPHID($this->getBlueprintPHID());
return $log->save();
}
+ public function getDisplayName() {
+ return pht('Drydock Resource %d', $this->getID());
+ }
+
/* -( Status )------------------------------------------------------------- */
public function getStatusObject() {
return DrydockResourceStatus::newStatusObject($this->getStatus());
}
public function getStatusIcon() {
return $this->getStatusObject()->getIcon();
}
public function getStatusColor() {
return $this->getStatusObject()->getColor();
}
public function getStatusDisplayName() {
return $this->getStatusObject()->getDisplayName();
}
public function canRelease() {
return $this->getStatusObject()->canRelease();
}
public function canReceiveCommands() {
return $this->getStatusObject()->canReceiveCommands();
}
public function isActive() {
return $this->getStatusObject()->isActive();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return $this->getBlueprint()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getBlueprint()->hasAutomaticCapability(
$capability,
$viewer);
}
public function describeAutomaticCapability($capability) {
return pht('Resources inherit the policies of their blueprints.');
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('blueprintPHID')
->setType('phid')
->setDescription(pht('The blueprint which generated this resource.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('status')
->setType('map<string, wild>')
->setDescription(pht('Information about resource status.')),
);
}
public function getFieldValuesForConduit() {
$status = $this->getStatus();
return array(
'blueprintPHID' => $this->getBlueprintPHID(),
'status' => array(
'value' => $status,
'name' => DrydockResourceStatus::getNameForStatus($status),
),
);
}
public function getConduitSearchAttachments() {
return array();
}
}
diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php
index b83022f720..0402276696 100644
--- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php
+++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php
@@ -1,1026 +1,1129 @@
<?php
/**
* @task update Updating Leases
* @task command Processing Commands
* @task allocator Drydock Allocator
* @task acquire Acquiring Leases
* @task activate Activating Leases
* @task release Releasing Leases
* @task break Breaking Leases
* @task destroy Destroying Leases
*/
final class DrydockLeaseUpdateWorker extends DrydockWorker {
protected function doWork() {
$lease_phid = $this->getTaskDataValue('leasePHID');
$hash = PhabricatorHash::digestForIndex($lease_phid);
$lock_key = 'drydock.lease:'.$hash;
$lock = PhabricatorGlobalLock::newLock($lock_key)
->lock(1);
try {
$lease = $this->loadLease($lease_phid);
$this->handleUpdate($lease);
} catch (Exception $ex) {
$lock->unlock();
$this->flushDrydockTaskQueue();
throw $ex;
}
$lock->unlock();
}
/* -( Updating Leases )---------------------------------------------------- */
/**
* @task update
*/
private function handleUpdate(DrydockLease $lease) {
try {
$this->updateLease($lease);
} catch (DrydockAcquiredBrokenResourceException $ex) {
// If this lease acquired a resource but failed to activate, we don't
// need to break the lease. We can throw it back in the pool and let
// it take another shot at acquiring a new resource.
// Before we throw it back, release any locks the lease is holding.
DrydockSlotLock::releaseLocks($lease->getPHID());
$lease
->setStatus(DrydockLeaseStatus::STATUS_PENDING)
->setResourcePHID(null)
->save();
$lease->logEvent(
DrydockLeaseReacquireLogType::LOGCONST,
array(
'class' => get_class($ex),
'message' => $ex->getMessage(),
));
$this->yieldLease($lease, $ex);
} catch (Exception $ex) {
if ($this->isTemporaryException($ex)) {
$this->yieldLease($lease, $ex);
} else {
$this->breakLease($lease, $ex);
}
}
}
/**
* @task update
*/
private function updateLease(DrydockLease $lease) {
$this->processLeaseCommands($lease);
$lease_status = $lease->getStatus();
switch ($lease_status) {
case DrydockLeaseStatus::STATUS_PENDING:
$this->executeAllocator($lease);
break;
case DrydockLeaseStatus::STATUS_ACQUIRED:
$this->activateLease($lease);
break;
case DrydockLeaseStatus::STATUS_ACTIVE:
// Nothing to do.
break;
case DrydockLeaseStatus::STATUS_RELEASED:
case DrydockLeaseStatus::STATUS_BROKEN:
$this->destroyLease($lease);
break;
case DrydockLeaseStatus::STATUS_DESTROYED:
break;
}
$this->yieldIfExpiringLease($lease);
}
/**
* @task update
*/
private function yieldLease(DrydockLease $lease, Exception $ex) {
$duration = $this->getYieldDurationFromException($ex);
$lease->logEvent(
DrydockLeaseActivationYieldLogType::LOGCONST,
array(
'duration' => $duration,
));
throw new PhabricatorWorkerYieldException($duration);
}
/* -( Processing Commands )------------------------------------------------ */
/**
* @task command
*/
private function processLeaseCommands(DrydockLease $lease) {
if (!$lease->canReceiveCommands()) {
return;
}
$this->checkLeaseExpiration($lease);
$commands = $this->loadCommands($lease->getPHID());
foreach ($commands as $command) {
if (!$lease->canReceiveCommands()) {
break;
}
$this->processLeaseCommand($lease, $command);
$command
->setIsConsumed(true)
->save();
}
}
/**
* @task command
*/
private function processLeaseCommand(
DrydockLease $lease,
DrydockCommand $command) {
switch ($command->getCommand()) {
case DrydockCommand::COMMAND_RELEASE:
$this->releaseLease($lease);
break;
}
}
/* -( Drydock Allocator )-------------------------------------------------- */
/**
* Find or build a resource which can satisfy a given lease request, then
* acquire the lease.
*
* @param DrydockLease Requested lease.
* @return void
* @task allocator
*/
private function executeAllocator(DrydockLease $lease) {
$blueprints = $this->loadBlueprintsForAllocatingLease($lease);
// If we get nothing back, that means no blueprint is defined which can
// ever build the requested resource. This is a permanent failure, since
// we don't expect to succeed no matter how many times we try.
if (!$blueprints) {
throw new PhabricatorWorkerPermanentFailureException(
pht(
'No active Drydock blueprint exists which can ever allocate a '.
'resource for lease "%s".',
$lease->getPHID()));
}
// First, try to find a suitable open resource which we can acquire a new
// lease on.
- $resources = $this->loadResourcesForAllocatingLease($blueprints, $lease);
+ $resources = $this->loadAcquirableResourcesForLease($blueprints, $lease);
- // If no resources exist yet, see if we can build one.
- if (!$resources) {
- $usable_blueprints = $this->removeOverallocatedBlueprints(
- $blueprints,
- $lease);
-
- // If we get nothing back here, some blueprint claims it can eventually
- // satisfy the lease, just not right now. This is a temporary failure,
- // and we expect allocation to succeed eventually.
- if (!$usable_blueprints) {
- $blueprints = $this->rankBlueprints($blueprints, $lease);
-
- // Try to actively reclaim unused resources. If we succeed, jump back
- // into the queue in an effort to claim it.
- foreach ($blueprints as $blueprint) {
- $reclaimed = $this->reclaimResources($blueprint, $lease);
- if ($reclaimed) {
- $lease->logEvent(
- DrydockLeaseReclaimLogType::LOGCONST,
- array(
- 'resourcePHIDs' => array($reclaimed->getPHID()),
- ));
-
- throw new PhabricatorWorkerYieldException(15);
- }
- }
+ list($free_resources, $used_resources) = $this->partitionResources(
+ $lease,
+ $resources);
+
+ $resource = $this->leaseAnyResource($lease, $free_resources);
+ if ($resource) {
+ return $resource;
+ }
+
+ // We're about to try creating a resource. If we're already creating
+ // something, just yield until that resolves.
+
+ $this->yieldForPendingResources($lease);
+
+ // We haven't been able to lease an existing resource yet, so now we try to
+ // create one. We may still have some less-desirable "used" resources that
+ // we'll sometimes try to lease later if we fail to allocate a new resource.
+
+ $resource = $this->newLeasedResource($lease, $blueprints);
+ if ($resource) {
+ return $resource;
+ }
+
+ // We haven't been able to lease a desirable "free" resource or create a
+ // new resource. Try to lease a "used" resource.
+
+ $resource = $this->leaseAnyResource($lease, $used_resources);
+ if ($resource) {
+ return $resource;
+ }
+
+ // If this lease has already triggered a reclaim, just yield and wait for
+ // it to resolve.
+ $this->yieldForReclaimingResources($lease);
+
+ // Try to reclaim a resource. This will yield if it reclaims something.
+ $this->reclaimAnyResource($lease, $blueprints);
+
+ // We weren't able to lease, create, or reclaim any resources. We just have
+ // to wait for resources to become available.
+
+ $lease->logEvent(
+ DrydockLeaseWaitingForResourcesLogType::LOGCONST,
+ array(
+ 'blueprintPHIDs' => mpull($blueprints, 'getPHID'),
+ ));
+
+ throw new PhabricatorWorkerYieldException(15);
+ }
+
+ private function reclaimAnyResource(DrydockLease $lease, array $blueprints) {
+ assert_instances_of($blueprints, 'DrydockBlueprint');
+
+ $blueprints = $this->rankBlueprints($blueprints, $lease);
+
+ // Try to actively reclaim unused resources. If we succeed, jump back
+ // into the queue in an effort to claim it.
+
+ foreach ($blueprints as $blueprint) {
+ $reclaimed = $this->reclaimResources($blueprint, $lease);
+ if ($reclaimed) {
$lease->logEvent(
- DrydockLeaseWaitingForResourcesLogType::LOGCONST,
+ DrydockLeaseReclaimLogType::LOGCONST,
array(
- 'blueprintPHIDs' => mpull($blueprints, 'getPHID'),
+ 'resourcePHIDs' => array($reclaimed->getPHID()),
));
+ // Yield explicitly here: we'll be awakened when the resource is
+ // reclaimed.
+
throw new PhabricatorWorkerYieldException(15);
}
+ }
+ }
- $usable_blueprints = $this->rankBlueprints($usable_blueprints, $lease);
-
- $exceptions = array();
- foreach ($usable_blueprints as $blueprint) {
- try {
- $resources[] = $this->allocateResource($blueprint, $lease);
-
- // Bail after allocating one resource, we don't need any more than
- // this.
- break;
- } catch (Exception $ex) {
- // This failure is not normally expected, so log it. It can be
- // caused by something mundane and recoverable, however (see below
- // for discussion).
-
- // We log to the blueprint separately from the log to the lease:
- // the lease is not attached to a blueprint yet so the lease log
- // will not show up on the blueprint; more than one blueprint may
- // fail; and the lease is not really impacted (and won't log) if at
- // least one blueprint actually works.
-
- $blueprint->logEvent(
- DrydockResourceAllocationFailureLogType::LOGCONST,
- array(
- 'class' => get_class($ex),
- 'message' => $ex->getMessage(),
- ));
+ private function yieldForPendingResources(DrydockLease $lease) {
+ // See T13677. If this lease has already triggered the allocation of
+ // one or more resources and they are still pending, just yield and
+ // wait for them.
- $exceptions[] = $ex;
- }
+ $viewer = $this->getViewer();
+
+ $phids = $lease->getAllocatedResourcePHIDs();
+ if (!$phids) {
+ return null;
+ }
+
+ $resources = id(new DrydockResourceQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($phids)
+ ->withStatuses(
+ array(
+ DrydockResourceStatus::STATUS_PENDING,
+ ))
+ ->setLimit(1)
+ ->execute();
+ if (!$resources) {
+ return;
+ }
+
+ $lease->logEvent(
+ DrydockLeaseWaitingForActivationLogType::LOGCONST,
+ array(
+ 'resourcePHIDs' => mpull($resources, 'getPHID'),
+ ));
+
+ throw new PhabricatorWorkerYieldException(15);
+ }
+
+ private function yieldForReclaimingResources(DrydockLease $lease) {
+ $viewer = $this->getViewer();
+
+ $phids = $lease->getReclaimedResourcePHIDs();
+ if (!$phids) {
+ return;
+ }
+
+ $resources = id(new DrydockResourceQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($phids)
+ ->withStatuses(
+ array(
+ DrydockResourceStatus::STATUS_ACTIVE,
+ DrydockResourceStatus::STATUS_RELEASED,
+ ))
+ ->setLimit(1)
+ ->execute();
+ if (!$resources) {
+ return;
+ }
+
+ $lease->logEvent(
+ DrydockLeaseWaitingForReclamationLogType::LOGCONST,
+ array(
+ 'resourcePHIDs' => mpull($resources, 'getPHID'),
+ ));
+
+ throw new PhabricatorWorkerYieldException(15);
+ }
+
+ private function newLeasedResource(
+ DrydockLease $lease,
+ array $blueprints) {
+ assert_instances_of($blueprints, 'DrydockBlueprint');
+
+ $usable_blueprints = $this->removeOverallocatedBlueprints(
+ $blueprints,
+ $lease);
+
+ // If we get nothing back here, some blueprint claims it can eventually
+ // satisfy the lease, just not right now. This is a temporary failure,
+ // and we expect allocation to succeed eventually.
+
+ // Return, try to lease a "used" resource, and continue from there.
+
+ if (!$usable_blueprints) {
+ return null;
+ }
+
+ $usable_blueprints = $this->rankBlueprints($usable_blueprints, $lease);
+
+ $new_resources = $this->newResources($lease, $usable_blueprints);
+ if (!$new_resources) {
+ // If we were unable to create any new resources, return and
+ // try to lease a "used" resource.
+ return null;
+ }
+
+ $new_resources = $this->removeUnacquirableResources(
+ $new_resources,
+ $lease);
+ if (!$new_resources) {
+ // If we make it here, we just built a resource but aren't allowed
+ // to acquire it. We expect this to happen if the resource prevents
+ // acquisition until it activates, which is common when a resource
+ // needs to perform setup steps.
+
+ // Explicitly yield and wait for activation, since we don't want to
+ // lease a "used" resource.
+
+ throw new PhabricatorWorkerYieldException(15);
+ }
+
+ $resource = $this->leaseAnyResource($lease, $new_resources);
+ if ($resource) {
+ return $resource;
+ }
+
+ // We may not be able to lease a resource even if we just built it:
+ // another process may snatch it up before we can lease it. This should
+ // be rare, but is not concerning. Just try to build another resource.
+
+ // We likely could try to build the next resource immediately, but err on
+ // the side of caution and yield for now, at least until this code is
+ // better vetted.
+
+ throw new PhabricatorWorkerYieldException(15);
+ }
+
+ private function partitionResources(
+ DrydockLease $lease,
+ array $resources) {
+
+ assert_instances_of($resources, 'DrydockResource');
+ $viewer = $this->getViewer();
+
+ $lease_statuses = array(
+ DrydockLeaseStatus::STATUS_PENDING,
+ DrydockLeaseStatus::STATUS_ACQUIRED,
+ DrydockLeaseStatus::STATUS_ACTIVE,
+ );
+
+ // Partition resources into "free" resources (which we can try to lease
+ // immediately) and "used" resources, which we can only to lease after we
+ // fail to allocate a new resource.
+
+ // "Free" resources are unleased and/or prefer reuse over allocation.
+ // "Used" resources are leased and prefer allocation over reuse.
+
+ $free_resources = array();
+ $used_resources = array();
+
+ foreach ($resources as $resource) {
+ $blueprint = $resource->getBlueprint();
+
+ if (!$blueprint->shouldAllocateSupplementalResource($resource, $lease)) {
+ $free_resources[] = $resource;
+ continue;
+ }
+
+ $leases = id(new DrydockLeaseQuery())
+ ->setViewer($viewer)
+ ->withResourcePHIDs(array($resource->getPHID()))
+ ->withStatuses($lease_statuses)
+ ->setLimit(1)
+ ->execute();
+ if (!$leases) {
+ $free_resources[] = $resource;
+ continue;
}
- if (!$resources) {
- // If one or more blueprints claimed that they would be able to
- // allocate resources but none are actually able to allocate resources,
- // log the failure and yield so we try again soon.
+ $used_resources[] = $resource;
+ }
- // This can happen if some unexpected issue occurs during allocation
- // (for example, a call to build a VM fails for some reason) or if we
- // raced another allocator and the blueprint is now full.
+ return array($free_resources, $used_resources);
+ }
- $ex = new PhutilAggregateException(
- pht(
- 'All blueprints failed to allocate a suitable new resource when '.
- 'trying to allocate lease ("%s").',
- $lease->getPHID()),
- $exceptions);
+ private function newResources(
+ DrydockLease $lease,
+ array $blueprints) {
+ assert_instances_of($blueprints, 'DrydockBlueprint');
- $lease->logEvent(
- DrydockLeaseAllocationFailureLogType::LOGCONST,
+ $resources = array();
+ $exceptions = array();
+ foreach ($blueprints as $blueprint) {
+ $caught = null;
+ try {
+ $resources[] = $this->allocateResource($blueprint, $lease);
+
+ // Bail after allocating one resource, we don't need any more than
+ // this.
+ break;
+ } catch (Exception $ex) {
+ $caught = $ex;
+ } catch (Throwable $ex) {
+ $caught = $ex;
+ }
+
+ if ($caught) {
+ // This failure is not normally expected, so log it. It can be
+ // caused by something mundane and recoverable, however (see below
+ // for discussion).
+
+ // We log to the blueprint separately from the log to the lease:
+ // the lease is not attached to a blueprint yet so the lease log
+ // will not show up on the blueprint; more than one blueprint may
+ // fail; and the lease is not really impacted (and won't log) if at
+ // least one blueprint actually works.
+
+ $blueprint->logEvent(
+ DrydockResourceAllocationFailureLogType::LOGCONST,
array(
- 'class' => get_class($ex),
- 'message' => $ex->getMessage(),
+ 'class' => get_class($caught),
+ 'message' => $caught->getMessage(),
));
- throw new PhabricatorWorkerYieldException(15);
+ $exceptions[] = $caught;
}
+ }
+
+ if (!$resources) {
+ // If one or more blueprints claimed that they would be able to allocate
+ // resources but none are actually able to allocate resources, log the
+ // failure and yield so we try again soon.
+
+ // This can happen if some unexpected issue occurs during allocation
+ // (for example, a call to build a VM fails for some reason) or if we
+ // raced another allocator and the blueprint is now full.
+
+ $ex = new PhutilAggregateException(
+ pht(
+ 'All blueprints failed to allocate a suitable new resource when '.
+ 'trying to allocate lease ("%s").',
+ $lease->getPHID()),
+ $exceptions);
+
+ $lease->logEvent(
+ DrydockLeaseAllocationFailureLogType::LOGCONST,
+ array(
+ 'class' => get_class($ex),
+ 'message' => $ex->getMessage(),
+ ));
+
+ return null;
+ }
+
+ return $resources;
+ }
- $resources = $this->removeUnacquirableResources($resources, $lease);
- if (!$resources) {
- // If we make it here, we just built a resource but aren't allowed
- // to acquire it. We expect this during routine operation if the
- // resource prevents acquisition until it activates. Yield and wait
- // for activation.
- throw new PhabricatorWorkerYieldException(15);
- }
- // NOTE: We have not acquired the lease yet, so it is possible that the
- // resource we just built will be snatched up by some other lease before
- // we can acquire it. This is not problematic: we'll retry a little later
- // and should succeed eventually.
+ private function leaseAnyResource(
+ DrydockLease $lease,
+ array $resources) {
+ assert_instances_of($resources, 'DrydockResource');
+
+ if (!$resources) {
+ return null;
}
$resources = $this->rankResources($resources, $lease);
$exceptions = array();
$yields = array();
- $allocated = false;
+
+ $allocated = null;
foreach ($resources as $resource) {
try {
- $resource = $this->newResourceForAcquisition($resource, $lease);
$this->acquireLease($resource, $lease);
- $allocated = true;
+ $allocated = $resource;
break;
} catch (DrydockResourceLockException $ex) {
// We need to lock the resource to actually acquire it. If we aren't
// able to acquire the lock quickly enough, we can yield and try again
// later.
$yields[] = $ex;
+ } catch (DrydockSlotLockException $ex) {
+ // This also just indicates we ran into some kind of contention,
+ // probably from another lease. Just yield.
+ $yields[] = $ex;
} catch (DrydockAcquiredBrokenResourceException $ex) {
// If a resource was reclaimed or destroyed by the time we actually
- // got around to acquiring it, we just got unlucky. We can yield and
- // try again later.
+ // got around to acquiring it, we just got unlucky.
$yields[] = $ex;
} catch (PhabricatorWorkerYieldException $ex) {
// We can be told to yield, particularly by the supplemental allocator
// trying to give us a supplemental resource.
$yields[] = $ex;
} catch (Exception $ex) {
$exceptions[] = $ex;
}
}
- if (!$allocated) {
- if ($yields) {
- throw new PhabricatorWorkerYieldException(15);
- } else {
- throw new PhutilAggregateException(
- pht(
- 'Unable to acquire lease "%s" on any resource.',
- $lease->getPHID()),
- $exceptions);
- }
+ if ($allocated) {
+ return $allocated;
}
- }
-
-
- /**
- * Get all the @{class:DrydockBlueprintImplementation}s which can possibly
- * build a resource to satisfy a lease.
- *
- * This method returns blueprints which might, at some time, be able to
- * build a resource which can satisfy the lease. They may not be able to
- * build that resource right now.
- *
- * @param DrydockLease Requested lease.
- * @return list<DrydockBlueprintImplementation> List of qualifying blueprint
- * implementations.
- * @task allocator
- */
- private function loadBlueprintImplementationsForAllocatingLease(
- DrydockLease $lease) {
- $impls = DrydockBlueprintImplementation::getAllBlueprintImplementations();
-
- $keep = array();
- foreach ($impls as $key => $impl) {
- // Don't use disabled blueprint types.
- if (!$impl->isEnabled()) {
- continue;
- }
-
- // Don't use blueprint types which can't allocate the correct kind of
- // resource.
- if ($impl->getType() != $lease->getResourceType()) {
- continue;
- }
-
- if (!$impl->canAnyBlueprintEverAllocateResourceForLease($lease)) {
- continue;
- }
-
- $keep[$key] = $impl;
+ if ($yields) {
+ throw new PhabricatorWorkerYieldException(15);
}
- return $keep;
+ throw new PhutilAggregateException(
+ pht(
+ 'Unable to acquire lease "%s" on any resource.',
+ $lease->getPHID()),
+ $exceptions);
}
/**
* Get all the concrete @{class:DrydockBlueprint}s which can possibly
* build a resource to satisfy a lease.
*
* @param DrydockLease Requested lease.
* @return list<DrydockBlueprint> List of qualifying blueprints.
* @task allocator
*/
private function loadBlueprintsForAllocatingLease(
DrydockLease $lease) {
$viewer = $this->getViewer();
- $impls = $this->loadBlueprintImplementationsForAllocatingLease($lease);
+ $impls = DrydockBlueprintImplementation::getAllForAllocatingLease($lease);
if (!$impls) {
return array();
}
$blueprint_phids = $lease->getAllowedBlueprintPHIDs();
if (!$blueprint_phids) {
$lease->logEvent(DrydockLeaseNoBlueprintsLogType::LOGCONST);
return array();
}
$query = id(new DrydockBlueprintQuery())
->setViewer($viewer)
->withPHIDs($blueprint_phids)
->withBlueprintClasses(array_keys($impls))
->withDisabled(false);
// The Drydock application itself is allowed to authorize anything. This
// is primarily used for leases generated by CLI administrative tools.
$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
$authorizing_phid = $lease->getAuthorizingPHID();
if ($authorizing_phid != $drydock_phid) {
$blueprints = id(clone $query)
->withAuthorizedPHIDs(array($authorizing_phid))
->execute();
if (!$blueprints) {
// If we didn't hit any blueprints, check if this is an authorization
// problem: re-execute the query without the authorization constraint.
// If the second query hits blueprints, the overall configuration is
// fine but this is an authorization problem. If the second query also
// comes up blank, this is some other kind of configuration issue so
// we fall through to the default pathway.
$all_blueprints = $query->execute();
if ($all_blueprints) {
$lease->logEvent(
DrydockLeaseNoAuthorizationsLogType::LOGCONST,
array(
'authorizingPHID' => $authorizing_phid,
));
return array();
}
}
} else {
$blueprints = $query->execute();
}
$keep = array();
foreach ($blueprints as $key => $blueprint) {
if (!$blueprint->canEverAllocateResourceForLease($lease)) {
continue;
}
$keep[$key] = $blueprint;
}
return $keep;
}
/**
* Load a list of all resources which a given lease can possibly be
* allocated against.
*
* @param list<DrydockBlueprint> Blueprints which may produce suitable
* resources.
* @param DrydockLease Requested lease.
* @return list<DrydockResource> Resources which may be able to allocate
* the lease.
* @task allocator
*/
- private function loadResourcesForAllocatingLease(
+ private function loadAcquirableResourcesForLease(
array $blueprints,
DrydockLease $lease) {
assert_instances_of($blueprints, 'DrydockBlueprint');
$viewer = $this->getViewer();
$resources = id(new DrydockResourceQuery())
->setViewer($viewer)
->withBlueprintPHIDs(mpull($blueprints, 'getPHID'))
->withTypes(array($lease->getResourceType()))
->withStatuses(
array(
- DrydockResourceStatus::STATUS_PENDING,
DrydockResourceStatus::STATUS_ACTIVE,
))
->execute();
return $this->removeUnacquirableResources($resources, $lease);
}
/**
* Remove resources which can not be acquired by a given lease from a list.
*
* @param list<DrydockResource> Candidate resources.
* @param DrydockLease Acquiring lease.
* @return list<DrydockResource> Resources which the lease may be able to
* acquire.
* @task allocator
*/
private function removeUnacquirableResources(
array $resources,
DrydockLease $lease) {
$keep = array();
foreach ($resources as $key => $resource) {
$blueprint = $resource->getBlueprint();
if (!$blueprint->canAcquireLeaseOnResource($resource, $lease)) {
continue;
}
$keep[$key] = $resource;
}
return $keep;
}
/**
* Remove blueprints which are too heavily allocated to build a resource for
* a lease from a list of blueprints.
*
* @param list<DrydockBlueprint> List of blueprints.
* @return list<DrydockBlueprint> List with blueprints that can not allocate
* a resource for the lease right now removed.
* @task allocator
*/
private function removeOverallocatedBlueprints(
array $blueprints,
DrydockLease $lease) {
assert_instances_of($blueprints, 'DrydockBlueprint');
$keep = array();
foreach ($blueprints as $key => $blueprint) {
if (!$blueprint->canAllocateResourceForLease($lease)) {
continue;
}
$keep[$key] = $blueprint;
}
return $keep;
}
/**
* Rank blueprints by suitability for building a new resource for a
* particular lease.
*
* @param list<DrydockBlueprint> List of blueprints.
* @param DrydockLease Requested lease.
* @return list<DrydockBlueprint> Ranked list of blueprints.
* @task allocator
*/
private function rankBlueprints(array $blueprints, DrydockLease $lease) {
assert_instances_of($blueprints, 'DrydockBlueprint');
// TODO: Implement improvements to this ranking algorithm if they become
// available.
shuffle($blueprints);
return $blueprints;
}
/**
* Rank resources by suitability for allocating a particular lease.
*
* @param list<DrydockResource> List of resources.
* @param DrydockLease Requested lease.
* @return list<DrydockResource> Ranked list of resources.
* @task allocator
*/
private function rankResources(array $resources, DrydockLease $lease) {
assert_instances_of($resources, 'DrydockResource');
// TODO: Implement improvements to this ranking algorithm if they become
// available.
shuffle($resources);
return $resources;
}
/**
* Perform an actual resource allocation with a particular blueprint.
*
* @param DrydockBlueprint The blueprint to allocate a resource from.
* @param DrydockLease Requested lease.
* @return DrydockResource Allocated resource.
* @task allocator
*/
private function allocateResource(
DrydockBlueprint $blueprint,
DrydockLease $lease) {
$resource = $blueprint->allocateResource($lease);
$this->validateAllocatedResource($blueprint, $resource, $lease);
// If this resource was allocated as a pending resource, queue a task to
// activate it.
if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) {
+
+ $lease->addAllocatedResourcePHIDs(
+ array(
+ $resource->getPHID(),
+ ));
+ $lease->save();
+
PhabricatorWorker::scheduleTask(
'DrydockResourceUpdateWorker',
array(
'resourcePHID' => $resource->getPHID(),
// This task will generally yield while the resource activates, so
// wake it back up once the resource comes online. Most of the time,
// we'll be able to lease the newly activated resource.
'awakenOnActivation' => array(
$this->getCurrentWorkerTaskID(),
),
),
array(
'objectPHID' => $resource->getPHID(),
));
}
return $resource;
}
/**
* Check that the resource a blueprint allocated is roughly the sort of
* object we expect.
*
* @param DrydockBlueprint Blueprint which built the resource.
* @param wild Thing which the blueprint claims is a valid resource.
* @param DrydockLease Lease the resource was allocated for.
* @return void
* @task allocator
*/
private function validateAllocatedResource(
DrydockBlueprint $blueprint,
$resource,
DrydockLease $lease) {
if (!($resource instanceof DrydockResource)) {
throw new Exception(
pht(
'Blueprint "%s" (of type "%s") is not properly implemented: %s must '.
'return an object of type %s or throw, but returned something else.',
$blueprint->getBlueprintName(),
$blueprint->getClassName(),
'allocateResource()',
'DrydockResource'));
}
if (!$resource->isAllocatedResource()) {
throw new Exception(
pht(
'Blueprint "%s" (of type "%s") is not properly implemented: %s '.
'must actually allocate the resource it returns.',
$blueprint->getBlueprintName(),
$blueprint->getClassName(),
'allocateResource()'));
}
$resource_type = $resource->getType();
$lease_type = $lease->getResourceType();
if ($resource_type !== $lease_type) {
throw new Exception(
pht(
'Blueprint "%s" (of type "%s") is not properly implemented: it '.
'built a resource of type "%s" to satisfy a lease requesting a '.
'resource of type "%s".',
$blueprint->getBlueprintName(),
$blueprint->getClassName(),
$resource_type,
$lease_type));
}
}
private function reclaimResources(
DrydockBlueprint $blueprint,
DrydockLease $lease) {
$viewer = $this->getViewer();
- // If this lease is marked as already in the process of reclaiming a
- // resource, don't let it reclaim another one until the first reclaim
- // completes. This stops one lease from reclaiming a large number of
- // resources if the reclaims take a while to complete.
- $reclaiming_phid = $lease->getAttribute('drydock.reclaimingPHID');
- if ($reclaiming_phid) {
- $reclaiming_resource = id(new DrydockResourceQuery())
- ->setViewer($viewer)
- ->withPHIDs(array($reclaiming_phid))
- ->withStatuses(
- array(
- DrydockResourceStatus::STATUS_ACTIVE,
- DrydockResourceStatus::STATUS_RELEASED,
- ))
- ->executeOne();
- if ($reclaiming_resource) {
- return null;
- }
- }
-
$resources = id(new DrydockResourceQuery())
->setViewer($viewer)
->withBlueprintPHIDs(array($blueprint->getPHID()))
->withStatuses(
array(
DrydockResourceStatus::STATUS_ACTIVE,
))
->execute();
// TODO: We could be much smarter about this and try to release long-unused
// resources, resources with many similar copies, old resources, resources
// that are cheap to rebuild, etc.
shuffle($resources);
foreach ($resources as $resource) {
if ($this->canReclaimResource($resource)) {
$this->reclaimResource($resource, $lease);
return $resource;
}
}
return null;
}
/* -( Acquiring Leases )--------------------------------------------------- */
/**
* Perform an actual lease acquisition on a particular resource.
*
* @param DrydockResource Resource to acquire a lease on.
* @param DrydockLease Lease to acquire.
* @return void
* @task acquire
*/
private function acquireLease(
DrydockResource $resource,
DrydockLease $lease) {
$blueprint = $resource->getBlueprint();
$blueprint->acquireLease($resource, $lease);
$this->validateAcquiredLease($blueprint, $resource, $lease);
// If this lease has been acquired but not activated, queue a task to
// activate it.
if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACQUIRED) {
$this->queueTask(
__CLASS__,
array(
'leasePHID' => $lease->getPHID(),
),
array(
'objectPHID' => $lease->getPHID(),
));
}
}
/**
* Make sure that a lease was really acquired properly.
*
* @param DrydockBlueprint Blueprint which created the resource.
* @param DrydockResource Resource which was acquired.
* @param DrydockLease The lease which was supposedly acquired.
* @return void
* @task acquire
*/
private function validateAcquiredLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
if (!$lease->isAcquiredLease()) {
throw new Exception(
pht(
'Blueprint "%s" (of type "%s") is not properly implemented: it '.
'returned from "%s" without acquiring a lease.',
$blueprint->getBlueprintName(),
$blueprint->getClassName(),
'acquireLease()'));
}
$lease_phid = $lease->getResourcePHID();
$resource_phid = $resource->getPHID();
if ($lease_phid !== $resource_phid) {
throw new Exception(
pht(
'Blueprint "%s" (of type "%s") is not properly implemented: it '.
'returned from "%s" with a lease acquired on the wrong resource.',
$blueprint->getBlueprintName(),
$blueprint->getClassName(),
'acquireLease()'));
}
}
- private function newResourceForAcquisition(
- DrydockResource $resource,
- DrydockLease $lease) {
-
- // If the resource has no leases against it, never build a new one. This is
- // likely already a new resource that just activated.
- $viewer = $this->getViewer();
-
- $statuses = array(
- DrydockLeaseStatus::STATUS_PENDING,
- DrydockLeaseStatus::STATUS_ACQUIRED,
- DrydockLeaseStatus::STATUS_ACTIVE,
- );
-
- $leases = id(new DrydockLeaseQuery())
- ->setViewer($viewer)
- ->withResourcePHIDs(array($resource->getPHID()))
- ->withStatuses($statuses)
- ->setLimit(1)
- ->execute();
- if (!$leases) {
- return $resource;
- }
-
- // If we're about to get a lease on a resource, check if the blueprint
- // wants to allocate a supplemental resource. If it does, try to perform a
- // new allocation instead.
- $blueprint = $resource->getBlueprint();
- if (!$blueprint->shouldAllocateSupplementalResource($resource, $lease)) {
- return $resource;
- }
-
- // If the blueprint is already overallocated, we can't allocate a new
- // resource. Just return the existing resource.
- $remaining = $this->removeOverallocatedBlueprints(
- array($blueprint),
- $lease);
- if (!$remaining) {
- return $resource;
- }
-
- // Try to build a new resource.
- try {
- $new_resource = $this->allocateResource($blueprint, $lease);
- } catch (Exception $ex) {
- $blueprint->logEvent(
- DrydockResourceAllocationFailureLogType::LOGCONST,
- array(
- 'class' => get_class($ex),
- 'message' => $ex->getMessage(),
- ));
-
- return $resource;
- }
-
- // If we can't actually acquire the new resource yet, just yield.
- // (We could try to move forward with the original resource instead.)
- $acquirable = $this->removeUnacquirableResources(
- array($new_resource),
- $lease);
- if (!$acquirable) {
- throw new PhabricatorWorkerYieldException(15);
- }
-
- return $new_resource;
- }
-
/* -( Activating Leases )-------------------------------------------------- */
/**
* @task activate
*/
private function activateLease(DrydockLease $lease) {
$resource = $lease->getResource();
if (!$resource) {
throw new Exception(
pht('Trying to activate lease with no resource.'));
}
$resource_status = $resource->getStatus();
if ($resource_status == DrydockResourceStatus::STATUS_PENDING) {
throw new PhabricatorWorkerYieldException(15);
}
if ($resource_status != DrydockResourceStatus::STATUS_ACTIVE) {
throw new DrydockAcquiredBrokenResourceException(
pht(
'Trying to activate lease ("%s") on a resource ("%s") in '.
'the wrong status ("%s").',
$lease->getPHID(),
$resource->getPHID(),
$resource_status));
}
// NOTE: We can race resource destruction here. Between the time we
// performed the read above and now, the resource might have closed, so
// we may activate leases on dead resources. At least for now, this seems
// fine: a resource dying right before we activate a lease on it should not
// be distinguishable from a resource dying right after we activate a lease
// on it. We end up with an active lease on a dead resource either way, and
// can not prevent resources dying from lightning strikes.
$blueprint = $resource->getBlueprint();
$blueprint->activateLease($resource, $lease);
$this->validateActivatedLease($blueprint, $resource, $lease);
}
/**
* @task activate
*/
private function validateActivatedLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
if (!$lease->isActivatedLease()) {
throw new Exception(
pht(
'Blueprint "%s" (of type "%s") is not properly implemented: it '.
'returned from "%s" without activating a lease.',
$blueprint->getBlueprintName(),
$blueprint->getClassName(),
'acquireLease()'));
}
}
/* -( Releasing Leases )--------------------------------------------------- */
/**
* @task release
*/
private function releaseLease(DrydockLease $lease) {
$lease
->setStatus(DrydockLeaseStatus::STATUS_RELEASED)
->save();
$lease->logEvent(DrydockLeaseReleasedLogType::LOGCONST);
$resource = $lease->getResource();
if ($resource) {
$blueprint = $resource->getBlueprint();
$blueprint->didReleaseLease($resource, $lease);
}
$this->destroyLease($lease);
}
/* -( Breaking Leases )---------------------------------------------------- */
/**
* @task break
*/
protected function breakLease(DrydockLease $lease, Exception $ex) {
switch ($lease->getStatus()) {
case DrydockLeaseStatus::STATUS_BROKEN:
case DrydockLeaseStatus::STATUS_RELEASED:
case DrydockLeaseStatus::STATUS_DESTROYED:
throw new PhutilProxyException(
pht(
'Unexpected failure while destroying lease ("%s").',
$lease->getPHID()),
$ex);
}
$lease
->setStatus(DrydockLeaseStatus::STATUS_BROKEN)
->save();
$lease->logEvent(
DrydockLeaseActivationFailureLogType::LOGCONST,
array(
'class' => get_class($ex),
'message' => $ex->getMessage(),
));
$lease->awakenTasks();
$this->queueTask(
__CLASS__,
array(
'leasePHID' => $lease->getPHID(),
),
array(
'objectPHID' => $lease->getPHID(),
));
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Permanent failure while activating lease ("%s"): %s',
$lease->getPHID(),
$ex->getMessage()));
}
/* -( Destroying Leases )-------------------------------------------------- */
/**
* @task destroy
*/
private function destroyLease(DrydockLease $lease) {
$resource = $lease->getResource();
if ($resource) {
$blueprint = $resource->getBlueprint();
$blueprint->destroyLease($resource, $lease);
}
DrydockSlotLock::releaseLocks($lease->getPHID());
$lease
->setStatus(DrydockLeaseStatus::STATUS_DESTROYED)
->save();
$lease->logEvent(DrydockLeaseDestroyedLogType::LOGCONST);
$lease->awakenTasks();
}
}
diff --git a/src/applications/drydock/worker/DrydockWorker.php b/src/applications/drydock/worker/DrydockWorker.php
index fcb6876cfe..457624ec5d 100644
--- a/src/applications/drydock/worker/DrydockWorker.php
+++ b/src/applications/drydock/worker/DrydockWorker.php
@@ -1,269 +1,285 @@
<?php
abstract class DrydockWorker extends PhabricatorWorker {
protected function getViewer() {
return PhabricatorUser::getOmnipotentUser();
}
protected function loadLease($lease_phid) {
$viewer = $this->getViewer();
$lease = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withPHIDs(array($lease_phid))
->executeOne();
if (!$lease) {
throw new PhabricatorWorkerPermanentFailureException(
pht('No such lease "%s"!', $lease_phid));
}
return $lease;
}
protected function loadResource($resource_phid) {
$viewer = $this->getViewer();
$resource = id(new DrydockResourceQuery())
->setViewer($viewer)
->withPHIDs(array($resource_phid))
->executeOne();
if (!$resource) {
throw new PhabricatorWorkerPermanentFailureException(
pht('No such resource "%s"!', $resource_phid));
}
return $resource;
}
protected function loadOperation($operation_phid) {
$viewer = $this->getViewer();
$operation = id(new DrydockRepositoryOperationQuery())
->setViewer($viewer)
->withPHIDs(array($operation_phid))
->executeOne();
if (!$operation) {
throw new PhabricatorWorkerPermanentFailureException(
pht('No such operation "%s"!', $operation_phid));
}
return $operation;
}
protected function loadCommands($target_phid) {
$viewer = $this->getViewer();
$commands = id(new DrydockCommandQuery())
->setViewer($viewer)
->withTargetPHIDs(array($target_phid))
->withConsumed(false)
->execute();
$commands = msort($commands, 'getID');
return $commands;
}
protected function checkLeaseExpiration(DrydockLease $lease) {
$this->checkObjectExpiration($lease);
}
protected function checkResourceExpiration(DrydockResource $resource) {
$this->checkObjectExpiration($resource);
}
private function checkObjectExpiration($object) {
// Check if the resource or lease has expired. If it has, we're going to
// send it a release command.
// This command is sent from within the update worker so it is handled
// immediately, but doing this generates a log and improves consistency.
$expires = $object->getUntil();
if (!$expires) {
return;
}
$now = PhabricatorTime::getNow();
if ($expires > $now) {
return;
}
$viewer = $this->getViewer();
$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
$command = DrydockCommand::initializeNewCommand($viewer)
->setTargetPHID($object->getPHID())
->setAuthorPHID($drydock_phid)
->setCommand(DrydockCommand::COMMAND_RELEASE)
->save();
}
protected function yieldIfExpiringLease(DrydockLease $lease) {
if (!$lease->canReceiveCommands()) {
return;
}
$this->yieldIfExpiring($lease->getUntil());
}
protected function yieldIfExpiringResource(DrydockResource $resource) {
if (!$resource->canReceiveCommands()) {
return;
}
$this->yieldIfExpiring($resource->getUntil());
}
private function yieldIfExpiring($expires) {
if (!$expires) {
return;
}
if (!$this->getTaskDataValue('isExpireTask')) {
return;
}
$now = PhabricatorTime::getNow();
throw new PhabricatorWorkerYieldException($expires - $now);
}
protected function isTemporaryException(Exception $ex) {
if ($ex instanceof PhabricatorWorkerYieldException) {
return true;
}
if ($ex instanceof DrydockSlotLockException) {
return true;
}
if ($ex instanceof PhutilAggregateException) {
$any_temporary = false;
foreach ($ex->getExceptions() as $sub) {
if ($this->isTemporaryException($sub)) {
$any_temporary = true;
break;
}
}
if ($any_temporary) {
return true;
}
}
if ($ex instanceof PhutilProxyException) {
return $this->isTemporaryException($ex->getPreviousException());
}
return false;
}
protected function getYieldDurationFromException(Exception $ex) {
if ($ex instanceof PhabricatorWorkerYieldException) {
return $ex->getDuration();
}
if ($ex instanceof DrydockSlotLockException) {
return 5;
}
return 15;
}
protected function flushDrydockTaskQueue() {
// NOTE: By default, queued tasks are not scheduled if the current task
// fails. This is a good, safe default behavior. For example, it can
// protect us from executing side effect tasks too many times, like
// sending extra email.
// However, it is not the behavior we want in Drydock, because we queue
// followup tasks after lease and resource failures and want them to
// execute in order to clean things up.
// At least for now, we just explicitly flush the queue before exiting
// with a failure to make sure tasks get queued up properly.
try {
$this->flushTaskQueue();
} catch (Exception $ex) {
// If this fails, we want to swallow the exception so the caller throws
// the original error, since we're more likely to be able to understand
// and fix the problem if we have the original error than if we replace
// it with this one.
phlog($ex);
}
return $this;
}
protected function canReclaimResource(DrydockResource $resource) {
$viewer = $this->getViewer();
// Don't reclaim a resource if it has been updated recently. If two
// leases are fighting, we don't want them to keep reclaiming resources
// from one another forever without making progress, so make resources
// immune to reclamation for a little while after they activate or update.
+ $now = PhabricatorTime::getNow();
+ $max_epoch = ($now - phutil_units('3 minutes in seconds'));
+
// TODO: It would be nice to use a more narrow time here, like "last
// activation or lease release", but we don't currently store that
// anywhere.
$updated = $resource->getDateModified();
- $now = PhabricatorTime::getNow();
- $ago = ($now - $updated);
- if ($ago < phutil_units('3 minutes in seconds')) {
+ if ($updated > $max_epoch) {
return false;
}
$statuses = array(
DrydockLeaseStatus::STATUS_PENDING,
DrydockLeaseStatus::STATUS_ACQUIRED,
DrydockLeaseStatus::STATUS_ACTIVE,
DrydockLeaseStatus::STATUS_RELEASED,
DrydockLeaseStatus::STATUS_BROKEN,
);
// Don't reclaim resources that have any active leases.
$leases = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withResourcePHIDs(array($resource->getPHID()))
->withStatuses($statuses)
->setLimit(1)
->execute();
if ($leases) {
return false;
}
+ // See T13676. Don't reclaim a resource if a lease recently released.
+ $leases = id(new DrydockLeaseQuery())
+ ->setViewer($viewer)
+ ->withResourcePHIDs(array($resource->getPHID()))
+ ->withStatuses(
+ array(
+ DrydockLeaseStatus::STATUS_DESTROYED,
+ ))
+ ->withDateModifiedBetween($max_epoch, null)
+ ->setLimit(1)
+ ->execute();
+ if ($leases) {
+ return false;
+ }
+
return true;
}
protected function reclaimResource(
DrydockResource $resource,
DrydockLease $lease) {
$viewer = $this->getViewer();
// Mark the lease as reclaiming this resource. It won't be allowed to start
// another reclaim as long as this resource is still in the process of
// being reclaimed.
- $lease->setAttribute('drydock.reclaimingPHID', $resource->getPHID());
+ $lease->addReclaimedResourcePHIDs(array($resource->getPHID()));
// When the resource releases, we we want to reawaken this task since it
// should (usually) be able to start building a new resource right away.
$worker_task_id = $this->getCurrentWorkerTaskID();
$command = DrydockCommand::initializeNewCommand($viewer)
->setTargetPHID($resource->getPHID())
->setAuthorPHID($lease->getPHID())
->setCommand(DrydockCommand::COMMAND_RECLAIM)
->setProperty('awakenTaskIDs', array($worker_task_id));
$lease->openTransaction();
$lease->save();
$command->save();
$lease->saveTransaction();
$resource->scheduleUpdate();
return $this;
}
}
diff --git a/src/applications/feed/conduit/FeedPublishConduitAPIMethod.php b/src/applications/feed/conduit/FeedPublishConduitAPIMethod.php
deleted file mode 100644
index 5f83e73b3d..0000000000
--- a/src/applications/feed/conduit/FeedPublishConduitAPIMethod.php
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-
-final class FeedPublishConduitAPIMethod extends FeedConduitAPIMethod {
-
- public function getAPIMethodName() {
- return 'feed.publish';
- }
-
- public function getMethodStatus() {
- return self::METHOD_STATUS_UNSTABLE;
- }
-
- public function getMethodDescription() {
- return pht('Publish a story to the feed.');
- }
-
- protected function defineParamTypes() {
- return array(
- 'type' => 'required string',
- 'data' => 'required dict',
- 'time' => 'optional int',
- );
- }
-
- protected function defineReturnType() {
- return 'nonempty phid';
- }
-
- protected function execute(ConduitAPIRequest $request) {
- $type = $request->getValue('type');
- $data = $request->getValue('data');
- $time = $request->getValue('time');
-
- $author_phid = $request->getUser()->getPHID();
- $phids = array($author_phid);
-
- $publisher = new PhabricatorFeedStoryPublisher();
- $publisher->setStoryType($type);
- $publisher->setStoryData($data);
- $publisher->setStoryTime($time);
- $publisher->setRelatedPHIDs($phids);
- $publisher->setStoryAuthorPHID($author_phid);
-
- $data = $publisher->publish();
-
- return $data->getPHID();
- }
-
-}
diff --git a/src/applications/feed/config/PhabricatorFeedConfigOptions.php b/src/applications/feed/config/PhabricatorFeedConfigOptions.php
index 29c5a9549b..eac6a097ae 100644
--- a/src/applications/feed/config/PhabricatorFeedConfigOptions.php
+++ b/src/applications/feed/config/PhabricatorFeedConfigOptions.php
@@ -1,42 +1,42 @@
<?php
final class PhabricatorFeedConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Feed');
}
public function getDescription() {
return pht('Feed options.');
}
public function getIcon() {
return 'fa-newspaper-o';
}
public function getGroup() {
return 'apps';
}
public function getOptions() {
$hooks_help = $this->deformat(pht(<<<EODOC
IMPORTANT: Feed hooks are deprecated and have been replaced by Webhooks.
You can configure Webhooks in Herald. This configuration option will be removed
-in a future version of Phabricator.
+in a future version of the software.
(This legacy option may be configured with a list of URIs; feed stories will
send to these URIs.)
EODOC
));
return array(
$this->newOption('feed.http-hooks', 'list<string>', array())
->setLocked(true)
->setSummary(pht('Deprecated.'))
->setDescription($hooks_help),
);
}
}
diff --git a/src/applications/files/application/PhabricatorFilesApplication.php b/src/applications/files/application/PhabricatorFilesApplication.php
index 2e4c48f0d6..e246d4c90c 100644
--- a/src/applications/files/application/PhabricatorFilesApplication.php
+++ b/src/applications/files/application/PhabricatorFilesApplication.php
@@ -1,145 +1,155 @@
<?php
final class PhabricatorFilesApplication extends PhabricatorApplication {
public function getBaseURI() {
return '/file/';
}
public function getName() {
return pht('Files');
}
public function getShortDescription() {
return pht('Store and Share Files');
}
public function getIcon() {
return 'fa-file';
}
public function getTitleGlyph() {
return "\xE2\x87\xAA";
}
public function getFlavorText() {
return pht('Blob store for Pokemon pictures.');
}
public function getApplicationGroup() {
return self::GROUP_UTILITIES;
}
public function canUninstall() {
return false;
}
public function getRemarkupRules() {
return array(
new PhabricatorEmbedFileRemarkupRule(),
new PhabricatorImageRemarkupRule(),
);
}
public function supportsEmailIntegration() {
return true;
}
public function getAppEmailBlurb() {
return pht(
'Send emails with file attachments to these addresses to upload '.
'files. %s',
phutil_tag(
'a',
array(
'href' => $this->getInboundEmailSupportLink(),
),
pht('Learn More')));
}
protected function getCustomCapabilities() {
return array(
FilesDefaultViewCapability::CAPABILITY => array(
'caption' => pht('Default view policy for newly created files.'),
'template' => PhabricatorFileFilePHIDType::TYPECONST,
'capability' => PhabricatorPolicyCapability::CAN_VIEW,
),
);
}
public function getRoutes() {
return array(
'/F(?P<id>[1-9]\d*)(?:\$(?P<lines>\d+(?:-\d+)?))?'
=> 'PhabricatorFileViewController',
'/file/' => array(
'(query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorFileListController',
'view/(?P<id>[1-9]\d*)/'.
'(?:(?P<engineKey>[^/]+)/)?'.
'(?:\$(?P<lines>\d+(?:-\d+)?))?'
=> 'PhabricatorFileViewController',
'info/(?P<phid>[^/]+)/' => 'PhabricatorFileViewController',
'upload/' => 'PhabricatorFileUploadController',
'dropupload/' => 'PhabricatorFileDropUploadController',
'compose/' => 'PhabricatorFileComposeController',
'thread/(?P<phid>[^/]+)/' => 'PhabricatorFileLightboxController',
'delete/(?P<id>[1-9]\d*)/' => 'PhabricatorFileDeleteController',
$this->getEditRoutePattern('edit/')
=> 'PhabricatorFileEditController',
'imageproxy/' => 'PhabricatorFileImageProxyController',
'transforms/(?P<id>[1-9]\d*)/' =>
'PhabricatorFileTransformListController',
'uploaddialog/(?P<single>single/)?'
=> 'PhabricatorFileUploadDialogController',
'iconset/(?P<key>[^/]+)/' => array(
'select/' => 'PhabricatorFileIconSetSelectController',
),
'document/(?P<engineKey>[^/]+)/(?P<phid>[^/]+)/'
=> 'PhabricatorFileDocumentController',
+ 'ui/' => array(
+ 'detach/(?P<objectPHID>[^/]+)/(?P<filePHID>[^/]+)/'
+ => 'PhabricatorFileDetachController',
+ 'curtain/' => array(
+ 'list/(?P<phid>[^/]+)/'
+ => 'PhabricatorFileUICurtainListController',
+ 'attach/(?P<objectPHID>[^/]+)/(?P<filePHID>[^/]+)/'
+ => 'PhabricatorFileUICurtainAttachController',
+ ),
+ ),
) + $this->getResourceSubroutes(),
);
}
public function getResourceRoutes() {
return array(
'/file/' => $this->getResourceSubroutes(),
);
}
private function getResourceSubroutes() {
return array(
'(?P<kind>data|download)/'.
'(?:@(?P<instance>[^/]+)/)?'.
'(?P<key>[^/]+)/'.
'(?P<phid>[^/]+)/'.
'(?:(?P<token>[^/]+)/)?'.
'.*'
=> 'PhabricatorFileDataController',
'xform/'.
'(?:@(?P<instance>[^/]+)/)?'.
'(?P<transform>[^/]+)/'.
'(?P<phid>[^/]+)/'.
'(?P<key>[^/]+)/'
=> 'PhabricatorFileTransformController',
);
}
public function getMailCommandObjects() {
return array(
'file' => array(
'name' => pht('Email Commands: Files'),
'header' => pht('Interacting with Files'),
'object' => new PhabricatorFile(),
'summary' => pht(
'This page documents the commands you can use to interact with '.
'files.'),
),
);
}
public function getQuicksandURIPatternBlacklist() {
return array(
'/file/(data|download)/.*',
);
}
}
diff --git a/src/applications/files/config/PhabricatorFilesConfigOptions.php b/src/applications/files/config/PhabricatorFilesConfigOptions.php
index 735ddfcb0d..7ed96a412b 100644
--- a/src/applications/files/config/PhabricatorFilesConfigOptions.php
+++ b/src/applications/files/config/PhabricatorFilesConfigOptions.php
@@ -1,217 +1,217 @@
<?php
final class PhabricatorFilesConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Files');
}
public function getDescription() {
return pht('Configure files and file storage.');
}
public function getIcon() {
return 'fa-file';
}
public function getGroup() {
return 'apps';
}
public function getOptions() {
$viewable_default = array(
'image/jpeg' => 'image/jpeg',
'image/jpg' => 'image/jpg',
'image/png' => 'image/png',
'image/gif' => 'image/gif',
'text/plain' => 'text/plain; charset=utf-8',
'text/x-diff' => 'text/plain; charset=utf-8',
// ".ico" favicon files, which have mime type diversity. See:
// http://en.wikipedia.org/wiki/ICO_(file_format)#MIME_type
'image/x-ico' => 'image/x-icon',
'image/x-icon' => 'image/x-icon',
'image/vnd.microsoft.icon' => 'image/x-icon',
// This is a generic type for both OGG video and OGG audio.
'application/ogg' => 'application/ogg',
'audio/x-wav' => 'audio/x-wav',
'audio/mpeg' => 'audio/mpeg',
'audio/ogg' => 'audio/ogg',
'video/mp4' => 'video/mp4',
'video/ogg' => 'video/ogg',
'video/webm' => 'video/webm',
'video/quicktime' => 'video/quicktime',
'application/pdf' => 'application/pdf',
);
$image_default = array(
'image/jpeg' => true,
'image/jpg' => true,
'image/png' => true,
'image/gif' => true,
'image/x-ico' => true,
'image/x-icon' => true,
'image/vnd.microsoft.icon' => true,
);
// The "application/ogg" type is listed as both an audio and video type,
// because it may contain either type of content.
$audio_default = array(
'audio/x-wav' => true,
'audio/mpeg' => true,
'audio/ogg' => true,
// These are video or ambiguous types, but can be forced to render as
// audio with `media=audio`, which seems to work properly in browsers.
// (For example, you can embed a music video as audio if you just want
// to set the mood for your task without distracting viewers.)
'video/mp4' => true,
'video/ogg' => true,
'video/quicktime' => true,
'application/ogg' => true,
);
$video_default = array(
'video/mp4' => true,
'video/ogg' => true,
'video/webm' => true,
'video/quicktime' => true,
'application/ogg' => true,
);
// largely lifted from http://en.wikipedia.org/wiki/Internet_media_type
$icon_default = array(
// audio file icon
'audio/basic' => 'fa-file-audio-o',
'audio/L24' => 'fa-file-audio-o',
'audio/mp4' => 'fa-file-audio-o',
'audio/mpeg' => 'fa-file-audio-o',
'audio/ogg' => 'fa-file-audio-o',
'audio/vorbis' => 'fa-file-audio-o',
'audio/vnd.rn-realaudio' => 'fa-file-audio-o',
'audio/vnd.wave' => 'fa-file-audio-o',
'audio/webm' => 'fa-file-audio-o',
// movie file icon
'video/mpeg' => 'fa-file-movie-o',
'video/mp4' => 'fa-file-movie-o',
'application/ogg' => 'fa-file-movie-o',
'video/ogg' => 'fa-file-movie-o',
'video/quicktime' => 'fa-file-movie-o',
'video/webm' => 'fa-file-movie-o',
'video/x-matroska' => 'fa-file-movie-o',
'video/x-ms-wmv' => 'fa-file-movie-o',
'video/x-flv' => 'fa-file-movie-o',
// pdf file icon
'application/pdf' => 'fa-file-pdf-o',
// zip file icon
'application/zip' => 'fa-file-zip-o',
// msword icon
'application/msword' => 'fa-file-word-o',
// msexcel
'application/vnd.ms-excel' => 'fa-file-excel-o',
// mspowerpoint
'application/vnd.ms-powerpoint' => 'fa-file-powerpoint-o',
) + array_fill_keys(array_keys($image_default), 'fa-file-image-o');
// NOTE: These options are locked primarily because adding "text/plain"
// as an image MIME type increases SSRF vulnerability by allowing users
// to load text files from remote servers as "images" (see T6755 for
// discussion).
return array(
$this->newOption('files.viewable-mime-types', 'wild', $viewable_default)
->setLocked(true)
->setSummary(
pht('Configure which MIME types are viewable in the browser.'))
->setDescription(
pht(
"Configure which uploaded file types may be viewed directly ".
"in the browser. Other file types will be downloaded instead ".
"of displayed. This is mainly a usability consideration, since ".
"browsers tend to freak out when viewing very large binary files.".
"\n\n".
"The keys in this map are viewable MIME types; the values are ".
"the MIME types they are delivered as when they are viewed in ".
"the browser.")),
$this->newOption('files.image-mime-types', 'set', $image_default)
->setLocked(true)
->setSummary(pht('Configure which MIME types are images.'))
->setDescription(
pht(
'List of MIME types which can be used as the `%s` for an `%s` tag.',
'src',
'<img />')),
$this->newOption('files.audio-mime-types', 'set', $audio_default)
->setLocked(true)
->setSummary(pht('Configure which MIME types are audio.'))
->setDescription(
pht(
'List of MIME types which can be rendered with an `%s` tag.',
'<audio />')),
$this->newOption('files.video-mime-types', 'set', $video_default)
->setLocked(true)
->setSummary(pht('Configure which MIME types are video.'))
->setDescription(
pht(
'List of MIME types which can be rendered with a `%s` tag.',
'<video />')),
$this->newOption('files.icon-mime-types', 'wild', $icon_default)
->setLocked(true)
->setSummary(pht('Configure which MIME types map to which icons.'))
->setDescription(
pht(
'Map of MIME type to icon name. MIME types which can not be '.
'found default to icon `%s`.',
'doc_files')),
$this->newOption('storage.mysql-engine.max-size', 'int', 1000000)
->setSummary(
pht(
'Configure the largest file which will be put into the MySQL '.
'storage engine.')),
$this->newOption('storage.local-disk.path', 'string', null)
->setLocked(true)
->setSummary(pht('Local storage disk path.'))
->setDescription(
pht(
- "Phabricator provides a local disk storage engine, which just ".
+ "This software provides a local disk storage engine, which just ".
"writes files to some directory on local disk. The webserver ".
"must have read/write permissions on this directory. This is ".
"straightforward and suitable for most installs, but will not ".
"scale past one web frontend unless the path is actually an NFS ".
"mount, since you'll end up with some of the files written to ".
"each web frontend and no way for them to share. To use the ".
"local disk storage engine, specify the path to a directory ".
"here. To disable it, specify null.")),
$this->newOption('storage.s3.bucket', 'string', null)
->setSummary(pht('Amazon S3 bucket.'))
->setDescription(
pht(
"Set this to a valid Amazon S3 bucket to store files there. You ".
"must also configure S3 access keys in the 'Amazon Web Services' ".
"group.")),
$this->newOption('files.enable-imagemagick', 'bool', false)
->setBoolOptions(
array(
pht('Enable'),
pht('Disable'),
))
->setDescription(
pht(
'This option will use Imagemagick to rescale images, so animated '.
'GIFs can be thumbnailed and set as profile pictures. Imagemagick '.
'must be installed and the "%s" binary must be available to '.
'the webserver for this to work.',
'convert')),
);
}
}
diff --git a/src/applications/files/controller/PhabricatorFileDetachController.php b/src/applications/files/controller/PhabricatorFileDetachController.php
new file mode 100644
index 0000000000..146eb42874
--- /dev/null
+++ b/src/applications/files/controller/PhabricatorFileDetachController.php
@@ -0,0 +1,120 @@
+<?php
+
+final class PhabricatorFileDetachController
+ extends PhabricatorFileController {
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $request->getViewer();
+
+ $object_phid = $request->getURIData('objectPHID');
+ $file_phid = $request->getURIData('filePHID');
+
+ $object = id(new PhabricatorObjectQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($object_phid))
+ ->executeOne();
+ if (!$object) {
+ return new Aphront404Response();
+ }
+
+ $handles = $viewer->loadHandles(
+ array(
+ $object_phid,
+ $file_phid,
+ ));
+
+ $object_handle = $handles[$object_phid];
+ $file_handle = $handles[$file_phid];
+ $cancel_uri = $file_handle->getURI();
+
+ $dialog = $this->newDialog()
+ ->setViewer($viewer)
+ ->setTitle(pht('Detach File'))
+ ->addCancelButton($cancel_uri, pht('Close'));
+
+ $file_link = phutil_tag('strong', array(), $file_handle->renderLink());
+ $object_link = phutil_tag('strong', array(), $object_handle->renderLink());
+
+ $attachment = id(new PhabricatorFileAttachmentQuery())
+ ->setViewer($viewer)
+ ->withObjectPHIDs(array($object->getPHID()))
+ ->withFilePHIDs(array($file_phid))
+ ->needFiles(true)
+ ->withVisibleFiles(true)
+ ->executeOne();
+ if (!$attachment) {
+ $body = pht(
+ 'The file %s is not attached to the object %s.',
+ $file_link,
+ $object_link);
+
+ return $dialog->appendParagraph($body);
+ }
+
+ $mode_reference = PhabricatorFileAttachment::MODE_REFERENCE;
+ if ($attachment->getAttachmentMode() === $mode_reference) {
+ $body = pht(
+ 'The file %s is referenced by the object %s, but not attached to '.
+ 'it, so it can not be detached.',
+ $file_link,
+ $object_link);
+
+ return $dialog->appendParagraph($body);
+ }
+
+ if (!$attachment->canDetach()) {
+ $body = pht(
+ 'The file %s can not be detached from the object %s.',
+ $file_link,
+ $object_link);
+
+ return $dialog->appendParagraph($body);
+ }
+
+ if (!$request->isDialogFormPost()) {
+ $dialog->appendParagraph(
+ pht(
+ 'Detach the file %s from the object %s?',
+ $file_link,
+ $object_link));
+
+ $dialog->addSubmitButton(pht('Detach File'));
+
+ return $dialog;
+ }
+
+ if (!($object instanceof PhabricatorApplicationTransactionInterface)) {
+ $dialog->appendParagraph(
+ pht(
+ 'This object (of class "%s") does not implement the required '.
+ 'interface ("%s"), so files can not be manually detached from it.',
+ get_class($object),
+ 'PhabricatorApplicationTransactionInterface'));
+
+ return $dialog;
+ }
+
+ $editor = $object->getApplicationTransactionEditor()
+ ->setActor($viewer)
+ ->setContentSourceFromRequest($request)
+ ->setContinueOnNoEffect(true)
+ ->setContinueOnMissingFields(true);
+
+ $template = $object->getApplicationTransactionTemplate();
+
+ $xactions = array();
+
+ $xactions[] = id(clone $template)
+ ->setTransactionType(PhabricatorTransactions::TYPE_FILE)
+ ->setNewValue(
+ array(
+ $file_phid => PhabricatorFileAttachment::MODE_DETACH,
+ ));
+
+ $editor->applyTransactions($object, $xactions);
+
+ return $this->newRedirect()
+ ->setURI($cancel_uri);
+ }
+
+}
diff --git a/src/applications/files/controller/PhabricatorFileUICurtainAttachController.php b/src/applications/files/controller/PhabricatorFileUICurtainAttachController.php
new file mode 100644
index 0000000000..8dcfa5b840
--- /dev/null
+++ b/src/applications/files/controller/PhabricatorFileUICurtainAttachController.php
@@ -0,0 +1,133 @@
+<?php
+
+final class PhabricatorFileUICurtainAttachController
+ extends PhabricatorFileController {
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $request->getViewer();
+
+ $object_phid = $request->getURIData('objectPHID');
+ $file_phid = $request->getURIData('filePHID');
+
+ $object = id(new PhabricatorObjectQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($object_phid))
+ ->executeOne();
+ if (!$object) {
+ return new Aphront404Response();
+ }
+
+ $attachment = id(new PhabricatorFileAttachmentQuery())
+ ->setViewer($viewer)
+ ->withObjectPHIDs(array($object->getPHID()))
+ ->withFilePHIDs(array($file_phid))
+ ->needFiles(true)
+ ->withVisibleFiles(true)
+ ->executeOne();
+ if (!$attachment) {
+ return new Aphront404Response();
+ }
+
+ $handles = $viewer->loadHandles(
+ array(
+ $object_phid,
+ $file_phid,
+ ));
+
+ $object_handle = $handles[$object_phid];
+ $file_handle = $handles[$file_phid];
+ $cancel_uri = $object_handle->getURI();
+
+ $dialog = $this->newDialog()
+ ->setViewer($viewer)
+ ->setTitle(pht('Attach File'))
+ ->addCancelButton($cancel_uri, pht('Close'));
+
+ $file_link = phutil_tag('strong', array(), $file_handle->renderLink());
+ $object_link = phutil_tag('strong', array(), $object_handle->renderLink());
+
+ if ($attachment->isPolicyAttachment()) {
+ $body = pht(
+ 'The file %s is already attached to the object %s.',
+ $file_link,
+ $object_link);
+
+ return $dialog->appendParagraph($body);
+ }
+
+ if (!$request->isDialogFormPost()) {
+ $dialog->appendRemarkup(
+ pht(
+ '(WARNING) This file is referenced by this object, but '.
+ 'not formally attached to it. Users who can see the object may '.
+ 'not be able to see the file.'));
+
+ $dialog->appendParagraph(
+ pht(
+ 'Do you want to attach the file %s to the object %s?',
+ $file_link,
+ $object_link));
+
+ $dialog->addSubmitButton(pht('Attach File'));
+
+ return $dialog;
+ }
+
+ if (!$request->getBool('confirm')) {
+ $dialog->setTitle(pht('Confirm File Attachment'));
+
+ $dialog->addHiddenInput('confirm', 1);
+
+ $dialog->appendRemarkup(
+ pht(
+ '(IMPORTANT) If you attach this file to this object, any user who '.
+ 'has permission to view the object will be able to view and '.
+ 'download the file!'));
+
+ $dialog->appendParagraph(
+ pht(
+ 'Really attach the file %s to the object %s, allowing any user '.
+ 'who can view the object to view and download the file?',
+ $file_link,
+ $object_link));
+
+ $dialog->addSubmitButton(pht('Grant Permission'));
+
+ return $dialog;
+ }
+
+ if (!($object instanceof PhabricatorApplicationTransactionInterface)) {
+ $dialog->appendParagraph(
+ pht(
+ 'This object (of class "%s") does not implement the required '.
+ 'interface ("%s"), so files can not be manually attached to it.',
+ get_class($object),
+ 'PhabricatorApplicationTransactionInterface'));
+
+ return $dialog;
+ }
+
+ $editor = $object->getApplicationTransactionEditor()
+ ->setActor($viewer)
+ ->setContentSourceFromRequest($request)
+ ->setContinueOnNoEffect(true)
+ ->setContinueOnMissingFields(true);
+
+ $template = $object->getApplicationTransactionTemplate();
+
+ $xactions = array();
+
+ $xactions[] = id(clone $template)
+ ->setTransactionType(PhabricatorTransactions::TYPE_FILE)
+ ->setNewValue(
+ array(
+ $file_phid => PhabricatorFileAttachment::MODE_ATTACH,
+ ));
+
+ $editor->applyTransactions($object, $xactions);
+
+ return $this->newRedirect()
+ ->setURI($cancel_uri);
+ }
+
+}
diff --git a/src/applications/files/controller/PhabricatorFileUICurtainListController.php b/src/applications/files/controller/PhabricatorFileUICurtainListController.php
new file mode 100644
index 0000000000..3049f4ddeb
--- /dev/null
+++ b/src/applications/files/controller/PhabricatorFileUICurtainListController.php
@@ -0,0 +1,61 @@
+<?php
+
+final class PhabricatorFileUICurtainListController
+ extends PhabricatorFileController {
+
+ public function shouldAllowPublic() {
+ return true;
+ }
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $request->getViewer();
+
+ $object_phid = $request->getURIData('phid');
+
+ $object = id(new PhabricatorObjectQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($object_phid))
+ ->executeOne();
+ if (!$object) {
+ return new Aphront404Response();
+ }
+
+ $attachments = id(new PhabricatorFileAttachmentQuery())
+ ->setViewer($viewer)
+ ->withObjectPHIDs(array($object->getPHID()))
+ ->needFiles(true)
+ ->execute();
+
+ $handles = $viewer->loadHandles(array($object_phid));
+ $object_handle = $handles[$object_phid];
+
+ $file_phids = mpull($attachments, 'getFilePHID');
+ $file_handles = $viewer->loadHandles($file_phids);
+
+ $list = id(new PHUIObjectItemListView())
+ ->setUser($viewer);
+ foreach ($attachments as $attachment) {
+ $file_phid = $attachment->getFilePHID();
+ $handle = $file_handles[$file_phid];
+
+ $item = id(new PHUIObjectItemView())
+ ->setHeader($handle->getFullName())
+ ->setHref($handle->getURI())
+ ->setDisabled($handle->isDisabled());
+
+ if ($handle->getImageURI()) {
+ $item->setImageURI($handle->getImageURI());
+ }
+
+ $list->addItem($item);
+ }
+
+ return $this->newDialog()
+ ->setViewer($viewer)
+ ->setWidth(AphrontDialogView::WIDTH_FORM)
+ ->setTitle(pht('Referenced Files'))
+ ->setObjectList($list)
+ ->addCancelButton($object_handle->getURI(), pht('Close'));
+ }
+
+}
diff --git a/src/applications/files/controller/PhabricatorFileUploadController.php b/src/applications/files/controller/PhabricatorFileUploadController.php
index c06e0e6d89..dfc4696e08 100644
--- a/src/applications/files/controller/PhabricatorFileUploadController.php
+++ b/src/applications/files/controller/PhabricatorFileUploadController.php
@@ -1,110 +1,110 @@
<?php
final class PhabricatorFileUploadController extends PhabricatorFileController {
public function isGlobalDragAndDropUploadEnabled() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getUser();
$file = PhabricatorFile::initializeNewFile();
$e_file = true;
$errors = array();
if ($request->isFormPost()) {
$view_policy = $request->getStr('viewPolicy');
if (!$request->getFileExists('file')) {
$e_file = pht('Required');
$errors[] = pht('You must select a file to upload.');
} else {
$file = PhabricatorFile::newFromPHPUpload(
idx($_FILES, 'file'),
array(
'name' => $request->getStr('name'),
'authorPHID' => $viewer->getPHID(),
'viewPolicy' => $view_policy,
'isExplicitUpload' => true,
));
}
if (!$errors) {
return id(new AphrontRedirectResponse())->setURI($file->getInfoURI());
}
$file->setViewPolicy($view_policy);
}
$support_id = celerity_generate_unique_node_id();
$instructions = id(new AphrontFormMarkupControl())
->setControlID($support_id)
->setControlStyle('display: none')
->setValue(hsprintf(
'<br /><br /><strong>%s</strong> %s<br /><br />',
pht('Drag and Drop:'),
pht(
'You can also upload files by dragging and dropping them from your '.
- 'desktop onto this page or the Phabricator home page.')));
+ 'desktop onto this page or the home page.')));
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($file)
->execute();
$form = id(new AphrontFormView())
->setUser($viewer)
->setEncType('multipart/form-data')
->appendChild(
id(new AphrontFormFileControl())
->setLabel(pht('File'))
->setName('file')
->setError($e_file))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setValue($request->getStr('name')))
->appendChild(
id(new AphrontFormPolicyControl())
->setUser($viewer)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicyObject($file)
->setPolicies($policies)
->setName('viewPolicy'))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Upload'))
->addCancelButton('/file/'))
->appendChild($instructions);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Upload'), $request->getRequestURI());
$crumbs->setBorder(true);
$title = pht('Upload File');
$global_upload = id(new PhabricatorGlobalUploadTargetView())
->setUser($viewer)
->setShowIfSupportedID($support_id);
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setFormErrors($errors)
->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->setForm($form);
$view = id(new PHUITwoColumnView())
->setFooter(array(
$form_box,
$global_upload,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
}
diff --git a/src/applications/files/controller/PhabricatorFileViewController.php b/src/applications/files/controller/PhabricatorFileViewController.php
index 145f066492..389fc66247 100644
--- a/src/applications/files/controller/PhabricatorFileViewController.php
+++ b/src/applications/files/controller/PhabricatorFileViewController.php
@@ -1,423 +1,493 @@
<?php
final class PhabricatorFileViewController extends PhabricatorFileController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$phid = $request->getURIData('phid');
if ($phid) {
$file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($phid))
->withIsDeleted(false)
->executeOne();
if (!$file) {
return new Aphront404Response();
}
return id(new AphrontRedirectResponse())->setURI($file->getInfoURI());
}
$file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withIDs(array($id))
->withIsDeleted(false)
->executeOne();
if (!$file) {
return new Aphront404Response();
}
$phid = $file->getPHID();
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setPolicyObject($file)
->setHeader($file->getName())
->setHeaderIcon('fa-file-o');
$ttl = $file->getTTL();
if ($ttl !== null) {
$ttl_tag = id(new PHUITagView())
->setType(PHUITagView::TYPE_SHADE)
->setColor(PHUITagView::COLOR_YELLOW)
->setName(pht('Temporary'));
$header->addTag($ttl_tag);
}
$partial = $file->getIsPartial();
if ($partial) {
$partial_tag = id(new PHUITagView())
->setType(PHUITagView::TYPE_SHADE)
->setColor(PHUITagView::COLOR_ORANGE)
->setName(pht('Partial Upload'));
$header->addTag($partial_tag);
}
$curtain = $this->buildCurtainView($file);
$timeline = $this->buildTransactionView($file);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(
$file->getMonogram(),
$file->getInfoURI());
$crumbs->setBorder(true);
$object_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('File Metadata'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
$this->buildPropertyViews($object_box, $file);
$title = $file->getName();
$file_content = $this->newFileContent($file);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->setMainColumn(
array(
$file_content,
$object_box,
$timeline,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->setPageObjectPHIDs(array($file->getPHID()))
->appendChild($view);
}
private function buildTransactionView(PhabricatorFile $file) {
$viewer = $this->getViewer();
$timeline = $this->buildTransactionTimeline(
$file,
new PhabricatorFileTransactionQuery());
$comment_view = id(new PhabricatorFileEditEngine())
->setViewer($viewer)
->buildEditEngineCommentView($file);
$monogram = $file->getMonogram();
$timeline->setQuoteRef($monogram);
$comment_view->setTransactionTimeline($timeline);
return array(
$timeline,
$comment_view,
);
}
private function buildCurtainView(PhabricatorFile $file) {
$viewer = $this->getViewer();
$id = $file->getID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$file,
PhabricatorPolicyCapability::CAN_EDIT);
$curtain = $this->newCurtainView($file);
$can_download = !$file->getIsPartial();
if ($file->isViewableInBrowser()) {
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('View File'))
->setIcon('fa-file-o')
->setHref($file->getViewURI())
->setDisabled(!$can_download)
->setWorkflow(!$can_download));
} else {
$curtain->addAction(
id(new PhabricatorActionView())
->setUser($viewer)
->setDownload($can_download)
->setName(pht('Download File'))
->setIcon('fa-download')
->setHref($file->getDownloadURI())
->setDisabled(!$can_download)
->setWorkflow(!$can_download));
}
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit File'))
->setIcon('fa-pencil')
->setHref($this->getApplicationURI("/edit/{$id}/"))
->setWorkflow(!$can_edit)
->setDisabled(!$can_edit));
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Delete File'))
->setIcon('fa-times')
->setHref($this->getApplicationURI("/delete/{$id}/"))
->setWorkflow(true)
->setDisabled(!$can_edit));
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('View Transforms'))
->setIcon('fa-crop')
->setHref($this->getApplicationURI("/transforms/{$id}/")));
$phids = array();
$viewer_phid = $viewer->getPHID();
$author_phid = $file->getAuthorPHID();
if ($author_phid) {
$phids[] = $author_phid;
}
$handles = $viewer->loadHandles($phids);
if ($author_phid) {
$author_refs = id(new PHUICurtainObjectRefListView())
->setViewer($viewer);
$author_ref = $author_refs->newObjectRefView()
->setHandle($handles[$author_phid])
->setEpoch($file->getDateCreated())
->setHighlighted($author_phid === $viewer_phid);
$curtain->newPanel()
->setHeaderText(pht('Authored By'))
->appendChild($author_refs);
}
$curtain->newPanel()
->setHeaderText(pht('Size'))
->appendChild(phutil_format_bytes($file->getByteSize()));
$width = $file->getImageWidth();
$height = $file->getImageHeight();
if ($width || $height) {
$curtain->newPanel()
->setHeaderText(pht('Dimensions'))
->appendChild(
pht(
"%spx \xC3\x97 %spx",
new PhutilNumber($width),
new PhutilNumber($height)));
}
return $curtain;
}
private function buildPropertyViews(
PHUIObjectBoxView $box,
PhabricatorFile $file) {
$request = $this->getRequest();
$viewer = $request->getUser();
$tab_group = id(new PHUITabGroupView());
$box->addTabGroup($tab_group);
$finfo = new PHUIPropertyListView();
$tab_group->addTab(
id(new PHUITabView())
->setName(pht('Details'))
->setKey('details')
->appendChild($finfo));
$finfo->addProperty(
pht('Mime Type'),
$file->getMimeType());
$ttl = $file->getTtl();
if ($ttl) {
$delta = $ttl - PhabricatorTime::getNow();
$finfo->addProperty(
pht('Expires'),
pht(
'%s (%s)',
phabricator_datetime($ttl, $viewer),
phutil_format_relative_time_detailed($delta)));
}
$is_image = $file->isViewableImage();
if ($is_image) {
$image_string = pht('Yes');
$cache_string = $file->getCanCDN() ? pht('Yes') : pht('No');
} else {
$image_string = pht('No');
$cache_string = pht('Not Applicable');
}
$types = array();
if ($file->isViewableImage()) {
$types[] = pht('Image');
}
if ($file->isVideo()) {
$types[] = pht('Video');
}
if ($file->isAudio()) {
$types[] = pht('Audio');
}
if ($file->getCanCDN()) {
$types[] = pht('Can CDN');
}
$builtin = $file->getBuiltinName();
if ($builtin !== null) {
$types[] = pht('Builtin ("%s")', $builtin);
}
if ($file->getIsProfileImage()) {
$types[] = pht('Profile');
}
if ($types) {
$types = implode(', ', $types);
$finfo->addProperty(pht('Attributes'), $types);
}
$finfo->addProperty(
pht('Storage Engine'),
$file->getStorageEngine());
$engine = $this->loadStorageEngine($file);
if ($engine && $engine->isChunkEngine()) {
$format_name = pht('Chunks');
} else {
$format_key = $file->getStorageFormat();
$format = PhabricatorFileStorageFormat::getFormat($format_key);
if ($format) {
$format_name = $format->getStorageFormatName();
} else {
$format_name = pht('Unknown ("%s")', $format_key);
}
}
$finfo->addProperty(pht('Storage Format'), $format_name);
$finfo->addProperty(
pht('Storage Handle'),
$file->getStorageHandle());
$custom_alt = $file->getCustomAltText();
if (strlen($custom_alt)) {
$finfo->addProperty(pht('Custom Alt Text'), $custom_alt);
}
$default_alt = $file->getDefaultAltText();
if (strlen($default_alt)) {
$finfo->addProperty(pht('Default Alt Text'), $default_alt);
}
- $phids = $file->getObjectPHIDs();
- if ($phids) {
- $attached = new PHUIPropertyListView();
-
- $tab_group->addTab(
- id(new PHUITabView())
- ->setName(pht('Attached'))
- ->setKey('attached')
- ->appendChild($attached));
-
- $attached->addProperty(
- pht('Attached To'),
- $viewer->renderHandleList($phids));
- }
+ $attachments_table = $this->newAttachmentsView($file);
+
+ $tab_group->addTab(
+ id(new PHUITabView())
+ ->setName(pht('Attached'))
+ ->setKey('attached')
+ ->appendChild($attachments_table));
$engine = $this->loadStorageEngine($file);
if ($engine) {
if ($engine->isChunkEngine()) {
$chunkinfo = new PHUIPropertyListView();
$tab_group->addTab(
id(new PHUITabView())
->setName(pht('Chunks'))
->setKey('chunks')
->appendChild($chunkinfo));
$chunks = id(new PhabricatorFileChunkQuery())
->setViewer($viewer)
->withChunkHandles(array($file->getStorageHandle()))
->execute();
$chunks = msort($chunks, 'getByteStart');
$rows = array();
$completed = array();
foreach ($chunks as $chunk) {
$is_complete = $chunk->getDataFilePHID();
$rows[] = array(
$chunk->getByteStart(),
$chunk->getByteEnd(),
($is_complete ? pht('Yes') : pht('No')),
);
if ($is_complete) {
$completed[] = $chunk;
}
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Offset'),
pht('End'),
pht('Complete'),
))
->setColumnClasses(
array(
'',
'',
'wide',
));
$chunkinfo->addProperty(
pht('Total Chunks'),
count($chunks));
$chunkinfo->addProperty(
pht('Completed Chunks'),
count($completed));
$chunkinfo->addRawContent($table);
}
}
}
private function loadStorageEngine(PhabricatorFile $file) {
$engine = null;
try {
$engine = $file->instantiateStorageEngine();
} catch (Exception $ex) {
// Don't bother raising this anywhere for now.
}
return $engine;
}
private function newFileContent(PhabricatorFile $file) {
$request = $this->getRequest();
$ref = id(new PhabricatorDocumentRef())
->setFile($file);
$engine = id(new PhabricatorFileDocumentRenderingEngine())
->setRequest($request);
return $engine->newDocumentView($ref);
}
+ private function newAttachmentsView(PhabricatorFile $file) {
+ $viewer = $this->getViewer();
+
+ $attachments = id(new PhabricatorFileAttachmentQuery())
+ ->setViewer($viewer)
+ ->withFilePHIDs(array($file->getPHID()))
+ ->execute();
+
+ $handles = $viewer->loadHandles(mpull($attachments, 'getObjectPHID'));
+
+ $rows = array();
+
+ $mode_map = PhabricatorFileAttachment::getModeNameMap();
+ $mode_attach = PhabricatorFileAttachment::MODE_ATTACH;
+
+ foreach ($attachments as $attachment) {
+ $object_phid = $attachment->getObjectPHID();
+ $handle = $handles[$object_phid];
+
+ $attachment_mode = $attachment->getAttachmentMode();
+
+ $mode_name = idx($mode_map, $attachment_mode);
+ if ($mode_name === null) {
+ $mode_name = pht('Unknown ("%s")', $attachment_mode);
+ }
+
+ $detach_uri = urisprintf(
+ '/file/ui/detach/%s/%s/',
+ $object_phid,
+ $file->getPHID());
+
+ $is_disabled = !$attachment->canDetach();
+
+ $detach_button = id(new PHUIButtonView())
+ ->setHref($detach_uri)
+ ->setTag('a')
+ ->setWorkflow(true)
+ ->setDisabled($is_disabled)
+ ->setColor(PHUIButtonView::GREY)
+ ->setSize(PHUIButtonView::SMALL)
+ ->setText(pht('Detach File'));
+
+ javelin_tag(
+ 'a',
+ array(
+ 'href' => $detach_uri,
+ 'sigil' => 'workflow',
+ 'disabled' => true,
+ 'class' => 'small button button-grey disabled',
+ ),
+ pht('Detach File'));
+
+ $rows[] = array(
+ $handle->renderLink(),
+ $mode_name,
+ $detach_button,
+ );
+ }
+
+ $table = id(new AphrontTableView($rows))
+ ->setHeaders(
+ array(
+ pht('Attached To'),
+ pht('Mode'),
+ null,
+ ))
+ ->setColumnClasses(
+ array(
+ 'pri wide',
+ null,
+ null,
+ ));
+
+ return $table;
+ }
+
+
}
diff --git a/src/applications/files/document/PhabricatorDocumentRef.php b/src/applications/files/document/PhabricatorDocumentRef.php
index 12d9f4f2fd..98dfda8856 100644
--- a/src/applications/files/document/PhabricatorDocumentRef.php
+++ b/src/applications/files/document/PhabricatorDocumentRef.php
@@ -1,202 +1,207 @@
<?php
final class PhabricatorDocumentRef
extends Phobject {
private $name;
private $mimeType;
private $file;
private $byteLength;
private $snippet;
private $symbolMetadata = array();
private $blameURI;
private $coverage = array();
private $data;
public function setFile(PhabricatorFile $file) {
$this->file = $file;
return $this;
}
public function getFile() {
return $this->file;
}
public function setMimeType($mime_type) {
$this->mimeType = $mime_type;
return $this;
}
public function getMimeType() {
if ($this->mimeType !== null) {
return $this->mimeType;
}
if ($this->file) {
return $this->file->getMimeType();
}
return null;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
if ($this->name !== null) {
return $this->name;
}
if ($this->file) {
return $this->file->getName();
}
return null;
}
public function setByteLength($length) {
$this->byteLength = $length;
return $this;
}
public function getByteLength() {
if ($this->byteLength !== null) {
return $this->byteLength;
}
if ($this->data !== null) {
return strlen($this->data);
}
if ($this->file) {
return (int)$this->file->getByteSize();
}
return null;
}
public function setData($data) {
$this->data = $data;
return $this;
}
public function loadData($begin = null, $end = null) {
if ($this->data !== null) {
$data = $this->data;
if ($begin !== null && $end !== null) {
$data = substr($data, $begin, $end - $begin);
} else if ($begin !== null) {
$data = substr($data, $begin);
} else if ($end !== null) {
$data = substr($data, 0, $end);
}
return $data;
}
if ($this->file) {
$iterator = $this->file->getFileDataIterator($begin, $end);
$result = '';
foreach ($iterator as $chunk) {
$result .= $chunk;
}
return $result;
}
throw new PhutilMethodNotImplementedException();
}
public function hasAnyMimeType(array $candidate_types) {
$mime_full = $this->getMimeType();
+
+ if (!phutil_nonempty_string($mime_full)) {
+ return false;
+ }
+
$mime_parts = explode(';', $mime_full);
$mime_type = head($mime_parts);
$mime_type = $this->normalizeMimeType($mime_type);
foreach ($candidate_types as $candidate_type) {
if ($this->normalizeMimeType($candidate_type) === $mime_type) {
return true;
}
}
return false;
}
private function normalizeMimeType($mime_type) {
$mime_type = trim($mime_type);
$mime_type = phutil_utf8_strtolower($mime_type);
return $mime_type;
}
public function isProbablyText() {
$snippet = $this->getSnippet();
return (strpos($snippet, "\0") === false);
}
public function isProbablyJSON() {
if (!$this->isProbablyText()) {
return false;
}
$snippet = $this->getSnippet();
// If the file is longer than the snippet, we don't detect the content
// as JSON. We could use some kind of heuristic here if we wanted, but
// see PHI749 for a false positive.
if (strlen($snippet) < $this->getByteLength()) {
return false;
}
// If the snippet is the whole file, just check if the snippet is valid
// JSON. Note that `phutil_json_decode()` only accepts arrays and objects
// as JSON, so this won't misfire on files with content like "3".
try {
phutil_json_decode($snippet);
return true;
} catch (Exception $ex) {
return false;
}
}
public function getSnippet() {
if ($this->snippet === null) {
$this->snippet = $this->loadData(null, (1024 * 1024 * 1));
}
return $this->snippet;
}
public function setSymbolMetadata(array $metadata) {
$this->symbolMetadata = $metadata;
return $this;
}
public function getSymbolMetadata() {
return $this->symbolMetadata;
}
public function setBlameURI($blame_uri) {
$this->blameURI = $blame_uri;
return $this;
}
public function getBlameURI() {
return $this->blameURI;
}
public function addCoverage($coverage) {
$this->coverage[] = array(
'data' => $coverage,
);
return $this;
}
public function getCoverage() {
return $this->coverage;
}
}
diff --git a/src/applications/files/document/PhabricatorJSONDocumentEngine.php b/src/applications/files/document/PhabricatorJSONDocumentEngine.php
index 42f4469ee6..9797ce3da2 100644
--- a/src/applications/files/document/PhabricatorJSONDocumentEngine.php
+++ b/src/applications/files/document/PhabricatorJSONDocumentEngine.php
@@ -1,77 +1,81 @@
<?php
final class PhabricatorJSONDocumentEngine
extends PhabricatorTextDocumentEngine {
const ENGINEKEY = 'json';
public function getViewAsLabel(PhabricatorDocumentRef $ref) {
return pht('View as JSON');
}
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-database';
}
protected function getContentScore(PhabricatorDocumentRef $ref) {
- if (preg_match('/\.json\z/', $ref->getName())) {
- return 2000;
+
+ $name = $ref->getName();
+ if ($name !== null) {
+ if (preg_match('/\.json\z/', $name)) {
+ return 2000;
+ }
}
if ($ref->isProbablyJSON()) {
return 1750;
}
return 500;
}
protected function newDocumentContent(PhabricatorDocumentRef $ref) {
$raw_data = $this->loadTextData($ref);
try {
$data = phutil_json_decode($raw_data);
// See T13635. "phutil_json_decode()" always turns JSON into a PHP array,
// and we lose the distinction between "{}" and "[]". This distinction is
// important when rendering a document.
$data = json_decode($raw_data, false);
if (!$data) {
throw new PhabricatorDocumentEngineParserException(
pht(
'Failed to "json_decode(...)" JSON document after successfully '.
'decoding it with "phutil_json_decode(...).'));
}
if (preg_match('/^\s*\[/', $raw_data)) {
$content = id(new PhutilJSON())->encodeAsList($data);
} else {
$content = id(new PhutilJSON())->encodeFormatted($data);
}
$message = null;
$content = PhabricatorSyntaxHighlighter::highlightWithLanguage(
'json',
$content);
} catch (PhutilJSONParserException $ex) {
$message = $this->newMessage(
pht(
'This document is not valid JSON: %s',
$ex->getMessage()));
$content = $raw_data;
} catch (PhabricatorDocumentEngineParserException $ex) {
$message = $this->newMessage(
pht(
'Unable to parse this document as JSON: %s',
$ex->getMessage()));
$content = $raw_data;
}
return array(
$message,
$this->newTextDocumentContent($ref, $content),
);
}
}
diff --git a/src/applications/files/document/PhabricatorRemarkupDocumentEngine.php b/src/applications/files/document/PhabricatorRemarkupDocumentEngine.php
index 296b78196f..053640af5f 100644
--- a/src/applications/files/document/PhabricatorRemarkupDocumentEngine.php
+++ b/src/applications/files/document/PhabricatorRemarkupDocumentEngine.php
@@ -1,47 +1,50 @@
<?php
final class PhabricatorRemarkupDocumentEngine
extends PhabricatorDocumentEngine {
const ENGINEKEY = 'remarkup';
public function getViewAsLabel(PhabricatorDocumentRef $ref) {
return pht('View as Remarkup');
}
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-file-text-o';
}
protected function getContentScore(PhabricatorDocumentRef $ref) {
$name = $ref->getName();
- if (preg_match('/\\.remarkup\z/i', $name)) {
- return 2000;
+
+ if ($name !== null) {
+ if (preg_match('/\\.remarkup\z/i', $name)) {
+ return 2000;
+ }
}
return 500;
}
protected function canRenderDocumentType(PhabricatorDocumentRef $ref) {
return $ref->isProbablyText();
}
protected function newDocumentContent(PhabricatorDocumentRef $ref) {
$viewer = $this->getViewer();
$content = $ref->loadData();
$content = phutil_utf8ize($content);
$remarkup = new PHUIRemarkupView($viewer, $content);
$container = phutil_tag(
'div',
array(
'class' => 'document-engine-remarkup',
),
$remarkup);
return $container;
}
}
diff --git a/src/applications/files/edge/PhabricatorFileHasObjectEdgeType.php b/src/applications/files/edge/PhabricatorFileHasObjectEdgeType.php
deleted file mode 100644
index c4a3c2af87..0000000000
--- a/src/applications/files/edge/PhabricatorFileHasObjectEdgeType.php
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-
-final class PhabricatorFileHasObjectEdgeType extends PhabricatorEdgeType {
-
- const EDGECONST = 26;
-
- public function getInverseEdgeConstant() {
- return PhabricatorObjectHasFileEdgeType::EDGECONST;
- }
-
- public function shouldWriteInverseTransactions() {
- return true;
- }
-
- public function getConduitKey() {
- return 'file.attached-objects';
- }
-
- public function getConduitName() {
- return pht('File Has Object');
- }
-
- public function getConduitDescription() {
- return pht('The source file is attached to the destination object.');
- }
-
-}
diff --git a/src/applications/files/engineextension/PhabricatorFilesCurtainExtension.php b/src/applications/files/engineextension/PhabricatorFilesCurtainExtension.php
new file mode 100644
index 0000000000..9a3a707f99
--- /dev/null
+++ b/src/applications/files/engineextension/PhabricatorFilesCurtainExtension.php
@@ -0,0 +1,119 @@
+<?php
+
+final class PhabricatorFilesCurtainExtension
+ extends PHUICurtainExtension {
+
+ const EXTENSIONKEY = 'files.files';
+
+ public function shouldEnableForObject($object) {
+ return true;
+ }
+
+ public function getExtensionApplication() {
+ return new PhabricatorFilesApplication();
+ }
+
+ public function buildCurtainPanel($object) {
+ $viewer = $this->getViewer();
+
+ $attachment_table = new PhabricatorFileAttachment();
+ $attachment_conn = $attachment_table->establishConnection('r');
+
+ $exact_limit = 100;
+ $visible_limit = 8;
+
+ $attachments = id(new PhabricatorFileAttachmentQuery())
+ ->setViewer($viewer)
+ ->withObjectPHIDs(array($object->getPHID()))
+ ->setLimit($exact_limit + 1)
+ ->needFiles(true)
+ ->execute();
+
+ $visible_attachments = array_slice($attachments, 0, $visible_limit, true);
+ $visible_phids = mpull($visible_attachments, 'getFilePHID');
+
+ $handles = $viewer->loadHandles($visible_phids);
+
+ $ref_list = id(new PHUICurtainObjectRefListView())
+ ->setViewer($viewer)
+ ->setEmptyMessage(pht('None'));
+
+ $view_capability = PhabricatorPolicyCapability::CAN_VIEW;
+ $object_policies = PhabricatorPolicyQuery::loadPolicies(
+ $viewer,
+ $object);
+ $object_policy = idx($object_policies, $view_capability);
+
+ foreach ($visible_attachments as $attachment) {
+ $file_phid = $attachment->getFilePHID();
+ $handle = $handles[$file_phid];
+
+ $ref = $ref_list->newObjectRefView()
+ ->setHandle($handle);
+
+ $file = $attachment->getFile();
+ if (!$file) {
+ // ...
+ } else {
+ if (!$attachment->isPolicyAttachment()) {
+ $file_policies = PhabricatorPolicyQuery::loadPolicies(
+ $viewer,
+ $file);
+ $file_policy = idx($file_policies, $view_capability);
+
+ if ($object_policy->isStrongerThanOrEqualTo($file_policy)) {
+ // The file is not attached to the object, but the file policy
+ // allows anyone who can see the object to see the file too, so
+ // there is no material problem with the file not being attached.
+ } else {
+ $attach_uri = urisprintf(
+ '/file/ui/curtain/attach/%s/%s/',
+ $object->getPHID(),
+ $file->getPHID());
+
+ $attached_link = javelin_tag(
+ 'a',
+ array(
+ 'href' => $attach_uri,
+ 'sigil' => 'workflow',
+ ),
+ pht('File Not Attached'));
+
+ $ref->setExiled(
+ true,
+ $attached_link);
+ }
+ }
+ }
+
+ $epoch = $attachment->getDateCreated();
+ $ref->setEpoch($epoch);
+ }
+
+ $show_all = (count($visible_attachments) < count($attachments));
+ if ($show_all) {
+ $view_all_uri = urisprintf(
+ '/file/ui/curtain/list/%s/',
+ $object->getPHID());
+
+ $loaded_count = count($attachments);
+ if ($loaded_count > $exact_limit) {
+ $link_text = pht('View All Files');
+ } else {
+ $link_text = pht('View All %d Files', new PhutilNumber($loaded_count));
+ }
+
+ $ref_list->newTailLink()
+ ->setURI($view_all_uri)
+ ->setText($link_text)
+ ->setWorkflow(true);
+ }
+
+ return $this->newPanel()
+ ->setHeaderText(pht('Referenced Files'))
+ ->setOrder(15000)
+ ->appendChild($ref_list);
+ }
+
+
+}
diff --git a/src/applications/files/format/__tests__/PhabricatorFileStorageFormatTestCase.php b/src/applications/files/format/__tests__/PhabricatorFileStorageFormatTestCase.php
index b3425ba313..bb850edc85 100644
--- a/src/applications/files/format/__tests__/PhabricatorFileStorageFormatTestCase.php
+++ b/src/applications/files/format/__tests__/PhabricatorFileStorageFormatTestCase.php
@@ -1,136 +1,140 @@
<?php
final class PhabricatorFileStorageFormatTestCase extends PhabricatorTestCase {
protected function getPhabricatorTestCaseConfiguration() {
return array(
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
);
}
public function testRot13Storage() {
$engine = new PhabricatorTestStorageEngine();
$rot13_format = PhabricatorFileROT13StorageFormat::FORMATKEY;
$data = 'The cow jumped over the full moon.';
$expect = 'Gur pbj whzcrq bire gur shyy zbba.';
$params = array(
'name' => 'test.dat',
'storageEngines' => array(
$engine,
),
'format' => $rot13_format,
);
$file = PhabricatorFile::newFromFileData($data, $params);
// We should have a file stored as rot13, which reads back the input
// data correctly.
$this->assertEqual($rot13_format, $file->getStorageFormat());
$this->assertEqual($data, $file->loadFileData());
// The actual raw data in the storage engine should be encoded.
$raw_data = $engine->readFile($file->getStorageHandle());
$this->assertEqual($expect, $raw_data);
// If we generate an iterator over a slice of the file, it should return
// the decrypted file.
$iterator = $file->getFileDataIterator(4, 14);
$raw_data = '';
foreach ($iterator as $data_chunk) {
$raw_data .= $data_chunk;
}
$this->assertEqual('cow jumped', $raw_data);
}
public function testAES256Storage() {
+ if (!function_exists('openssl_encrypt')) {
+ $this->assertSkipped(pht('No OpenSSL extension available.'));
+ }
+
$engine = new PhabricatorTestStorageEngine();
$key_name = 'test.abcd';
$key_text = 'abcdefghijklmnopABCDEFGHIJKLMNOP';
PhabricatorKeyring::addKey(
array(
'name' => $key_name,
'type' => 'aes-256-cbc',
'material.base64' => base64_encode($key_text),
));
$format = id(new PhabricatorFileAES256StorageFormat())
->selectMasterKey($key_name);
$data = 'The cow jumped over the full moon.';
$params = array(
'name' => 'test.dat',
'storageEngines' => array(
$engine,
),
'format' => $format,
);
$file = PhabricatorFile::newFromFileData($data, $params);
// We should have a file stored as AES256.
$format_key = $format->getStorageFormatKey();
$this->assertEqual($format_key, $file->getStorageFormat());
$this->assertEqual($data, $file->loadFileData());
// The actual raw data in the storage engine should be encrypted. We
// can't really test this, but we can make sure it's not the same as the
// input data.
$raw_data = $engine->readFile($file->getStorageHandle());
$this->assertTrue($data !== $raw_data);
// If we generate an iterator over a slice of the file, it should return
// the decrypted file.
$iterator = $file->getFileDataIterator(4, 14);
$raw_data = '';
foreach ($iterator as $data_chunk) {
$raw_data .= $data_chunk;
}
$this->assertEqual('cow jumped', $raw_data);
$iterator = $file->getFileDataIterator(4, null);
$raw_data = '';
foreach ($iterator as $data_chunk) {
$raw_data .= $data_chunk;
}
$this->assertEqual('cow jumped over the full moon.', $raw_data);
}
public function testStorageTampering() {
$engine = new PhabricatorTestStorageEngine();
$good = 'The cow jumped over the full moon.';
$evil = 'The cow slept quietly, honoring the glorious dictator.';
$params = array(
'name' => 'message.txt',
'storageEngines' => array(
$engine,
),
);
// First, write the file normally.
$file = PhabricatorFile::newFromFileData($good, $params);
$this->assertEqual($good, $file->loadFileData());
// As an adversary, tamper with the file.
$engine->tamperWithFile($file->getStorageHandle(), $evil);
// Attempts to read the file data should now fail the integrity check.
$caught = null;
try {
$file->loadFileData();
} catch (PhabricatorFileIntegrityException $ex) {
$caught = $ex;
}
$this->assertTrue($caught instanceof PhabricatorFileIntegrityException);
}
}
diff --git a/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php b/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php
index 8e2d0cf0c9..9f37bdcaab 100644
--- a/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php
+++ b/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php
@@ -1,327 +1,358 @@
<?php
final class PhabricatorEmbedFileRemarkupRule
extends PhabricatorObjectRemarkupRule {
private $viewer;
- const KEY_EMBED_FILE_PHIDS = 'phabricator.embedded-file-phids';
+ const KEY_ATTACH_INTENT_FILE_PHIDS = 'files.attach-intent';
protected function getObjectNamePrefix() {
return 'F';
}
protected function loadObjects(array $ids) {
$engine = $this->getEngine();
$this->viewer = $engine->getConfig('viewer');
$objects = id(new PhabricatorFileQuery())
->setViewer($this->viewer)
->withIDs($ids)
->needTransforms(
array(
PhabricatorFileThumbnailTransform::TRANSFORM_PREVIEW,
))
->execute();
+ $objects = mpull($objects, null, 'getID');
- $phids_key = self::KEY_EMBED_FILE_PHIDS;
- $phids = $engine->getTextMetadata($phids_key, array());
- foreach (mpull($objects, 'getPHID') as $phid) {
- $phids[] = $phid;
+
+ // Identify files embedded in the block with "attachment intent", i.e.
+ // those files which the user appears to want to attach to the object.
+ // Files referenced inside quoted blocks are not considered to have this
+ // attachment intent.
+
+ $metadata_key = self::KEY_RULE_OBJECT.'.'.$this->getObjectNamePrefix();
+ $metadata = $engine->getTextMetadata($metadata_key, array());
+
+ $attach_key = self::KEY_ATTACH_INTENT_FILE_PHIDS;
+ $attach_phids = $engine->getTextMetadata($attach_key, array());
+
+ foreach ($metadata as $item) {
+
+ // If this reference was inside a quoted block, don't count it. Quoting
+ // someone else doesn't establish an intent to attach a file.
+ $depth = idx($item, 'quote.depth');
+ if ($depth > 0) {
+ continue;
+ }
+
+ $id = $item['id'];
+ $file = idx($objects, $id);
+
+ if (!$file) {
+ continue;
+ }
+
+ $attach_phids[] = $file->getPHID();
}
- $engine->setTextMetadata($phids_key, $phids);
+
+ $attach_phids = array_fuse($attach_phids);
+ $attach_phids = array_keys($attach_phids);
+
+ $engine->setTextMetadata($attach_key, $attach_phids);
+
return $objects;
}
protected function renderObjectEmbed(
$object,
PhabricatorObjectHandle $handle,
$options) {
$options = $this->getFileOptions($options) + array(
'name' => $object->getName(),
);
$is_viewable_image = $object->isViewableImage();
$is_audio = $object->isAudio();
$is_video = $object->isVideo();
$force_link = ($options['layout'] == 'link');
// If a file is both audio and video, as with "application/ogg" by default,
// render it as video but allow the user to specify `media=audio` if they
// want to force it to render as audio.
if ($is_audio && $is_video) {
$media = $options['media'];
if ($media == 'audio') {
$is_video = false;
} else {
$is_audio = false;
}
}
$options['viewable'] = ($is_viewable_image || $is_audio || $is_video);
if ($is_viewable_image && !$force_link) {
return $this->renderImageFile($object, $handle, $options);
} else if ($is_video && !$force_link) {
return $this->renderVideoFile($object, $handle, $options);
} else if ($is_audio && !$force_link) {
return $this->renderAudioFile($object, $handle, $options);
} else {
return $this->renderFileLink($object, $handle, $options);
}
}
private function getFileOptions($option_string) {
$options = array(
'size' => null,
'layout' => 'left',
'float' => false,
'width' => null,
'height' => null,
'alt' => null,
'media' => null,
'autoplay' => null,
'loop' => null,
);
if ($option_string) {
$option_string = trim($option_string, ', ');
$parser = new PhutilSimpleOptions();
$options = $parser->parse($option_string) + $options;
}
return $options;
}
private function renderImageFile(
PhabricatorFile $file,
PhabricatorObjectHandle $handle,
array $options) {
require_celerity_resource('phui-lightbox-css');
$attrs = array();
$image_class = 'phabricator-remarkup-embed-image';
$use_size = true;
if (!$options['size']) {
$width = $this->parseDimension($options['width']);
$height = $this->parseDimension($options['height']);
if ($width || $height) {
$use_size = false;
$attrs += array(
'src' => $file->getBestURI(),
'width' => $width,
'height' => $height,
);
}
}
if ($use_size) {
switch ((string)$options['size']) {
case 'full':
$attrs += array(
'src' => $file->getBestURI(),
'height' => $file->getImageHeight(),
'width' => $file->getImageWidth(),
);
$image_class = 'phabricator-remarkup-embed-image-full';
break;
// Displays "full" in normal Remarkup, "wide" in Documents
case 'wide':
$attrs += array(
'src' => $file->getBestURI(),
'width' => $file->getImageWidth(),
);
$image_class = 'phabricator-remarkup-embed-image-wide';
break;
case 'thumb':
default:
$preview_key = PhabricatorFileThumbnailTransform::TRANSFORM_PREVIEW;
$xform = PhabricatorFileTransform::getTransformByKey($preview_key);
$existing_xform = $file->getTransform($preview_key);
if ($existing_xform) {
$xform_uri = $existing_xform->getCDNURI('data');
} else {
$xform_uri = $file->getURIForTransform($xform);
}
$attrs['src'] = $xform_uri;
$dimensions = $xform->getTransformedDimensions($file);
if ($dimensions) {
list($x, $y) = $dimensions;
$attrs['width'] = $x;
$attrs['height'] = $y;
}
break;
}
}
$alt = null;
if (isset($options['alt'])) {
$alt = $options['alt'];
}
if (!strlen($alt)) {
$alt = $file->getAltText();
}
$attrs['alt'] = $alt;
$img = phutil_tag('img', $attrs);
$embed = javelin_tag(
'a',
array(
'href' => $file->getBestURI(),
'class' => $image_class,
'sigil' => 'lightboxable',
'meta' => array(
'phid' => $file->getPHID(),
'uri' => $file->getBestURI(),
'dUri' => $file->getDownloadURI(),
'alt' => $alt,
'viewable' => true,
'monogram' => $file->getMonogram(),
),
),
$img);
switch ($options['layout']) {
case 'right':
case 'center':
case 'inline':
case 'left':
$layout_class = 'phabricator-remarkup-embed-layout-'.$options['layout'];
break;
default:
$layout_class = 'phabricator-remarkup-embed-layout-left';
break;
}
if ($options['float']) {
switch ($options['layout']) {
case 'center':
case 'inline':
break;
case 'right':
$layout_class .= ' phabricator-remarkup-embed-float-right';
break;
case 'left':
default:
$layout_class .= ' phabricator-remarkup-embed-float-left';
break;
}
}
return phutil_tag(
($options['layout'] == 'inline' ? 'span' : 'div'),
array(
'class' => $layout_class,
),
$embed);
}
private function renderAudioFile(
PhabricatorFile $file,
PhabricatorObjectHandle $handle,
array $options) {
return $this->renderMediaFile('audio', $file, $handle, $options);
}
private function renderVideoFile(
PhabricatorFile $file,
PhabricatorObjectHandle $handle,
array $options) {
return $this->renderMediaFile('video', $file, $handle, $options);
}
private function renderMediaFile(
$tag,
PhabricatorFile $file,
PhabricatorObjectHandle $handle,
array $options) {
$is_video = ($tag == 'video');
if (idx($options, 'autoplay')) {
$preload = 'auto';
$autoplay = 'autoplay';
} else {
// If we don't preload video, the user can't see the first frame and
// has no clue what they're looking at, so always preload.
if ($is_video) {
$preload = 'auto';
} else {
$preload = 'none';
}
$autoplay = null;
}
// Rendering contexts like feed can disable autoplay.
$engine = $this->getEngine();
if ($engine->getConfig('autoplay.disable')) {
$autoplay = null;
}
if ($is_video) {
// See T13135. Chrome refuses to play videos with type "video/quicktime",
// even though it may actually be able to play them. The least awful fix
// based on available information is to simply omit the "type" attribute
// from `<source />` tags. This causes Chrome to try to play the video
// and realize it can, and does not appear to produce any bad behavior in
// any other browser.
$mime_type = null;
} else {
$mime_type = $file->getMimeType();
}
return $this->newTag(
$tag,
array(
'controls' => 'controls',
'preload' => $preload,
'autoplay' => $autoplay,
'loop' => idx($options, 'loop') ? 'loop' : null,
'alt' => $options['alt'],
'class' => 'phabricator-media',
),
$this->newTag(
'source',
array(
'src' => $file->getBestURI(),
'type' => $mime_type,
)));
}
private function renderFileLink(
PhabricatorFile $file,
PhabricatorObjectHandle $handle,
array $options) {
return id(new PhabricatorFileLinkView())
->setViewer($this->viewer)
->setFilePHID($file->getPHID())
->setFileName($this->assertFlatText($options['name']))
->setFileDownloadURI($file->getDownloadURI())
->setFileViewURI($file->getBestURI())
->setFileViewable((bool)$options['viewable'])
->setFileSize(phutil_format_bytes($file->getByteSize()))
->setFileMonogram($file->getMonogram());
}
private function parseDimension($string) {
$string = trim($string);
if (preg_match('/^(?:\d*\\.)?\d+%?$/', $string)) {
return $string;
}
return null;
}
}
diff --git a/src/applications/files/phid/PhabricatorFileFilePHIDType.php b/src/applications/files/phid/PhabricatorFileFilePHIDType.php
index c847c7f1a5..c583595c4d 100644
--- a/src/applications/files/phid/PhabricatorFileFilePHIDType.php
+++ b/src/applications/files/phid/PhabricatorFileFilePHIDType.php
@@ -1,74 +1,77 @@
<?php
final class PhabricatorFileFilePHIDType extends PhabricatorPHIDType {
const TYPECONST = 'FILE';
public function getTypeName() {
return pht('File');
}
public function newObject() {
return new PhabricatorFile();
}
public function getPHIDTypeApplicationClass() {
return 'PhabricatorFilesApplication';
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {
return id(new PhabricatorFileQuery())
->withPHIDs($phids);
}
public function loadHandles(
PhabricatorHandleQuery $query,
array $handles,
array $objects) {
foreach ($handles as $phid => $handle) {
$file = $objects[$phid];
$id = $file->getID();
$name = $file->getName();
$uri = $file->getInfoURI();
$handle->setName("F{$id}");
$handle->setFullName("F{$id}: {$name}");
$handle->setURI($uri);
+
+ $icon = FileTypeIcon::getFileIcon($name);
+ $handle->setIcon($icon);
}
}
public function canLoadNamedObject($name) {
return preg_match('/^F\d*[1-9]\d*$/', $name);
}
public function loadNamedObjects(
PhabricatorObjectQuery $query,
array $names) {
$id_map = array();
foreach ($names as $name) {
$id = (int)substr($name, 1);
$id_map[$id][] = $name;
}
$objects = id(new PhabricatorFileQuery())
->setViewer($query->getViewer())
->withIDs(array_keys($id_map))
->execute();
$results = array();
foreach ($objects as $id => $object) {
foreach (idx($id_map, $id, array()) as $name) {
$results[$name] = $object;
}
}
return $results;
}
}
diff --git a/src/applications/files/query/PhabricatorFileAttachmentQuery.php b/src/applications/files/query/PhabricatorFileAttachmentQuery.php
new file mode 100644
index 0000000000..5fb37dc32e
--- /dev/null
+++ b/src/applications/files/query/PhabricatorFileAttachmentQuery.php
@@ -0,0 +1,131 @@
+<?php
+
+final class PhabricatorFileAttachmentQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $objectPHIDs;
+ private $filePHIDs;
+ private $needFiles;
+ private $visibleFiles;
+
+ public function withObjectPHIDs(array $object_phids) {
+ $this->objectPHIDs = $object_phids;
+ return $this;
+ }
+
+ public function withFilePHIDs(array $file_phids) {
+ $this->filePHIDs = $file_phids;
+ return $this;
+ }
+
+ public function withVisibleFiles($visible_files) {
+ $this->visibleFiles = $visible_files;
+ return $this;
+ }
+
+ public function needFiles($need) {
+ $this->needFiles = $need;
+ return $this;
+ }
+
+ public function newResultObject() {
+ return new PhabricatorFileAttachment();
+ }
+
+ protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
+ $where = parent::buildWhereClauseParts($conn);
+
+ if ($this->objectPHIDs !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'attachments.objectPHID IN (%Ls)',
+ $this->objectPHIDs);
+ }
+
+ if ($this->filePHIDs !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'attachments.filePHID IN (%Ls)',
+ $this->filePHIDs);
+ }
+
+ return $where;
+ }
+
+ protected function willFilterPage(array $attachments) {
+ $viewer = $this->getViewer();
+ $object_phids = array();
+
+ foreach ($attachments as $attachment) {
+ $object_phid = $attachment->getObjectPHID();
+ $object_phids[$object_phid] = $object_phid;
+ }
+
+ if ($object_phids) {
+ $objects = id(new PhabricatorObjectQuery())
+ ->setViewer($viewer)
+ ->setParentQuery($this)
+ ->withPHIDs($object_phids)
+ ->execute();
+ $objects = mpull($objects, null, 'getPHID');
+ } else {
+ $objects = array();
+ }
+
+ foreach ($attachments as $key => $attachment) {
+ $object_phid = $attachment->getObjectPHID();
+ $object = idx($objects, $object_phid);
+
+ if (!$object) {
+ $this->didRejectResult($attachment);
+ unset($attachments[$key]);
+ continue;
+ }
+
+ $attachment->attachObject($object);
+ }
+
+ if ($this->needFiles) {
+ $file_phids = array();
+ foreach ($attachments as $attachment) {
+ $file_phid = $attachment->getFilePHID();
+ $file_phids[$file_phid] = $file_phid;
+ }
+
+ if ($file_phids) {
+ $files = id(new PhabricatorFileQuery())
+ ->setViewer($viewer)
+ ->setParentQuery($this)
+ ->withPHIDs($file_phids)
+ ->execute();
+ $files = mpull($files, null, 'getPHID');
+ } else {
+ $files = array();
+ }
+
+ foreach ($attachments as $key => $attachment) {
+ $file_phid = $attachment->getFilePHID();
+ $file = idx($files, $file_phid);
+
+ if ($this->visibleFiles && !$file) {
+ $this->didRejectResult($attachment);
+ unset($attachments[$key]);
+ continue;
+ }
+
+ $attachment->attachFile($file);
+ }
+ }
+
+ return $attachments;
+ }
+
+ protected function getPrimaryTableAlias() {
+ return 'attachments';
+ }
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorFilesApplication';
+ }
+
+}
diff --git a/src/applications/files/query/PhabricatorFileQuery.php b/src/applications/files/query/PhabricatorFileQuery.php
index c19574acaa..80e511b1e2 100644
--- a/src/applications/files/query/PhabricatorFileQuery.php
+++ b/src/applications/files/query/PhabricatorFileQuery.php
@@ -1,496 +1,546 @@
<?php
final class PhabricatorFileQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $authorPHIDs;
private $explicitUploads;
private $transforms;
private $dateCreatedAfter;
private $dateCreatedBefore;
private $contentHashes;
private $minLength;
private $maxLength;
private $names;
private $isPartial;
private $isDeleted;
private $needTransforms;
private $builtinKeys;
private $isBuiltin;
private $storageEngines;
+ private $attachedObjectPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withAuthorPHIDs(array $phids) {
$this->authorPHIDs = $phids;
return $this;
}
public function withDateCreatedBefore($date_created_before) {
$this->dateCreatedBefore = $date_created_before;
return $this;
}
public function withDateCreatedAfter($date_created_after) {
$this->dateCreatedAfter = $date_created_after;
return $this;
}
public function withContentHashes(array $content_hashes) {
$this->contentHashes = $content_hashes;
return $this;
}
public function withBuiltinKeys(array $keys) {
$this->builtinKeys = $keys;
return $this;
}
public function withIsBuiltin($is_builtin) {
$this->isBuiltin = $is_builtin;
return $this;
}
+ public function withAttachedObjectPHIDs(array $phids) {
+ $this->attachedObjectPHIDs = $phids;
+ return $this;
+ }
+
/**
* Select files which are transformations of some other file. For example,
* you can use this query to find previously generated thumbnails of an image
* file.
*
* As a parameter, provide a list of transformation specifications. Each
* specification is a dictionary with the keys `originalPHID` and `transform`.
* The `originalPHID` is the PHID of the original file (the file which was
* transformed) and the `transform` is the name of the transform to query
* for. If you pass `true` as the `transform`, all transformations of the
* file will be selected.
*
* For example:
*
* array(
* array(
* 'originalPHID' => 'PHID-FILE-aaaa',
* 'transform' => 'sepia',
* ),
* array(
* 'originalPHID' => 'PHID-FILE-bbbb',
* 'transform' => true,
* ),
* )
*
* This selects the `"sepia"` transformation of the file with PHID
* `PHID-FILE-aaaa` and all transformations of the file with PHID
* `PHID-FILE-bbbb`.
*
* @param list<dict> List of transform specifications, described above.
* @return this
*/
public function withTransforms(array $specs) {
foreach ($specs as $spec) {
if (!is_array($spec) ||
empty($spec['originalPHID']) ||
empty($spec['transform'])) {
throw new Exception(
pht(
"Transform specification must be a dictionary with keys ".
"'%s' and '%s'!",
'originalPHID',
'transform'));
}
}
$this->transforms = $specs;
return $this;
}
public function withLengthBetween($min, $max) {
$this->minLength = $min;
$this->maxLength = $max;
return $this;
}
public function withNames(array $names) {
$this->names = $names;
return $this;
}
public function withIsPartial($partial) {
$this->isPartial = $partial;
return $this;
}
public function withIsDeleted($deleted) {
$this->isDeleted = $deleted;
return $this;
}
public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint(
id(new PhabricatorFileNameNgrams()),
$ngrams);
}
public function withStorageEngines(array $engines) {
$this->storageEngines = $engines;
return $this;
}
public function showOnlyExplicitUploads($explicit_uploads) {
$this->explicitUploads = $explicit_uploads;
return $this;
}
public function needTransforms(array $transforms) {
$this->needTransforms = $transforms;
return $this;
}
public function newResultObject() {
return new PhabricatorFile();
}
protected function loadPage() {
$files = $this->loadStandardPage($this->newResultObject());
if (!$files) {
return $files;
}
// Figure out which files we need to load attached objects for. In most
// cases, we need to load attached objects to perform policy checks for
// files.
// However, in some special cases where we know files will always be
// visible, we skip this. See T8478 and T13106.
$need_objects = array();
$need_xforms = array();
foreach ($files as $file) {
$always_visible = false;
if ($file->getIsProfileImage()) {
$always_visible = true;
}
if ($file->isBuiltin()) {
$always_visible = true;
}
if ($always_visible) {
// We just treat these files as though they aren't attached to
// anything. This saves a query in common cases when we're loading
// profile images or builtins. We could be slightly more nuanced
// about this and distinguish between "not attached to anything" and
// "might be attached but policy checks don't need to care".
$file->attachObjectPHIDs(array());
continue;
}
$need_objects[] = $file;
$need_xforms[] = $file;
}
$viewer = $this->getViewer();
$is_omnipotent = $viewer->isOmnipotent();
// If we have any files left which do need objects, load the edges now.
$object_phids = array();
if ($need_objects) {
- $edge_type = PhabricatorFileHasObjectEdgeType::EDGECONST;
- $file_phids = mpull($need_objects, 'getPHID');
-
- $edges = id(new PhabricatorEdgeQuery())
- ->withSourcePHIDs($file_phids)
- ->withEdgeTypes(array($edge_type))
- ->execute();
+ $attachments_map = $this->newAttachmentsMap($need_objects);
foreach ($need_objects as $file) {
- $phids = array_keys($edges[$file->getPHID()][$edge_type]);
+ $file_phid = $file->getPHID();
+ $phids = $attachments_map[$file_phid];
+
$file->attachObjectPHIDs($phids);
if ($is_omnipotent) {
// If the viewer is omnipotent, we don't need to load the associated
// objects either since the viewer can certainly see the object.
// Skipping this can improve performance and prevent cycles. This
// could possibly become part of the profile/builtin code above which
// short circuits attacment policy checks in cases where we know them
// to be unnecessary.
continue;
}
foreach ($phids as $phid) {
$object_phids[$phid] = true;
}
}
}
// If this file is a transform of another file, load that file too. If you
// can see the original file, you can see the thumbnail.
// TODO: It might be nice to put this directly on PhabricatorFile and
// remove the PhabricatorTransformedFile table, which would be a little
// simpler.
if ($need_xforms) {
$xforms = id(new PhabricatorTransformedFile())->loadAllWhere(
'transformedPHID IN (%Ls)',
mpull($need_xforms, 'getPHID'));
$xform_phids = mpull($xforms, 'getOriginalPHID', 'getTransformedPHID');
foreach ($xform_phids as $derived_phid => $original_phid) {
$object_phids[$original_phid] = true;
}
} else {
$xform_phids = array();
}
$object_phids = array_keys($object_phids);
// Now, load the objects.
$objects = array();
if ($object_phids) {
// NOTE: We're explicitly turning policy exceptions off, since the rule
// here is "you can see the file if you can see ANY associated object".
// Without this explicit flag, we'll incorrectly throw unless you can
// see ALL associated objects.
$objects = id(new PhabricatorObjectQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($object_phids)
->setRaisePolicyExceptions(false)
->execute();
$objects = mpull($objects, null, 'getPHID');
}
foreach ($files as $file) {
$file_objects = array_select_keys($objects, $file->getObjectPHIDs());
$file->attachObjects($file_objects);
}
foreach ($files as $key => $file) {
$original_phid = idx($xform_phids, $file->getPHID());
if ($original_phid == PhabricatorPHIDConstants::PHID_VOID) {
// This is a special case for builtin files, which are handled
// oddly.
$original = null;
} else if ($original_phid) {
$original = idx($objects, $original_phid);
if (!$original) {
// If the viewer can't see the original file, also prevent them from
// seeing the transformed file.
$this->didRejectResult($file);
unset($files[$key]);
continue;
}
} else {
$original = null;
}
$file->attachOriginalFile($original);
}
return $files;
}
+ private function newAttachmentsMap(array $files) {
+ $file_phids = mpull($files, 'getPHID');
+
+ $attachments_table = new PhabricatorFileAttachment();
+ $attachments_conn = $attachments_table->establishConnection('r');
+
+ $attachments = queryfx_all(
+ $attachments_conn,
+ 'SELECT filePHID, objectPHID FROM %R WHERE filePHID IN (%Ls)
+ AND attachmentMode IN (%Ls)',
+ $attachments_table,
+ $file_phids,
+ array(
+ PhabricatorFileAttachment::MODE_ATTACH,
+ ));
+
+ $attachments_map = array_fill_keys($file_phids, array());
+ foreach ($attachments as $row) {
+ $file_phid = $row['filePHID'];
+ $object_phid = $row['objectPHID'];
+ $attachments_map[$file_phid][] = $object_phid;
+ }
+
+ return $attachments_map;
+ }
+
protected function didFilterPage(array $files) {
$xform_keys = $this->needTransforms;
if ($xform_keys !== null) {
$xforms = id(new PhabricatorTransformedFile())->loadAllWhere(
'originalPHID IN (%Ls) AND transform IN (%Ls)',
mpull($files, 'getPHID'),
$xform_keys);
if ($xforms) {
$xfiles = id(new PhabricatorFile())->loadAllWhere(
'phid IN (%Ls)',
mpull($xforms, 'getTransformedPHID'));
$xfiles = mpull($xfiles, null, 'getPHID');
}
$xform_map = array();
foreach ($xforms as $xform) {
$xfile = idx($xfiles, $xform->getTransformedPHID());
if (!$xfile) {
continue;
}
$original_phid = $xform->getOriginalPHID();
$xform_key = $xform->getTransform();
$xform_map[$original_phid][$xform_key] = $xfile;
}
$default_xforms = array_fill_keys($xform_keys, null);
foreach ($files as $file) {
$file_xforms = idx($xform_map, $file->getPHID(), array());
$file_xforms += $default_xforms;
$file->attachTransforms($file_xforms);
}
}
return $files;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->transforms) {
$joins[] = qsprintf(
$conn,
'JOIN %T t ON t.transformedPHID = f.phid',
id(new PhabricatorTransformedFile())->getTableName());
}
+ if ($this->shouldJoinAttachmentsTable()) {
+ $joins[] = qsprintf(
+ $conn,
+ 'JOIN %R attachments ON attachments.filePHID = f.phid
+ AND attachmentMode IN (%Ls)',
+ new PhabricatorFileAttachment(),
+ array(
+ PhabricatorFileAttachment::MODE_ATTACH,
+ ));
+ }
+
return $joins;
}
+ private function shouldJoinAttachmentsTable() {
+ return ($this->attachedObjectPHIDs !== null);
+ }
+
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'f.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'f.phid IN (%Ls)',
$this->phids);
}
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'f.authorPHID IN (%Ls)',
$this->authorPHIDs);
}
if ($this->explicitUploads !== null) {
$where[] = qsprintf(
$conn,
'f.isExplicitUpload = %d',
(int)$this->explicitUploads);
}
if ($this->transforms !== null) {
$clauses = array();
foreach ($this->transforms as $transform) {
if ($transform['transform'] === true) {
$clauses[] = qsprintf(
$conn,
'(t.originalPHID = %s)',
$transform['originalPHID']);
} else {
$clauses[] = qsprintf(
$conn,
'(t.originalPHID = %s AND t.transform = %s)',
$transform['originalPHID'],
$transform['transform']);
}
}
$where[] = qsprintf($conn, '%LO', $clauses);
}
if ($this->dateCreatedAfter !== null) {
$where[] = qsprintf(
$conn,
'f.dateCreated >= %d',
$this->dateCreatedAfter);
}
if ($this->dateCreatedBefore !== null) {
$where[] = qsprintf(
$conn,
'f.dateCreated <= %d',
$this->dateCreatedBefore);
}
if ($this->contentHashes !== null) {
$where[] = qsprintf(
$conn,
'f.contentHash IN (%Ls)',
$this->contentHashes);
}
if ($this->minLength !== null) {
$where[] = qsprintf(
$conn,
'byteSize >= %d',
$this->minLength);
}
if ($this->maxLength !== null) {
$where[] = qsprintf(
$conn,
'byteSize <= %d',
$this->maxLength);
}
if ($this->names !== null) {
$where[] = qsprintf(
$conn,
'name in (%Ls)',
$this->names);
}
if ($this->isPartial !== null) {
$where[] = qsprintf(
$conn,
'isPartial = %d',
(int)$this->isPartial);
}
if ($this->isDeleted !== null) {
$where[] = qsprintf(
$conn,
'isDeleted = %d',
(int)$this->isDeleted);
}
if ($this->builtinKeys !== null) {
$where[] = qsprintf(
$conn,
'builtinKey IN (%Ls)',
$this->builtinKeys);
}
if ($this->isBuiltin !== null) {
if ($this->isBuiltin) {
$where[] = qsprintf(
$conn,
'builtinKey IS NOT NULL');
} else {
$where[] = qsprintf(
$conn,
'builtinKey IS NULL');
}
}
if ($this->storageEngines !== null) {
$where[] = qsprintf(
$conn,
'storageEngine IN (%Ls)',
$this->storageEngines);
}
+ if ($this->attachedObjectPHIDs !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'attachments.objectPHID IN (%Ls)',
+ $this->attachedObjectPHIDs);
+ }
+
return $where;
}
protected function getPrimaryTableAlias() {
return 'f';
}
public function getQueryApplicationClass() {
return 'PhabricatorFilesApplication';
}
}
diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php
index c78d4e1941..cc1b701dcd 100644
--- a/src/applications/files/storage/PhabricatorFile.php
+++ b/src/applications/files/storage/PhabricatorFile.php
@@ -1,1767 +1,1764 @@
<?php
/**
* Parameters
* ==========
*
* When creating a new file using a method like @{method:newFromFileData}, these
* parameters are supported:
*
* | name | Human readable filename.
* | authorPHID | User PHID of uploader.
* | ttl.absolute | Temporary file lifetime as an epoch timestamp.
* | ttl.relative | Temporary file lifetime, relative to now, in seconds.
* | viewPolicy | File visibility policy.
* | isExplicitUpload | Used to show users files they explicitly uploaded.
* | canCDN | Allows the file to be cached and delivered over a CDN.
* | profile | Marks the file as a profile image.
* | format | Internal encoding format.
* | mime-type | Optional, explicit file MIME type.
* | builtin | Optional filename, identifies this as a builtin.
*
*/
final class PhabricatorFile extends PhabricatorFileDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorTokenReceiverInterface,
PhabricatorSubscribableInterface,
PhabricatorFlaggableInterface,
PhabricatorPolicyInterface,
PhabricatorDestructibleInterface,
PhabricatorConduitResultInterface,
PhabricatorIndexableInterface,
PhabricatorNgramsInterface {
const METADATA_IMAGE_WIDTH = 'width';
const METADATA_IMAGE_HEIGHT = 'height';
const METADATA_CAN_CDN = 'canCDN';
const METADATA_BUILTIN = 'builtin';
const METADATA_PARTIAL = 'partial';
const METADATA_PROFILE = 'profile';
const METADATA_STORAGE = 'storage';
const METADATA_INTEGRITY = 'integrity';
const METADATA_CHUNK = 'chunk';
const METADATA_ALT_TEXT = 'alt';
const STATUS_ACTIVE = 'active';
const STATUS_DELETED = 'deleted';
protected $name;
protected $mimeType;
protected $byteSize;
protected $authorPHID;
protected $secretKey;
protected $contentHash;
protected $metadata = array();
protected $mailKey;
protected $builtinKey;
protected $storageEngine;
protected $storageFormat;
protected $storageHandle;
protected $ttl;
protected $isExplicitUpload = 1;
protected $viewPolicy = PhabricatorPolicies::POLICY_USER;
protected $isPartial = 0;
protected $isDeleted = 0;
private $objects = self::ATTACHABLE;
private $objectPHIDs = self::ATTACHABLE;
private $originalFile = self::ATTACHABLE;
private $transforms = self::ATTACHABLE;
public static function initializeNewFile() {
$app = id(new PhabricatorApplicationQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withClasses(array('PhabricatorFilesApplication'))
->executeOne();
$view_policy = $app->getPolicy(
FilesDefaultViewCapability::CAPABILITY);
return id(new PhabricatorFile())
->setViewPolicy($view_policy)
->setIsPartial(0)
->attachOriginalFile(null)
->attachObjects(array())
->attachObjectPHIDs(array());
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'metadata' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'sort255?',
'mimeType' => 'text255?',
'byteSize' => 'uint64',
'storageEngine' => 'text32',
'storageFormat' => 'text32',
'storageHandle' => 'text255',
'authorPHID' => 'phid?',
'secretKey' => 'bytes20?',
'contentHash' => 'bytes64?',
'ttl' => 'epoch?',
'isExplicitUpload' => 'bool?',
'mailKey' => 'bytes20',
'isPartial' => 'bool',
'builtinKey' => 'text64?',
'isDeleted' => 'bool',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
),
'authorPHID' => array(
'columns' => array('authorPHID'),
),
'contentHash' => array(
'columns' => array('contentHash'),
),
'key_ttl' => array(
'columns' => array('ttl'),
),
'key_dateCreated' => array(
'columns' => array('dateCreated'),
),
'key_partial' => array(
'columns' => array('authorPHID', 'isPartial'),
),
'key_builtin' => array(
'columns' => array('builtinKey'),
'unique' => true,
),
'key_engine' => array(
'columns' => array('storageEngine', 'storageHandle(64)'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorFileFilePHIDType::TYPECONST);
}
public function save() {
if (!$this->getSecretKey()) {
$this->setSecretKey($this->generateSecretKey());
}
if (!$this->getMailKey()) {
$this->setMailKey(Filesystem::readRandomCharacters(20));
}
return parent::save();
}
public function saveAndIndex() {
$this->save();
if ($this->isIndexableFile()) {
PhabricatorSearchWorker::queueDocumentForIndexing($this->getPHID());
}
return $this;
}
private function isIndexableFile() {
if ($this->getIsChunk()) {
return false;
}
return true;
}
public function getMonogram() {
return 'F'.$this->getID();
}
public function scrambleSecret() {
return $this->setSecretKey($this->generateSecretKey());
}
public static function readUploadedFileData($spec) {
if (!$spec) {
throw new Exception(pht('No file was uploaded!'));
}
$err = idx($spec, 'error');
if ($err) {
throw new PhabricatorFileUploadException($err);
}
$tmp_name = idx($spec, 'tmp_name');
// NOTE: If we parsed the request body ourselves, the files we wrote will
// not be registered in the `is_uploaded_file()` list. It's fine to skip
// this check: it just protects against sloppy code from the long ago era
// of "register_globals".
if (ini_get('enable_post_data_reading')) {
$is_valid = @is_uploaded_file($tmp_name);
if (!$is_valid) {
throw new Exception(pht('File is not an uploaded file.'));
}
}
$file_data = Filesystem::readFile($tmp_name);
$file_size = idx($spec, 'size');
if (strlen($file_data) != $file_size) {
throw new Exception(pht('File size disagrees with uploaded size.'));
}
return $file_data;
}
public static function newFromPHPUpload($spec, array $params = array()) {
$file_data = self::readUploadedFileData($spec);
$file_name = nonempty(
idx($params, 'name'),
idx($spec, 'name'));
$params = array(
'name' => $file_name,
) + $params;
return self::newFromFileData($file_data, $params);
}
public static function newFromXHRUpload($data, array $params = array()) {
return self::newFromFileData($data, $params);
}
public static function newFileFromContentHash($hash, array $params) {
if ($hash === null) {
return null;
}
// Check to see if a file with same hash already exists.
$file = id(new PhabricatorFile())->loadOneWhere(
'contentHash = %s LIMIT 1',
$hash);
if (!$file) {
return null;
}
$copy_of_storage_engine = $file->getStorageEngine();
$copy_of_storage_handle = $file->getStorageHandle();
$copy_of_storage_format = $file->getStorageFormat();
$copy_of_storage_properties = $file->getStorageProperties();
$copy_of_byte_size = $file->getByteSize();
$copy_of_mime_type = $file->getMimeType();
$new_file = self::initializeNewFile();
$new_file->setByteSize($copy_of_byte_size);
$new_file->setContentHash($hash);
$new_file->setStorageEngine($copy_of_storage_engine);
$new_file->setStorageHandle($copy_of_storage_handle);
$new_file->setStorageFormat($copy_of_storage_format);
$new_file->setStorageProperties($copy_of_storage_properties);
$new_file->setMimeType($copy_of_mime_type);
$new_file->copyDimensions($file);
$new_file->readPropertiesFromParameters($params);
$new_file->saveAndIndex();
return $new_file;
}
public static function newChunkedFile(
PhabricatorFileStorageEngine $engine,
$length,
array $params) {
$file = self::initializeNewFile();
$file->setByteSize($length);
// NOTE: Once we receive the first chunk, we'll detect its MIME type and
// update the parent file if a MIME type hasn't been provided. This matters
// for large media files like video.
$mime_type = idx($params, 'mime-type');
if (!strlen($mime_type)) {
$file->setMimeType('application/octet-stream');
}
$chunked_hash = idx($params, 'chunkedHash');
// Get rid of this parameter now; we aren't passing it any further down
// the stack.
unset($params['chunkedHash']);
if ($chunked_hash) {
$file->setContentHash($chunked_hash);
} else {
// See PhabricatorChunkedFileStorageEngine::getChunkedHash() for some
// discussion of this.
$seed = Filesystem::readRandomBytes(64);
$hash = PhabricatorChunkedFileStorageEngine::getChunkedHashForInput(
$seed);
$file->setContentHash($hash);
}
$file->setStorageEngine($engine->getEngineIdentifier());
$file->setStorageHandle(PhabricatorFileChunk::newChunkHandle());
// Chunked files are always stored raw because they do not actually store
// data. The chunks do, and can be individually formatted.
$file->setStorageFormat(PhabricatorFileRawStorageFormat::FORMATKEY);
$file->setIsPartial(1);
$file->readPropertiesFromParameters($params);
return $file;
}
private static function buildFromFileData($data, array $params = array()) {
if (isset($params['storageEngines'])) {
$engines = $params['storageEngines'];
} else {
$size = strlen($data);
$engines = PhabricatorFileStorageEngine::loadStorageEngines($size);
if (!$engines) {
throw new Exception(
pht(
'No configured storage engine can store this file. See '.
'"Configuring File Storage" in the documentation for '.
'information on configuring storage engines.'));
}
}
assert_instances_of($engines, 'PhabricatorFileStorageEngine');
if (!$engines) {
throw new Exception(pht('No valid storage engines are available!'));
}
$file = self::initializeNewFile();
$aes_type = PhabricatorFileAES256StorageFormat::FORMATKEY;
$has_aes = PhabricatorKeyring::getDefaultKeyName($aes_type);
if ($has_aes !== null) {
$default_key = PhabricatorFileAES256StorageFormat::FORMATKEY;
} else {
$default_key = PhabricatorFileRawStorageFormat::FORMATKEY;
}
$key = idx($params, 'format', $default_key);
// Callers can pass in an object explicitly instead of a key. This is
// primarily useful for unit tests.
if ($key instanceof PhabricatorFileStorageFormat) {
$format = clone $key;
} else {
$format = clone PhabricatorFileStorageFormat::requireFormat($key);
}
$format->setFile($file);
$properties = $format->newStorageProperties();
$file->setStorageFormat($format->getStorageFormatKey());
$file->setStorageProperties($properties);
$data_handle = null;
$engine_identifier = null;
$integrity_hash = null;
$exceptions = array();
foreach ($engines as $engine) {
$engine_class = get_class($engine);
try {
$result = $file->writeToEngine(
$engine,
$data,
$params);
list($engine_identifier, $data_handle, $integrity_hash) = $result;
// We stored the file somewhere so stop trying to write it to other
// places.
break;
} catch (PhabricatorFileStorageConfigurationException $ex) {
// If an engine is outright misconfigured (or misimplemented), raise
// that immediately since it probably needs attention.
throw $ex;
} catch (Exception $ex) {
phlog($ex);
// If an engine doesn't work, keep trying all the other valid engines
// in case something else works.
$exceptions[$engine_class] = $ex;
}
}
if (!$data_handle) {
throw new PhutilAggregateException(
pht('All storage engines failed to write file:'),
$exceptions);
}
$file->setByteSize(strlen($data));
$hash = self::hashFileContent($data);
$file->setContentHash($hash);
$file->setStorageEngine($engine_identifier);
$file->setStorageHandle($data_handle);
$file->setIntegrityHash($integrity_hash);
$file->readPropertiesFromParameters($params);
if (!$file->getMimeType()) {
$tmp = new TempFile();
Filesystem::writeFile($tmp, $data);
$file->setMimeType(Filesystem::getMimeType($tmp));
unset($tmp);
}
try {
$file->updateDimensions(false);
} catch (Exception $ex) {
// Do nothing.
}
$file->saveAndIndex();
return $file;
}
public static function newFromFileData($data, array $params = array()) {
$hash = self::hashFileContent($data);
if ($hash !== null) {
$file = self::newFileFromContentHash($hash, $params);
if ($file) {
return $file;
}
}
return self::buildFromFileData($data, $params);
}
public function migrateToEngine(
PhabricatorFileStorageEngine $engine,
$make_copy) {
if (!$this->getID() || !$this->getStorageHandle()) {
throw new Exception(
pht("You can not migrate a file which hasn't yet been saved."));
}
$data = $this->loadFileData();
$params = array(
'name' => $this->getName(),
);
list($new_identifier, $new_handle, $integrity_hash) = $this->writeToEngine(
$engine,
$data,
$params);
$old_engine = $this->instantiateStorageEngine();
$old_identifier = $this->getStorageEngine();
$old_handle = $this->getStorageHandle();
$this->setStorageEngine($new_identifier);
$this->setStorageHandle($new_handle);
$this->setIntegrityHash($integrity_hash);
$this->save();
if (!$make_copy) {
$this->deleteFileDataIfUnused(
$old_engine,
$old_identifier,
$old_handle);
}
return $this;
}
public function migrateToStorageFormat(PhabricatorFileStorageFormat $format) {
if (!$this->getID() || !$this->getStorageHandle()) {
throw new Exception(
pht("You can not migrate a file which hasn't yet been saved."));
}
$data = $this->loadFileData();
$params = array(
'name' => $this->getName(),
);
$engine = $this->instantiateStorageEngine();
$old_handle = $this->getStorageHandle();
$properties = $format->newStorageProperties();
$this->setStorageFormat($format->getStorageFormatKey());
$this->setStorageProperties($properties);
list($identifier, $new_handle, $integrity_hash) = $this->writeToEngine(
$engine,
$data,
$params);
$this->setStorageHandle($new_handle);
$this->setIntegrityHash($integrity_hash);
$this->save();
$this->deleteFileDataIfUnused(
$engine,
$identifier,
$old_handle);
return $this;
}
public function cycleMasterStorageKey(PhabricatorFileStorageFormat $format) {
if (!$this->getID() || !$this->getStorageHandle()) {
throw new Exception(
pht("You can not cycle keys for a file which hasn't yet been saved."));
}
$properties = $format->cycleStorageProperties();
$this->setStorageProperties($properties);
$this->save();
return $this;
}
private function writeToEngine(
PhabricatorFileStorageEngine $engine,
$data,
array $params) {
$engine_class = get_class($engine);
$format = $this->newStorageFormat();
$data_iterator = array($data);
$formatted_iterator = $format->newWriteIterator($data_iterator);
$formatted_data = $this->loadDataFromIterator($formatted_iterator);
$integrity_hash = $engine->newIntegrityHash($formatted_data, $format);
$data_handle = $engine->writeFile($formatted_data, $params);
if (!$data_handle || strlen($data_handle) > 255) {
// This indicates an improperly implemented storage engine.
throw new PhabricatorFileStorageConfigurationException(
pht(
"Storage engine '%s' executed %s but did not return a valid ".
"handle ('%s') to the data: it must be nonempty and no longer ".
"than 255 characters.",
$engine_class,
'writeFile()',
$data_handle));
}
$engine_identifier = $engine->getEngineIdentifier();
if (!$engine_identifier || strlen($engine_identifier) > 32) {
throw new PhabricatorFileStorageConfigurationException(
pht(
"Storage engine '%s' returned an improper engine identifier '{%s}': ".
"it must be nonempty and no longer than 32 characters.",
$engine_class,
$engine_identifier));
}
return array($engine_identifier, $data_handle, $integrity_hash);
}
/**
* Download a remote resource over HTTP and save the response body as a file.
*
* This method respects `security.outbound-blacklist`, and protects against
* HTTP redirection (by manually following "Location" headers and verifying
* each destination). It does not protect against DNS rebinding. See
* discussion in T6755.
*/
public static function newFromFileDownload($uri, array $params = array()) {
$timeout = 5;
$redirects = array();
$current = $uri;
while (true) {
try {
if (count($redirects) > 10) {
throw new Exception(
pht('Too many redirects trying to fetch remote URI.'));
}
$resolved = PhabricatorEnv::requireValidRemoteURIForFetch(
$current,
array(
'http',
'https',
));
list($resolved_uri, $resolved_domain) = $resolved;
$current = new PhutilURI($current);
if ($current->getProtocol() == 'http') {
// For HTTP, we can use a pre-resolved URI to defuse DNS rebinding.
$fetch_uri = $resolved_uri;
$fetch_host = $resolved_domain;
} else {
// For HTTPS, we can't: cURL won't verify the SSL certificate if
// the domain has been replaced with an IP. But internal services
// presumably will not have valid certificates for rebindable
// domain names on attacker-controlled domains, so the DNS rebinding
// attack should generally not be possible anyway.
$fetch_uri = $current;
$fetch_host = null;
}
$future = id(new HTTPSFuture($fetch_uri))
->setFollowLocation(false)
->setTimeout($timeout);
if ($fetch_host !== null) {
$future->addHeader('Host', $fetch_host);
}
list($status, $body, $headers) = $future->resolve();
if ($status->isRedirect()) {
// This is an HTTP 3XX status, so look for a "Location" header.
$location = null;
foreach ($headers as $header) {
list($name, $value) = $header;
if (phutil_utf8_strtolower($name) == 'location') {
$location = $value;
break;
}
}
// HTTP 3XX status with no "Location" header, just treat this like
// a normal HTTP error.
if ($location === null) {
throw $status;
}
if (isset($redirects[$location])) {
throw new Exception(
pht('Encountered loop while following redirects.'));
}
$redirects[$location] = $location;
$current = $location;
// We'll fall off the bottom and go try this URI now.
} else if ($status->isError()) {
// This is something other than an HTTP 2XX or HTTP 3XX status, so
// just bail out.
throw $status;
} else {
// This is HTTP 2XX, so use the response body to save the file data.
// Provide a default name based on the URI, truncating it if the URI
// is exceptionally long.
$default_name = basename($uri);
$default_name = id(new PhutilUTF8StringTruncator())
->setMaximumBytes(64)
->truncateString($default_name);
$params = $params + array(
'name' => $default_name,
);
return self::newFromFileData($body, $params);
}
} catch (Exception $ex) {
if ($redirects) {
throw new PhutilProxyException(
pht(
'Failed to fetch remote URI "%s" after following %s redirect(s) '.
'(%s): %s',
$uri,
phutil_count($redirects),
implode(' > ', array_keys($redirects)),
$ex->getMessage()),
$ex);
} else {
throw $ex;
}
}
}
}
public static function normalizeFileName($file_name) {
$pattern = "@[\\x00-\\x19#%&+!~'\$\"\/=\\\\?<> ]+@";
$file_name = preg_replace($pattern, '_', $file_name);
$file_name = preg_replace('@_+@', '_', $file_name);
$file_name = trim($file_name, '_');
$disallowed_filenames = array(
'.' => 'dot',
'..' => 'dotdot',
'' => 'file',
);
$file_name = idx($disallowed_filenames, $file_name, $file_name);
return $file_name;
}
public function delete() {
// We want to delete all the rows which mark this file as the transformation
// of some other file (since we're getting rid of it). We also delete all
// the transformations of this file, so that a user who deletes an image
// doesn't need to separately hunt down and delete a bunch of thumbnails and
// resizes of it.
$outbound_xforms = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withTransforms(
array(
array(
'originalPHID' => $this->getPHID(),
'transform' => true,
),
))
->execute();
foreach ($outbound_xforms as $outbound_xform) {
$outbound_xform->delete();
}
$inbound_xforms = id(new PhabricatorTransformedFile())->loadAllWhere(
'transformedPHID = %s',
$this->getPHID());
$this->openTransaction();
foreach ($inbound_xforms as $inbound_xform) {
$inbound_xform->delete();
}
$ret = parent::delete();
$this->saveTransaction();
$this->deleteFileDataIfUnused(
$this->instantiateStorageEngine(),
$this->getStorageEngine(),
$this->getStorageHandle());
return $ret;
}
/**
* Destroy stored file data if there are no remaining files which reference
* it.
*/
public function deleteFileDataIfUnused(
PhabricatorFileStorageEngine $engine,
$engine_identifier,
$handle) {
// Check to see if any files are using storage.
$usage = id(new PhabricatorFile())->loadAllWhere(
'storageEngine = %s AND storageHandle = %s LIMIT 1',
$engine_identifier,
$handle);
// If there are no files using the storage, destroy the actual storage.
if (!$usage) {
try {
$engine->deleteFile($handle);
} catch (Exception $ex) {
// In the worst case, we're leaving some data stranded in a storage
// engine, which is not a big deal.
phlog($ex);
}
}
}
public static function hashFileContent($data) {
// NOTE: Hashing can fail if the algorithm isn't available in the current
// build of PHP. It's fine if we're unable to generate a content hash:
// it just means we'll store extra data when users upload duplicate files
// instead of being able to deduplicate it.
$hash = hash('sha256', $data, $raw_output = false);
if ($hash === false) {
return null;
}
return $hash;
}
public function loadFileData() {
$iterator = $this->getFileDataIterator();
return $this->loadDataFromIterator($iterator);
}
/**
* Return an iterable which emits file content bytes.
*
* @param int Offset for the start of data.
* @param int Offset for the end of data.
* @return Iterable Iterable object which emits requested data.
*/
public function getFileDataIterator($begin = null, $end = null) {
$engine = $this->instantiateStorageEngine();
$format = $this->newStorageFormat();
$iterator = $engine->getRawFileDataIterator(
$this,
$begin,
$end,
$format);
return $iterator;
}
public function getURI() {
return $this->getInfoURI();
}
public function getViewURI() {
if (!$this->getPHID()) {
throw new Exception(
pht('You must save a file before you can generate a view URI.'));
}
return $this->getCDNURI('data');
}
public function getCDNURI($request_kind) {
if (($request_kind !== 'data') &&
($request_kind !== 'download')) {
throw new Exception(
pht(
'Unknown file content request kind "%s".',
$request_kind));
}
$name = self::normalizeFileName($this->getName());
$name = phutil_escape_uri($name);
$parts = array();
$parts[] = 'file';
$parts[] = $request_kind;
// If this is an instanced install, add the instance identifier to the URI.
// Instanced configurations behind a CDN may not be able to control the
// request domain used by the CDN (as with AWS CloudFront). Embedding the
// instance identity in the path allows us to distinguish between requests
// originating from different instances but served through the same CDN.
$instance = PhabricatorEnv::getEnvConfig('cluster.instance');
if (strlen($instance)) {
$parts[] = '@'.$instance;
}
$parts[] = $this->getSecretKey();
$parts[] = $this->getPHID();
$parts[] = $name;
$path = '/'.implode('/', $parts);
// If this file is only partially uploaded, we're just going to return a
// local URI to make sure that Ajax works, since the page is inevitably
// going to give us an error back.
if ($this->getIsPartial()) {
return PhabricatorEnv::getURI($path);
} else {
return PhabricatorEnv::getCDNURI($path);
}
}
public function getInfoURI() {
return '/'.$this->getMonogram();
}
public function getBestURI() {
if ($this->isViewableInBrowser()) {
return $this->getViewURI();
} else {
return $this->getInfoURI();
}
}
public function getDownloadURI() {
return $this->getCDNURI('download');
}
public function getURIForTransform(PhabricatorFileTransform $transform) {
return $this->getTransformedURI($transform->getTransformKey());
}
private function getTransformedURI($transform) {
$parts = array();
$parts[] = 'file';
$parts[] = 'xform';
$instance = PhabricatorEnv::getEnvConfig('cluster.instance');
if (strlen($instance)) {
$parts[] = '@'.$instance;
}
$parts[] = $transform;
$parts[] = $this->getPHID();
$parts[] = $this->getSecretKey();
$path = implode('/', $parts);
$path = $path.'/';
return PhabricatorEnv::getCDNURI($path);
}
public function isViewableInBrowser() {
return ($this->getViewableMimeType() !== null);
}
public function isViewableImage() {
if (!$this->isViewableInBrowser()) {
return false;
}
$mime_map = PhabricatorEnv::getEnvConfig('files.image-mime-types');
$mime_type = $this->getMimeType();
return idx($mime_map, $mime_type);
}
public function isAudio() {
if (!$this->isViewableInBrowser()) {
return false;
}
$mime_map = PhabricatorEnv::getEnvConfig('files.audio-mime-types');
$mime_type = $this->getMimeType();
return idx($mime_map, $mime_type);
}
public function isVideo() {
if (!$this->isViewableInBrowser()) {
return false;
}
$mime_map = PhabricatorEnv::getEnvConfig('files.video-mime-types');
$mime_type = $this->getMimeType();
return idx($mime_map, $mime_type);
}
public function isPDF() {
if (!$this->isViewableInBrowser()) {
return false;
}
$mime_map = array(
'application/pdf' => 'application/pdf',
);
$mime_type = $this->getMimeType();
return idx($mime_map, $mime_type);
}
public function isTransformableImage() {
// NOTE: The way the 'gd' extension works in PHP is that you can install it
// with support for only some file types, so it might be able to handle
// PNG but not JPEG. Try to generate thumbnails for whatever we can. Setup
// warns you if you don't have complete support.
$matches = null;
$ok = preg_match(
'@^image/(gif|png|jpe?g)@',
$this->getViewableMimeType(),
$matches);
if (!$ok) {
return false;
}
switch ($matches[1]) {
case 'jpg';
case 'jpeg':
return function_exists('imagejpeg');
break;
case 'png':
return function_exists('imagepng');
break;
case 'gif':
return function_exists('imagegif');
break;
default:
throw new Exception(pht('Unknown type matched as image MIME type.'));
}
}
public static function getTransformableImageFormats() {
$supported = array();
if (function_exists('imagejpeg')) {
$supported[] = 'jpg';
}
if (function_exists('imagepng')) {
$supported[] = 'png';
}
if (function_exists('imagegif')) {
$supported[] = 'gif';
}
return $supported;
}
public function getDragAndDropDictionary() {
return array(
'id' => $this->getID(),
'phid' => $this->getPHID(),
'uri' => $this->getBestURI(),
);
}
public function instantiateStorageEngine() {
return self::buildEngine($this->getStorageEngine());
}
public static function buildEngine($engine_identifier) {
$engines = self::buildAllEngines();
foreach ($engines as $engine) {
if ($engine->getEngineIdentifier() == $engine_identifier) {
return $engine;
}
}
throw new Exception(
pht(
"Storage engine '%s' could not be located!",
$engine_identifier));
}
public static function buildAllEngines() {
return id(new PhutilClassMapQuery())
->setAncestorClass('PhabricatorFileStorageEngine')
->execute();
}
public function getViewableMimeType() {
$mime_map = PhabricatorEnv::getEnvConfig('files.viewable-mime-types');
$mime_type = $this->getMimeType();
$mime_parts = explode(';', $mime_type);
$mime_type = trim(reset($mime_parts));
return idx($mime_map, $mime_type);
}
public function getDisplayIconForMimeType() {
$mime_map = PhabricatorEnv::getEnvConfig('files.icon-mime-types');
$mime_type = $this->getMimeType();
return idx($mime_map, $mime_type, 'fa-file-o');
}
public function validateSecretKey($key) {
return ($key == $this->getSecretKey());
}
public function generateSecretKey() {
return Filesystem::readRandomCharacters(20);
}
public function setStorageProperties(array $properties) {
$this->metadata[self::METADATA_STORAGE] = $properties;
return $this;
}
public function getStorageProperties() {
return idx($this->metadata, self::METADATA_STORAGE, array());
}
public function getStorageProperty($key, $default = null) {
$properties = $this->getStorageProperties();
return idx($properties, $key, $default);
}
public function loadDataFromIterator($iterator) {
$result = '';
foreach ($iterator as $chunk) {
$result .= $chunk;
}
return $result;
}
public function updateDimensions($save = true) {
if (!$this->isViewableImage()) {
throw new Exception(pht('This file is not a viewable image.'));
}
if (!function_exists('imagecreatefromstring')) {
throw new Exception(pht('Cannot retrieve image information.'));
}
if ($this->getIsChunk()) {
throw new Exception(
pht('Refusing to assess image dimensions of file chunk.'));
}
$engine = $this->instantiateStorageEngine();
if ($engine->isChunkEngine()) {
throw new Exception(
pht('Refusing to assess image dimensions of chunked file.'));
}
$data = $this->loadFileData();
$img = @imagecreatefromstring($data);
if ($img === false) {
throw new Exception(pht('Error when decoding image.'));
}
$this->metadata[self::METADATA_IMAGE_WIDTH] = imagesx($img);
$this->metadata[self::METADATA_IMAGE_HEIGHT] = imagesy($img);
if ($save) {
$this->save();
}
return $this;
}
public function copyDimensions(PhabricatorFile $file) {
$metadata = $file->getMetadata();
$width = idx($metadata, self::METADATA_IMAGE_WIDTH);
if ($width) {
$this->metadata[self::METADATA_IMAGE_WIDTH] = $width;
}
$height = idx($metadata, self::METADATA_IMAGE_HEIGHT);
if ($height) {
$this->metadata[self::METADATA_IMAGE_HEIGHT] = $height;
}
return $this;
}
/**
* Load (or build) the {@class:PhabricatorFile} objects for builtin file
* resources. The builtin mechanism allows files shipped with Phabricator
* to be treated like normal files so that APIs do not need to special case
* things like default images or deleted files.
*
* Builtins are located in `resources/builtin/` and identified by their
* name.
*
* @param PhabricatorUser Viewing user.
* @param list<PhabricatorFilesBuiltinFile> List of builtin file specs.
* @return dict<string, PhabricatorFile> Dictionary of named builtins.
*/
public static function loadBuiltins(PhabricatorUser $user, array $builtins) {
$builtins = mpull($builtins, null, 'getBuiltinFileKey');
// NOTE: Anyone is allowed to access builtin files.
$files = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withBuiltinKeys(array_keys($builtins))
->execute();
$results = array();
foreach ($files as $file) {
$builtin_key = $file->getBuiltinName();
if ($builtin_key !== null) {
$results[$builtin_key] = $file;
}
}
$build = array();
foreach ($builtins as $key => $builtin) {
if (isset($results[$key])) {
continue;
}
$data = $builtin->loadBuiltinFileData();
$params = array(
'name' => $builtin->getBuiltinDisplayName(),
'canCDN' => true,
'builtin' => $key,
);
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
try {
$file = self::newFromFileData($data, $params);
} catch (AphrontDuplicateKeyQueryException $ex) {
$file = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withBuiltinKeys(array($key))
->executeOne();
if (!$file) {
throw new Exception(
pht(
'Collided mid-air when generating builtin file "%s", but '.
'then failed to load the object we collided with.',
$key));
}
}
unset($unguarded);
$file->attachObjectPHIDs(array());
$file->attachObjects(array());
$results[$key] = $file;
}
return $results;
}
/**
* Convenience wrapper for @{method:loadBuiltins}.
*
* @param PhabricatorUser Viewing user.
* @param string Single builtin name to load.
* @return PhabricatorFile Corresponding builtin file.
*/
public static function loadBuiltin(PhabricatorUser $user, $name) {
$builtin = id(new PhabricatorFilesOnDiskBuiltinFile())
->setName($name);
$key = $builtin->getBuiltinFileKey();
return idx(self::loadBuiltins($user, array($builtin)), $key);
}
public function getObjects() {
return $this->assertAttached($this->objects);
}
public function attachObjects(array $objects) {
$this->objects = $objects;
return $this;
}
public function getObjectPHIDs() {
return $this->assertAttached($this->objectPHIDs);
}
public function attachObjectPHIDs(array $object_phids) {
$this->objectPHIDs = $object_phids;
return $this;
}
public function getOriginalFile() {
return $this->assertAttached($this->originalFile);
}
public function attachOriginalFile(PhabricatorFile $file = null) {
$this->originalFile = $file;
return $this;
}
public function getImageHeight() {
if (!$this->isViewableImage()) {
return null;
}
return idx($this->metadata, self::METADATA_IMAGE_HEIGHT);
}
public function getImageWidth() {
if (!$this->isViewableImage()) {
return null;
}
return idx($this->metadata, self::METADATA_IMAGE_WIDTH);
}
public function getAltText() {
$alt = $this->getCustomAltText();
if (strlen($alt)) {
return $alt;
}
return $this->getDefaultAltText();
}
public function getCustomAltText() {
return idx($this->metadata, self::METADATA_ALT_TEXT);
}
public function setCustomAltText($value) {
$value = phutil_string_cast($value);
if (!strlen($value)) {
$value = null;
}
if ($value === null) {
unset($this->metadata[self::METADATA_ALT_TEXT]);
} else {
$this->metadata[self::METADATA_ALT_TEXT] = $value;
}
return $this;
}
public function getDefaultAltText() {
$parts = array();
$name = $this->getName();
if (strlen($name)) {
$parts[] = $name;
}
$stats = array();
$image_x = $this->getImageHeight();
$image_y = $this->getImageWidth();
if ($image_x && $image_y) {
$stats[] = pht(
"%d\xC3\x97%d px",
new PhutilNumber($image_x),
new PhutilNumber($image_y));
}
$bytes = $this->getByteSize();
if ($bytes) {
$stats[] = phutil_format_bytes($bytes);
}
if ($stats) {
$parts[] = pht('(%s)', implode(', ', $stats));
}
if (!$parts) {
return null;
}
return implode(' ', $parts);
}
public function getCanCDN() {
if (!$this->isViewableImage()) {
return false;
}
return idx($this->metadata, self::METADATA_CAN_CDN);
}
public function setCanCDN($can_cdn) {
$this->metadata[self::METADATA_CAN_CDN] = $can_cdn ? 1 : 0;
return $this;
}
public function isBuiltin() {
return ($this->getBuiltinName() !== null);
}
public function getBuiltinName() {
return idx($this->metadata, self::METADATA_BUILTIN);
}
public function setBuiltinName($name) {
$this->metadata[self::METADATA_BUILTIN] = $name;
return $this;
}
public function getIsProfileImage() {
return idx($this->metadata, self::METADATA_PROFILE);
}
public function setIsProfileImage($value) {
$this->metadata[self::METADATA_PROFILE] = $value;
return $this;
}
public function getIsChunk() {
return idx($this->metadata, self::METADATA_CHUNK);
}
public function setIsChunk($value) {
$this->metadata[self::METADATA_CHUNK] = $value;
return $this;
}
public function setIntegrityHash($integrity_hash) {
$this->metadata[self::METADATA_INTEGRITY] = $integrity_hash;
return $this;
}
public function getIntegrityHash() {
return idx($this->metadata, self::METADATA_INTEGRITY);
}
public function newIntegrityHash() {
$engine = $this->instantiateStorageEngine();
if ($engine->isChunkEngine()) {
return null;
}
$format = $this->newStorageFormat();
$storage_handle = $this->getStorageHandle();
$data = $engine->readFile($storage_handle);
return $engine->newIntegrityHash($data, $format);
}
/**
* Write the policy edge between this file and some object.
*
* @param phid Object PHID to attach to.
* @return this
*/
public function attachToObject($phid) {
- $edge_type = PhabricatorObjectHasFileEdgeType::EDGECONST;
-
- id(new PhabricatorEdgeEditor())
- ->addEdge($phid, $edge_type, $this->getPHID())
- ->save();
-
- return $this;
- }
-
-
- /**
- * Remove the policy edge between this file and some object.
- *
- * @param phid Object PHID to detach from.
- * @return this
- */
- public function detachFromObject($phid) {
- $edge_type = PhabricatorObjectHasFileEdgeType::EDGECONST;
-
- id(new PhabricatorEdgeEditor())
- ->removeEdge($phid, $edge_type, $this->getPHID())
- ->save();
+ $attachment_table = new PhabricatorFileAttachment();
+ $attachment_conn = $attachment_table->establishConnection('w');
+
+ queryfx(
+ $attachment_conn,
+ 'INSERT INTO %R (objectPHID, filePHID, attachmentMode,
+ attacherPHID, dateCreated, dateModified)
+ VALUES (%s, %s, %s, %ns, %d, %d)
+ ON DUPLICATE KEY UPDATE
+ attachmentMode = VALUES(attachmentMode),
+ attacherPHID = VALUES(attacherPHID),
+ dateModified = VALUES(dateModified)',
+ $attachment_table,
+ $phid,
+ $this->getPHID(),
+ PhabricatorFileAttachment::MODE_ATTACH,
+ null,
+ PhabricatorTime::getNow(),
+ PhabricatorTime::getNow());
return $this;
}
/**
* Configure a newly created file object according to specified parameters.
*
* This method is called both when creating a file from fresh data, and
* when creating a new file which reuses existing storage.
*
* @param map<string, wild> Bag of parameters, see @{class:PhabricatorFile}
* for documentation.
* @return this
*/
private function readPropertiesFromParameters(array $params) {
PhutilTypeSpec::checkMap(
$params,
array(
'name' => 'optional string',
'authorPHID' => 'optional string',
'ttl.relative' => 'optional int',
'ttl.absolute' => 'optional int',
'viewPolicy' => 'optional string',
'isExplicitUpload' => 'optional bool',
'canCDN' => 'optional bool',
'profile' => 'optional bool',
'format' => 'optional string|PhabricatorFileStorageFormat',
'mime-type' => 'optional string',
'builtin' => 'optional string',
'storageEngines' => 'optional list<PhabricatorFileStorageEngine>',
'chunk' => 'optional bool',
));
$file_name = idx($params, 'name');
$this->setName($file_name);
$author_phid = idx($params, 'authorPHID');
$this->setAuthorPHID($author_phid);
$absolute_ttl = idx($params, 'ttl.absolute');
$relative_ttl = idx($params, 'ttl.relative');
if ($absolute_ttl !== null && $relative_ttl !== null) {
throw new Exception(
pht(
'Specify an absolute TTL or a relative TTL, but not both.'));
} else if ($absolute_ttl !== null) {
if ($absolute_ttl < PhabricatorTime::getNow()) {
throw new Exception(
pht(
'Absolute TTL must be in the present or future, but TTL "%s" '.
'is in the past.',
$absolute_ttl));
}
$this->setTtl($absolute_ttl);
} else if ($relative_ttl !== null) {
if ($relative_ttl < 0) {
throw new Exception(
pht(
'Relative TTL must be zero or more seconds, but "%s" is '.
'negative.',
$relative_ttl));
}
$max_relative = phutil_units('365 days in seconds');
if ($relative_ttl > $max_relative) {
throw new Exception(
pht(
'Relative TTL must not be more than "%s" seconds, but TTL '.
'"%s" was specified.',
$max_relative,
$relative_ttl));
}
$absolute_ttl = PhabricatorTime::getNow() + $relative_ttl;
$this->setTtl($absolute_ttl);
}
$view_policy = idx($params, 'viewPolicy');
if ($view_policy) {
$this->setViewPolicy($params['viewPolicy']);
}
$is_explicit = (idx($params, 'isExplicitUpload') ? 1 : 0);
$this->setIsExplicitUpload($is_explicit);
$can_cdn = idx($params, 'canCDN');
if ($can_cdn) {
$this->setCanCDN(true);
}
$builtin = idx($params, 'builtin');
if ($builtin) {
$this->setBuiltinName($builtin);
$this->setBuiltinKey($builtin);
}
$profile = idx($params, 'profile');
if ($profile) {
$this->setIsProfileImage(true);
}
$mime_type = idx($params, 'mime-type');
if ($mime_type) {
$this->setMimeType($mime_type);
}
$is_chunk = idx($params, 'chunk');
if ($is_chunk) {
$this->setIsChunk(true);
}
return $this;
}
public function getRedirectResponse() {
$uri = $this->getBestURI();
// TODO: This is a bit iffy. Sometimes, getBestURI() returns a CDN URI
// (if the file is a viewable image) and sometimes a local URI (if not).
// For now, just detect which one we got and configure the response
// appropriately. In the long run, if this endpoint is served from a CDN
// domain, we can't issue a local redirect to an info URI (which is not
// present on the CDN domain). We probably never actually issue local
// redirects here anyway, since we only ever transform viewable images
// right now.
$is_external = strlen(id(new PhutilURI($uri))->getDomain());
return id(new AphrontRedirectResponse())
->setIsExternal($is_external)
->setURI($uri);
}
public function newDownloadResponse() {
// We're cheating a little bit here and relying on the fact that
// getDownloadURI() always returns a fully qualified URI with a complete
// domain.
return id(new AphrontRedirectResponse())
->setIsExternal(true)
->setCloseDialogBeforeRedirect(true)
->setURI($this->getDownloadURI());
}
public function attachTransforms(array $map) {
$this->transforms = $map;
return $this;
}
public function getTransform($key) {
return $this->assertAttachedKey($this->transforms, $key);
}
public function newStorageFormat() {
$key = $this->getStorageFormat();
$template = PhabricatorFileStorageFormat::requireFormat($key);
$format = id(clone $template)
->setFile($this);
return $format;
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorFileEditor();
}
public function getApplicationTransactionTemplate() {
return new PhabricatorFileTransaction();
}
/* -( PhabricatorPolicyInterface Implementation )-------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
if ($this->isBuiltin()) {
return PhabricatorPolicies::getMostOpenPolicy();
}
if ($this->getIsProfileImage()) {
return PhabricatorPolicies::getMostOpenPolicy();
}
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return PhabricatorPolicies::POLICY_NOONE;
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
$viewer_phid = $viewer->getPHID();
if ($viewer_phid) {
if ($this->getAuthorPHID() == $viewer_phid) {
return true;
}
}
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
// If you can see the file this file is a transform of, you can see
// this file.
if ($this->getOriginalFile()) {
return true;
}
// If you can see any object this file is attached to, you can see
// the file.
return (count($this->getObjects()) > 0);
}
return false;
}
public function describeAutomaticCapability($capability) {
$out = array();
$out[] = pht('The user who uploaded a file can always view and edit it.');
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
$out[] = pht(
'Files attached to objects are visible to users who can view '.
'those objects.');
$out[] = pht(
'Thumbnails are visible only to users who can view the original '.
'file.');
break;
}
return $out;
}
/* -( PhabricatorSubscribableInterface Implementation )-------------------- */
public function isAutomaticallySubscribed($phid) {
return ($this->authorPHID == $phid);
}
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
public function getUsersToNotifyOfTokenGiven() {
return array(
$this->getAuthorPHID(),
);
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$this->delete();
$this->saveTransaction();
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('name')
->setType('string')
->setDescription(pht('The name of the file.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('uri')
->setType('uri')
->setDescription(pht('View URI for the file.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('dataURI')
->setType('uri')
->setDescription(pht('Download URI for the file data.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('size')
->setType('int')
->setDescription(pht('File size, in bytes.')),
);
}
public function getFieldValuesForConduit() {
return array(
'name' => $this->getName(),
'uri' => PhabricatorEnv::getURI($this->getURI()),
'dataURI' => $this->getCDNURI('data'),
'size' => (int)$this->getByteSize(),
'alt' => array(
'custom' => $this->getCustomAltText(),
'default' => $this->getDefaultAltText(),
),
);
}
public function getConduitSearchAttachments() {
return array();
}
/* -( PhabricatorNgramInterface )------------------------------------------ */
public function newNgrams() {
return array(
id(new PhabricatorFileNameNgrams())
->setValue($this->getName()),
);
}
}
diff --git a/src/applications/files/storage/PhabricatorFileAttachment.php b/src/applications/files/storage/PhabricatorFileAttachment.php
new file mode 100644
index 0000000000..c6aa170c79
--- /dev/null
+++ b/src/applications/files/storage/PhabricatorFileAttachment.php
@@ -0,0 +1,123 @@
+<?php
+
+final class PhabricatorFileAttachment
+ extends PhabricatorFileDAO
+ implements
+ PhabricatorPolicyInterface,
+ PhabricatorExtendedPolicyInterface {
+
+ protected $objectPHID;
+ protected $filePHID;
+ protected $attacherPHID;
+ protected $attachmentMode;
+
+ private $object = self::ATTACHABLE;
+ private $file = self::ATTACHABLE;
+
+ const MODE_ATTACH = 'attach';
+ const MODE_REFERENCE = 'reference';
+ const MODE_DETACH = 'detach';
+
+ protected function getConfiguration() {
+ return array(
+ self::CONFIG_COLUMN_SCHEMA => array(
+ 'objectPHID' => 'phid',
+ 'filePHID' => 'phid',
+ 'attacherPHID' => 'phid?',
+ 'attachmentMode' => 'text32',
+ ),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'key_object' => array(
+ 'columns' => array('objectPHID', 'filePHID'),
+ 'unique' => true,
+ ),
+ 'key_file' => array(
+ 'columns' => array('filePHID'),
+ ),
+ ),
+ ) + parent::getConfiguration();
+ }
+
+ public static function getModeList() {
+ return array(
+ self::MODE_ATTACH,
+ self::MODE_REFERENCE,
+ self::MODE_DETACH,
+ );
+ }
+
+ public static function getModeNameMap() {
+ return array(
+ self::MODE_ATTACH => pht('Attached'),
+ self::MODE_REFERENCE => pht('Referenced'),
+ );
+ }
+
+ public function isPolicyAttachment() {
+ switch ($this->getAttachmentMode()) {
+ case self::MODE_ATTACH:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public function attachObject($object) {
+ $this->object = $object;
+ return $this;
+ }
+
+ public function getObject() {
+ return $this->assertAttached($this->object);
+ }
+
+ public function attachFile(PhabricatorFile $file = null) {
+ $this->file = $file;
+ return $this;
+ }
+
+ public function getFile() {
+ return $this->assertAttached($this->file);
+ }
+
+ public function canDetach() {
+ switch ($this->getAttachmentMode()) {
+ case self::MODE_ATTACH:
+ return true;
+ }
+
+ return false;
+ }
+
+
+/* -( PhabricatorPolicyInterface )----------------------------------------- */
+
+
+ public function getCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ );
+ }
+
+ public function getPolicy($capability) {
+ switch ($capability) {
+ case PhabricatorPolicyCapability::CAN_VIEW:
+ return PhabricatorPolicies::getMostOpenPolicy();
+ }
+ }
+
+ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ return false;
+ }
+
+
+/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
+
+
+ public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
+ return array(
+ array($this->getObject(), $capability),
+ );
+ }
+
+}
diff --git a/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php b/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php
index 65dc0a12a2..21cb2daf11 100644
--- a/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php
+++ b/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php
@@ -1,496 +1,535 @@
<?php
final class PhabricatorFileTestCase extends PhabricatorTestCase {
protected function getPhabricatorTestCaseConfiguration() {
return array(
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
);
}
public function testFileDirectScramble() {
// Changes to a file's view policy should scramble the file secret.
$engine = new PhabricatorTestStorageEngine();
$data = Filesystem::readRandomCharacters(64);
$author = $this->generateNewTestUser();
$params = array(
'name' => 'test.dat',
'viewPolicy' => PhabricatorPolicies::POLICY_USER,
'authorPHID' => $author->getPHID(),
'storageEngines' => array(
$engine,
),
);
$file = PhabricatorFile::newFromFileData($data, $params);
$secret1 = $file->getSecretKey();
// First, change the name: this should not scramble the secret.
$xactions = array();
$xactions[] = id(new PhabricatorFileTransaction())
->setTransactionType(PhabricatorFileNameTransaction::TRANSACTIONTYPE)
->setNewValue('test.dat2');
$engine = id(new PhabricatorFileEditor())
->setActor($author)
->setContentSource($this->newContentSource())
->applyTransactions($file, $xactions);
$file = $file->reload();
$secret2 = $file->getSecretKey();
$this->assertEqual(
$secret1,
$secret2,
pht('No secret scramble on non-policy edit.'));
// Now, change the view policy. This should scramble the secret.
$xactions = array();
$xactions[] = id(new PhabricatorFileTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
->setNewValue($author->getPHID());
$engine = id(new PhabricatorFileEditor())
->setActor($author)
->setContentSource($this->newContentSource())
->applyTransactions($file, $xactions);
$file = $file->reload();
$secret3 = $file->getSecretKey();
$this->assertTrue(
($secret1 !== $secret3),
pht('Changing file view policy should scramble secret.'));
}
public function testFileIndirectScramble() {
// When a file is attached to an object like a task and the task view
// policy changes, the file secret should be scrambled. This invalidates
// old URIs if tasks get locked down.
$engine = new PhabricatorTestStorageEngine();
$data = Filesystem::readRandomCharacters(64);
$author = $this->generateNewTestUser();
$params = array(
'name' => 'test.dat',
'viewPolicy' => $author->getPHID(),
'authorPHID' => $author->getPHID(),
'storageEngines' => array(
$engine,
),
);
$file = PhabricatorFile::newFromFileData($data, $params);
$secret1 = $file->getSecretKey();
$task = ManiphestTask::initializeNewTask($author);
$xactions = array();
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE)
->setNewValue(pht('File Scramble Test Task'));
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(
ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE)
- ->setNewValue('{'.$file->getMonogram().'}');
+ ->setNewValue('{'.$file->getMonogram().'}')
+ ->setMetadataValue(
+ 'remarkup.control',
+ array(
+ 'attachedFilePHIDs' => array(
+ $file->getPHID(),
+ ),
+ ));
id(new ManiphestTransactionEditor())
->setActor($author)
->setContentSource($this->newContentSource())
->applyTransactions($task, $xactions);
$file = $file->reload();
$secret2 = $file->getSecretKey();
$this->assertEqual(
$secret1,
$secret2,
pht(
'File policy should not scramble when attached to '.
'newly created object.'));
$xactions = array();
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
->setNewValue($author->getPHID());
id(new ManiphestTransactionEditor())
->setActor($author)
->setContentSource($this->newContentSource())
->applyTransactions($task, $xactions);
$file = $file->reload();
$secret3 = $file->getSecretKey();
$this->assertTrue(
($secret1 !== $secret3),
pht('Changing attached object view policy should scramble secret.'));
}
public function testFileVisibility() {
$engine = new PhabricatorTestStorageEngine();
$data = Filesystem::readRandomCharacters(64);
$author = $this->generateNewTestUser();
$viewer = $this->generateNewTestUser();
$users = array($author, $viewer);
$params = array(
'name' => 'test.dat',
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
'authorPHID' => $author->getPHID(),
'storageEngines' => array(
$engine,
),
);
$file = PhabricatorFile::newFromFileData($data, $params);
$filter = new PhabricatorPolicyFilter();
// Test bare file policies.
$this->assertEqual(
array(
true,
false,
),
$this->canViewFile($users, $file),
pht('File Visibility'));
// Create an object and test object policies.
- $object = ManiphestTask::initializeNewTask($author);
- $object->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy());
- $object->save();
+ $object = ManiphestTask::initializeNewTask($author)
+ ->setTitle(pht('File Visibility Test Task'))
+ ->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy())
+ ->save();
$this->assertTrue(
$filter->hasCapability(
$author,
$object,
PhabricatorPolicyCapability::CAN_VIEW),
pht('Object Visible to Author'));
$this->assertTrue(
$filter->hasCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_VIEW),
pht('Object Visible to Others'));
+ // Reference the file in a comment. This should not affect the file
+ // policy.
+
+ $file_ref = '{F'.$file->getID().'}';
+
+ $xactions = array();
+ $xactions[] = id(new ManiphestTransaction())
+ ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
+ ->attachComment(
+ id(new ManiphestTransactionComment())
+ ->setContent($file_ref));
+
+ id(new ManiphestTransactionEditor())
+ ->setActor($author)
+ ->setContentSource($this->newContentSource())
+ ->applyTransactions($object, $xactions);
+
+ // Test the referenced file's visibility.
+ $this->assertEqual(
+ array(
+ true,
+ false,
+ ),
+ $this->canViewFile($users, $file),
+ pht('Referenced File Visibility'));
+
// Attach the file to the object and test that the association opens a
// policy exception for the non-author viewer.
- $file->attachToObject($object->getPHID());
+ $xactions = array();
+ $xactions[] = id(new ManiphestTransaction())
+ ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
+ ->setMetadataValue(
+ 'remarkup.control',
+ array(
+ 'attachedFilePHIDs' => array(
+ $file->getPHID(),
+ ),
+ ))
+ ->attachComment(
+ id(new ManiphestTransactionComment())
+ ->setContent($file_ref));
+
+ id(new ManiphestTransactionEditor())
+ ->setActor($author)
+ ->setContentSource($this->newContentSource())
+ ->applyTransactions($object, $xactions);
// Test the attached file's visibility.
$this->assertEqual(
array(
true,
true,
),
$this->canViewFile($users, $file),
pht('Attached File Visibility'));
// Create a "thumbnail" of the original file.
$params = array(
'name' => 'test.thumb.dat',
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
'storageEngines' => array(
$engine,
),
);
$xform = PhabricatorFile::newFromFileData($data, $params);
id(new PhabricatorTransformedFile())
->setOriginalPHID($file->getPHID())
->setTransform('test-thumb')
->setTransformedPHID($xform->getPHID())
->save();
// Test the thumbnail's visibility.
$this->assertEqual(
array(
true,
true,
),
$this->canViewFile($users, $xform),
pht('Attached Thumbnail Visibility'));
-
- // Detach the object and make sure it affects the thumbnail.
- $file->detachFromObject($object->getPHID());
-
- // Test the detached thumbnail's visibility.
- $this->assertEqual(
- array(
- true,
- false,
- ),
- $this->canViewFile($users, $xform),
- pht('Detached Thumbnail Visibility'));
}
private function canViewFile(array $users, PhabricatorFile $file) {
$results = array();
foreach ($users as $user) {
$results[] = (bool)id(new PhabricatorFileQuery())
->setViewer($user)
->withPHIDs(array($file->getPHID()))
->execute();
}
return $results;
}
public function testFileStorageReadWrite() {
$engine = new PhabricatorTestStorageEngine();
$data = Filesystem::readRandomCharacters(64);
$params = array(
'name' => 'test.dat',
'storageEngines' => array(
$engine,
),
);
$file = PhabricatorFile::newFromFileData($data, $params);
// Test that the storage engine worked, and was the target of the write. We
// don't actually care what the data is (future changes may compress or
// encrypt it), just that it exists in the test storage engine.
$engine->readFile($file->getStorageHandle());
// Now test that we get the same data back out.
$this->assertEqual($data, $file->loadFileData());
}
public function testFileStorageUploadDifferentFiles() {
$engine = new PhabricatorTestStorageEngine();
$data = Filesystem::readRandomCharacters(64);
$other_data = Filesystem::readRandomCharacters(64);
$params = array(
'name' => 'test.dat',
'storageEngines' => array(
$engine,
),
);
$first_file = PhabricatorFile::newFromFileData($data, $params);
$second_file = PhabricatorFile::newFromFileData($other_data, $params);
// Test that the second file uses different storage handle from
// the first file.
$first_handle = $first_file->getStorageHandle();
$second_handle = $second_file->getStorageHandle();
$this->assertTrue($first_handle != $second_handle);
}
public function testFileStorageUploadSameFile() {
$engine = new PhabricatorTestStorageEngine();
$data = Filesystem::readRandomCharacters(64);
$hash = PhabricatorFile::hashFileContent($data);
if ($hash === null) {
$this->assertSkipped(pht('File content hashing is not available.'));
}
$params = array(
'name' => 'test.dat',
'storageEngines' => array(
$engine,
),
);
$first_file = PhabricatorFile::newFromFileData($data, $params);
$second_file = PhabricatorFile::newFromFileData($data, $params);
// Test that the second file uses the same storage handle as
// the first file.
$handle = $first_file->getStorageHandle();
$second_handle = $second_file->getStorageHandle();
$this->assertEqual($handle, $second_handle);
}
public function testFileStorageDelete() {
$engine = new PhabricatorTestStorageEngine();
$data = Filesystem::readRandomCharacters(64);
$params = array(
'name' => 'test.dat',
'storageEngines' => array(
$engine,
),
);
$file = PhabricatorFile::newFromFileData($data, $params);
$handle = $file->getStorageHandle();
$file->delete();
$caught = null;
try {
$engine->readFile($handle);
} catch (Exception $ex) {
$caught = $ex;
}
$this->assertTrue($caught instanceof Exception);
}
public function testFileStorageDeleteSharedHandle() {
$engine = new PhabricatorTestStorageEngine();
$data = Filesystem::readRandomCharacters(64);
$params = array(
'name' => 'test.dat',
'storageEngines' => array(
$engine,
),
);
$first_file = PhabricatorFile::newFromFileData($data, $params);
$second_file = PhabricatorFile::newFromFileData($data, $params);
$first_file->delete();
$this->assertEqual($data, $second_file->loadFileData());
}
public function testReadWriteTtlFiles() {
$engine = new PhabricatorTestStorageEngine();
$data = Filesystem::readRandomCharacters(64);
$ttl = (PhabricatorTime::getNow() + phutil_units('24 hours in seconds'));
$params = array(
'name' => 'test.dat',
'ttl.absolute' => $ttl,
'storageEngines' => array(
$engine,
),
);
$file = PhabricatorFile::newFromFileData($data, $params);
$this->assertEqual($ttl, $file->getTTL());
}
public function testFileTransformDelete() {
// We want to test that a file deletes all its inbound transformation
// records and outbound transformed derivatives when it is deleted.
// First, we create a chain of transforms, A -> B -> C.
$engine = new PhabricatorTestStorageEngine();
$params = array(
'name' => 'test.txt',
'storageEngines' => array(
$engine,
),
);
$a = PhabricatorFile::newFromFileData('a', $params);
$b = PhabricatorFile::newFromFileData('b', $params);
$c = PhabricatorFile::newFromFileData('c', $params);
id(new PhabricatorTransformedFile())
->setOriginalPHID($a->getPHID())
->setTransform('test:a->b')
->setTransformedPHID($b->getPHID())
->save();
id(new PhabricatorTransformedFile())
->setOriginalPHID($b->getPHID())
->setTransform('test:b->c')
->setTransformedPHID($c->getPHID())
->save();
// Now, verify that A -> B and B -> C exist.
$xform_a = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withTransforms(
array(
array(
'originalPHID' => $a->getPHID(),
'transform' => true,
),
))
->execute();
$this->assertEqual(1, count($xform_a));
$this->assertEqual($b->getPHID(), head($xform_a)->getPHID());
$xform_b = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withTransforms(
array(
array(
'originalPHID' => $b->getPHID(),
'transform' => true,
),
))
->execute();
$this->assertEqual(1, count($xform_b));
$this->assertEqual($c->getPHID(), head($xform_b)->getPHID());
// Delete "B".
$b->delete();
// Now, verify that the A -> B and B -> C links are gone.
$xform_a = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withTransforms(
array(
array(
'originalPHID' => $a->getPHID(),
'transform' => true,
),
))
->execute();
$this->assertEqual(0, count($xform_a));
$xform_b = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withTransforms(
array(
array(
'originalPHID' => $b->getPHID(),
'transform' => true,
),
))
->execute();
$this->assertEqual(0, count($xform_b));
// Also verify that C has been deleted.
$alternate_c = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($c->getPHID()))
->execute();
$this->assertEqual(array(), $alternate_c);
}
}
diff --git a/src/applications/fund/query/FundInitiativeQuery.php b/src/applications/fund/query/FundInitiativeQuery.php
index bbb4ef2746..4b18597873 100644
--- a/src/applications/fund/query/FundInitiativeQuery.php
+++ b/src/applications/fund/query/FundInitiativeQuery.php
@@ -1,81 +1,77 @@
<?php
final class FundInitiativeQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $ownerPHIDs;
private $statuses;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withOwnerPHIDs(array $phids) {
$this->ownerPHIDs = $phids;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function newResultObject() {
return new FundInitiative();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'i.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'i.phid IN (%Ls)',
$this->phids);
}
if ($this->ownerPHIDs !== null) {
$where[] = qsprintf(
$conn,
'i.ownerPHID IN (%Ls)',
$this->ownerPHIDs);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'i.status IN (%Ls)',
$this->statuses);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorFundApplication';
}
protected function getPrimaryTableAlias() {
return 'i';
}
}
diff --git a/src/applications/guides/module/PhabricatorGuideInstallModule.php b/src/applications/guides/module/PhabricatorGuideInstallModule.php
index 172ff7a17b..2299bce260 100644
--- a/src/applications/guides/module/PhabricatorGuideInstallModule.php
+++ b/src/applications/guides/module/PhabricatorGuideInstallModule.php
@@ -1,179 +1,180 @@
<?php
final class PhabricatorGuideInstallModule extends PhabricatorGuideModule {
public function getModuleKey() {
return 'install';
}
public function getModuleName() {
- return pht('Install Phabricator');
+ return pht('Install');
}
public function getModulePosition() {
return 20;
}
public function getIsModuleEnabled() {
if (PhabricatorEnv::getEnvConfig('cluster.instance')) {
return false;
}
return true;
}
public function renderModuleStatus(AphrontRequest $request) {
$viewer = $request->getViewer();
$guide_items = new PhabricatorGuideListView();
$title = pht('Resolve Setup Issues');
$issues_resolved = !PhabricatorSetupCheck::getOpenSetupIssueKeys();
$href = PhabricatorEnv::getURI('/config/issue/');
if ($issues_resolved) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$description = pht(
"You've resolved (or ignored) all outstanding setup issues.");
} else {
$icon = 'fa-warning';
$icon_bg = 'bg-red';
$description =
pht('You have some unresolved setup issues to take care of.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setDescription($description);
$guide_items->addItem($item);
$configs = id(new PhabricatorAuthProviderConfigQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->execute();
$title = pht('Login and Registration');
$href = PhabricatorEnv::getURI('/auth/');
$have_auth = (bool)$configs;
if ($have_auth) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$description = pht(
"You've configured at least one authentication provider.");
} else {
$icon = 'fa-key';
$icon_bg = 'bg-sky';
$description = pht(
'Authentication providers allow users to register accounts and '.
- 'log in to Phabricator.');
+ 'log in.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setDescription($description);
$guide_items->addItem($item);
- $title = pht('Configure Phabricator');
+ $title = pht('Configure');
$href = PhabricatorEnv::getURI('/config/');
// Just load any config value at all; if one exists the install has figured
// out how to configure things.
$have_config = (bool)id(new PhabricatorConfigEntry())->loadAllWhere(
'1 = 1 LIMIT 1');
if ($have_config) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$description = pht(
"You've configured at least one setting from the web interface.");
} else {
$icon = 'fa-sliders';
$icon_bg = 'bg-sky';
$description = pht(
- 'Learn how to configure mail and other options in Phabricator.');
+ 'Learn how to configure mail and other options.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setDescription($description);
$guide_items->addItem($item);
$title = pht('User Account Settings');
$href = PhabricatorEnv::getURI('/settings/');
$preferences = id(new PhabricatorUserPreferencesQuery())
->setViewer($viewer)
->withUsers(array($viewer))
->executeOne();
$have_settings = ($preferences && $preferences->getPreferences());
if ($have_settings) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$description = pht(
"You've adjusted at least one setting on your account.");
} else {
$icon = 'fa-wrench';
$icon_bg = 'bg-sky';
$description = pht(
'Configure account settings for all users, or just yourself');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setDescription($description);
$guide_items->addItem($item);
$title = pht('Notification Server');
$href = PhabricatorEnv::getURI('/config/edit/notification.servers/');
$have_notifications = PhabricatorEnv::getEnvConfig('notification.servers');
if ($have_notifications) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$description = pht(
"You've set up a real-time notification server.");
} else {
$icon = 'fa-bell';
$icon_bg = 'bg-sky';
$description = pht(
- 'Phabricator can deliver notifications in real-time with WebSockets.');
+ 'Real-time notifications can be delivered with WebSockets.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setDescription($description);
$guide_items->addItem($item);
$intro = pht(
- 'Phabricator has been successfully installed. These next guides will '.
+ '%s has been successfully installed. These next guides will '.
'take you through configuration and new user orientation. '.
'These steps are optional, and you can go through them in any order. '.
'If you want to get back to this guide later on, you can find it in '.
- '{icon globe} **Applications** under {icon map-o} **Guides**.');
+ '{icon globe} **Applications** under {icon map-o} **Guides**.',
+ PlatformSymbols::getPlatformServerName());
$intro = new PHUIRemarkupView($viewer, $intro);
$intro = id(new PHUIDocumentView())
->appendChild($intro);
return array($intro, $guide_items);
}
}
diff --git a/src/applications/guides/module/PhabricatorGuideQuickStartModule.php b/src/applications/guides/module/PhabricatorGuideQuickStartModule.php
index 65b07ffe2c..3486ce4ba0 100644
--- a/src/applications/guides/module/PhabricatorGuideQuickStartModule.php
+++ b/src/applications/guides/module/PhabricatorGuideQuickStartModule.php
@@ -1,189 +1,187 @@
<?php
final class PhabricatorGuideQuickStartModule extends PhabricatorGuideModule {
public function getModuleKey() {
return 'quickstart';
}
public function getModuleName() {
return pht('Quick Start');
}
public function getModulePosition() {
return 30;
}
public function getIsModuleEnabled() {
return true;
}
public function renderModuleStatus(AphrontRequest $request) {
$viewer = $request->getViewer();
$instance = PhabricatorEnv::getEnvConfig('cluster.instance');
$guide_items = new PhabricatorGuideListView();
$title = pht('Create a Repository');
$repository_check = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->execute();
$href = PhabricatorEnv::getURI('/diffusion/');
if ($repository_check) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$description = pht(
"You've created at least one repository.");
} else {
$icon = 'fa-code';
$icon_bg = 'bg-sky';
$description =
pht('If you are here for code review, let\'s set up your first '.
'repository.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setDescription($description);
$guide_items->addItem($item);
$title = pht('Create a Project');
$project_check = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->execute();
$href = PhabricatorEnv::getURI('/project/');
if ($project_check) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$description = pht(
"You've created at least one project.");
} else {
$icon = 'fa-briefcase';
$icon_bg = 'bg-sky';
$description =
pht('Project tags define everything. Create them for teams, tags, '.
'or actual projects.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setDescription($description);
$guide_items->addItem($item);
$title = pht('Create a Task');
$task_check = id(new ManiphestTaskQuery())
->setViewer($viewer)
->execute();
$href = PhabricatorEnv::getURI('/maniphest/');
if ($task_check) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$description = pht(
"You've created at least one task.");
} else {
$icon = 'fa-anchor';
$icon_bg = 'bg-sky';
$description =
pht('Create some work for the interns in Maniphest.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setDescription($description);
$guide_items->addItem($item);
$title = pht('Personalize your Install');
$wordmark = PhabricatorEnv::getEnvConfig('ui.logo');
$href = PhabricatorEnv::getURI('/config/edit/ui.logo/');
if ($wordmark) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$description = pht(
'It looks amazing, good work. Home Sweet Home.');
} else {
$icon = 'fa-home';
$icon_bg = 'bg-sky';
$description =
pht('Change the name and add your company logo, just to give it a '.
'little extra polish.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setDescription($description);
$guide_items->addItem($item);
$title = pht('Explore Applications');
$href = PhabricatorEnv::getURI('/applications/');
$icon = 'fa-globe';
$icon_bg = 'bg-sky';
$description =
- pht('See all the applications included in Phabricator.');
+ pht('See all available applications.');
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setDescription($description);
$guide_items->addItem($item);
if (!$instance) {
$title = pht('Invite Collaborators');
$people_check = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->execute();
$people = count($people_check);
$href = PhabricatorEnv::getURI('/people/invite/send/');
if ($people > 1) {
$icon = 'fa-check';
$icon_bg = 'bg-green';
$description = pht(
'Your invitations have been accepted. You will not be alone on '.
'this journey.');
} else {
$icon = 'fa-group';
$icon_bg = 'bg-sky';
$description =
- pht('Invite the rest of your team to get started on Phabricator.');
+ pht('Invite the rest of your team to get started.');
}
$item = id(new PhabricatorGuideItemView())
->setTitle($title)
->setHref($href)
->setIcon($icon)
->setIconBackground($icon_bg)
->setDescription($description);
$guide_items->addItem($item);
}
$intro = pht(
- 'If you\'re new to Phabricator, these optional steps can help you learn '.
- 'the basics. Conceptually, Phabricator is structured as a graph, and '.
- 'repositories, tasks, and projects are all independent from each other. '.
- 'Feel free to set up Phabricator for how you work best, and explore '.
- 'these features at your own pace.');
+ 'If you\'re new to this software, these optional steps can help you '.
+ 'learn the basics. Feel free to set things up for how you work best '.
+ 'and explore these features at your own pace.');
$intro = new PHUIRemarkupView($viewer, $intro);
$intro = id(new PHUIDocumentView())
->appendChild($intro);
return array($intro, $guide_items);
}
}
diff --git a/src/applications/harbormaster/artifact/HarbormasterFileArtifact.php b/src/applications/harbormaster/artifact/HarbormasterFileArtifact.php
index 91fc025c21..6b536d8efa 100644
--- a/src/applications/harbormaster/artifact/HarbormasterFileArtifact.php
+++ b/src/applications/harbormaster/artifact/HarbormasterFileArtifact.php
@@ -1,66 +1,65 @@
<?php
final class HarbormasterFileArtifact extends HarbormasterArtifact {
const ARTIFACTCONST = 'file';
public function getArtifactTypeName() {
return pht('File');
}
public function getArtifactTypeDescription() {
return pht(
- 'Stores a reference to file data which has been uploaded to '.
- 'Phabricator.');
+ 'Stores a reference to file data.');
}
public function getArtifactParameterSpecification() {
return array(
'filePHID' => 'string',
);
}
public function getArtifactParameterDescriptions() {
return array(
'filePHID' => pht('File to create an artifact from.'),
);
}
public function getArtifactDataExample() {
return array(
'filePHID' => 'PHID-FILE-abcdefghijklmnopqrst',
);
}
public function renderArtifactSummary(PhabricatorUser $viewer) {
$artifact = $this->getBuildArtifact();
$file_phid = $artifact->getProperty('filePHID');
return $viewer->renderHandle($file_phid);
}
public function willCreateArtifact(PhabricatorUser $actor) {
// NOTE: This is primarily making sure the actor has permission to view the
// file. We don't want to let you run builds using files you don't have
// permission to see, since this could let you violate permissions.
$this->loadArtifactFile($actor);
}
public function loadArtifactFile(PhabricatorUser $viewer) {
$artifact = $this->getBuildArtifact();
$file_phid = $artifact->getProperty('filePHID');
$file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($file_phid))
->executeOne();
if (!$file) {
throw new Exception(
pht(
'File PHID "%s" does not correspond to a valid file.',
$file_phid));
}
return $file;
}
}
diff --git a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php
index 5d80d421aa..cfa27fec1d 100644
--- a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php
+++ b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php
@@ -1,104 +1,104 @@
<?php
final class HarbormasterPlanRunController extends HarbormasterPlanController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$plan_id = $request->getURIData('id');
$plan = id(new HarbormasterBuildPlanQuery())
->setViewer($viewer)
->withIDs(array($plan_id))
->executeOne();
if (!$plan) {
return new Aphront404Response();
}
$plan->assertHasRunCapability($viewer);
$cancel_uri = $this->getApplicationURI("plan/{$plan_id}/");
if (!$plan->canRunManually()) {
return $this->newDialog()
->setTitle(pht('Can Not Run Plan'))
->appendParagraph(pht('This plan can not be run manually.'))
->addCancelButton($cancel_uri);
}
$e_name = true;
$v_name = null;
$errors = array();
if ($request->isFormPost()) {
$buildable = HarbormasterBuildable::initializeNewBuildable($viewer)
->setIsManualBuildable(true);
$v_name = $request->getStr('buildablePHID');
if ($v_name) {
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withNames(array($v_name))
->executeOne();
if ($object instanceof HarbormasterBuildableInterface) {
$buildable
->setBuildablePHID($object->getHarbormasterBuildablePHID())
->setContainerPHID($object->getHarbormasterContainerPHID());
} else {
$e_name = pht('Invalid');
$errors[] = pht('Enter the name of a revision or commit.');
}
} else {
$e_name = pht('Required');
$errors[] = pht('You must choose a revision or commit to build.');
}
if (!$errors) {
$buildable->save();
$buildable->sendMessage(
$viewer,
HarbormasterMessageType::BUILDABLE_BUILD,
false);
$buildable->applyPlan($plan, array(), $viewer->getPHID());
$buildable_uri = '/B'.$buildable->getID();
return id(new AphrontRedirectResponse())->setURI($buildable_uri);
}
}
if ($errors) {
$errors = id(new PHUIInfoView())->setErrors($errors);
}
$title = pht('Run Build Plan Manually');
$save_button = pht('Run Plan Manually');
$form = id(new PHUIFormLayoutView())
->setUser($viewer)
->appendRemarkupInstructions(
pht(
"Enter the name of a commit or revision to run this plan on (for ".
"example, `rX123456` or `D123`).\n\n".
"For more detailed output, you can also run manual builds from ".
"the command line:\n\n".
- " phabricator/ $ ./bin/harbormaster build <object> --plan %s",
+ " $ ./bin/harbormaster build <object> --plan %s",
$plan->getID()))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Buildable Name'))
->setName('buildablePHID')
->setError($e_name)
->setValue($v_name));
return $this->newDialog()
->setWidth(AphrontDialogView::WIDTH_FULL)
->setTitle($title)
->appendChild($form)
->addCancelButton($cancel_uri)
->addSubmitButton($save_button);
}
}
diff --git a/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php b/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php
index 6f8fe28c6e..c83ae46086 100644
--- a/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php
+++ b/src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php
@@ -1,79 +1,79 @@
<?php
final class HarbormasterBuildStepCoreCustomField
extends HarbormasterBuildStepCustomField
implements PhabricatorStandardCustomFieldInterface {
public function getStandardCustomFieldNamespace() {
return 'harbormaster:core';
}
public function createFields($object) {
try {
$impl = $object->getStepImplementation();
} catch (Exception $ex) {
return array();
}
$specs = $impl->getFieldSpecifications();
if ($impl->supportsWaitForMessage()) {
$specs['builtin.next-steps-header'] = array(
'type' => 'header',
'name' => pht('Next Steps'),
);
$specs['builtin.wait-for-message'] = array(
'type' => 'select',
'name' => pht('When Complete'),
'instructions' => pht(
'After completing this build step Harbormaster can continue the '.
'build normally, or it can pause the build and wait for a message. '.
'If you are using this build step to trigger some work in an '.
- 'external system, you may want to have Phabricator wait for that '.
- 'system to perform the work and report results back.'.
+ 'external system, you may want wait for that system to perform '.
+ 'the work and report results back.'.
"\n\n".
'If you select **Continue Build Normally**, the build plan will '.
'proceed once this step finishes.'.
"\n\n".
'If you select **Wait For Message**, the build plan will pause '.
'indefinitely once this step finishes. To resume the build, an '.
'external system must call `harbormaster.sendmessage` with the '.
'build target PHID, and either `"pass"` or `"fail"` to indicate '.
'the result for this step. After the result is recorded, the build '.
'plan will resume.'),
'options' => array(
'continue' => pht('Continue Build Normally'),
'wait' => pht('Wait For Message'),
),
);
}
return PhabricatorStandardCustomField::buildStandardFields($this, $specs);
}
public function shouldUseStorage() {
return false;
}
public function readValueFromObject(PhabricatorCustomFieldInterface $object) {
$key = $this->getProxy()->getRawStandardFieldKey();
$this->setValueFromStorage($object->getDetail($key));
}
public function applyApplicationTransactionInternalEffects(
PhabricatorApplicationTransaction $xaction) {
$object = $this->getObject();
$key = $this->getProxy()->getRawStandardFieldKey();
$this->setValueFromApplicationTransactions($xaction->getNewValue());
$value = $this->getValueForStorage();
$object->setDetail($key, $value);
}
public function getBuildTargetFieldValue() {
return $this->getProxy()->getFieldValue();
}
}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildArtifactQuery.php b/src/applications/harbormaster/query/HarbormasterBuildArtifactQuery.php
index de35c05aa6..59d4c07d2f 100644
--- a/src/applications/harbormaster/query/HarbormasterBuildArtifactQuery.php
+++ b/src/applications/harbormaster/query/HarbormasterBuildArtifactQuery.php
@@ -1,117 +1,113 @@
<?php
final class HarbormasterBuildArtifactQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $buildTargetPHIDs;
private $artifactTypes;
private $artifactIndexes;
private $keyBuildPHID;
private $keyBuildGeneration;
private $isReleased;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withBuildTargetPHIDs(array $build_target_phids) {
$this->buildTargetPHIDs = $build_target_phids;
return $this;
}
public function withArtifactTypes(array $artifact_types) {
$this->artifactTypes = $artifact_types;
return $this;
}
public function withArtifactIndexes(array $artifact_indexes) {
$this->artifactIndexes = $artifact_indexes;
return $this;
}
public function withIsReleased($released) {
$this->isReleased = $released;
return $this;
}
public function newResultObject() {
return new HarbormasterBuildArtifact();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $page) {
$build_targets = array();
$build_target_phids = array_filter(mpull($page, 'getBuildTargetPHID'));
if ($build_target_phids) {
$build_targets = id(new HarbormasterBuildTargetQuery())
->setViewer($this->getViewer())
->withPHIDs($build_target_phids)
->setParentQuery($this)
->execute();
$build_targets = mpull($build_targets, null, 'getPHID');
}
foreach ($page as $key => $build_log) {
$build_target_phid = $build_log->getBuildTargetPHID();
if (empty($build_targets[$build_target_phid])) {
unset($page[$key]);
continue;
}
$build_log->attachBuildTarget($build_targets[$build_target_phid]);
}
return $page;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->buildTargetPHIDs !== null) {
$where[] = qsprintf(
$conn,
'buildTargetPHID IN (%Ls)',
$this->buildTargetPHIDs);
}
if ($this->artifactTypes !== null) {
$where[] = qsprintf(
$conn,
'artifactType in (%Ls)',
$this->artifactTypes);
}
if ($this->artifactIndexes !== null) {
$where[] = qsprintf(
$conn,
'artifactIndex IN (%Ls)',
$this->artifactIndexes);
}
if ($this->isReleased !== null) {
$where[] = qsprintf(
$conn,
'isReleased = %d',
(int)$this->isReleased);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorHarbormasterApplication';
}
}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildLogQuery.php b/src/applications/harbormaster/query/HarbormasterBuildLogQuery.php
index ad27d09582..f62919f989 100644
--- a/src/applications/harbormaster/query/HarbormasterBuildLogQuery.php
+++ b/src/applications/harbormaster/query/HarbormasterBuildLogQuery.php
@@ -1,90 +1,86 @@
<?php
final class HarbormasterBuildLogQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $buildPHIDs;
private $buildTargetPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withBuildTargetPHIDs(array $build_target_phids) {
$this->buildTargetPHIDs = $build_target_phids;
return $this;
}
public function newResultObject() {
return new HarbormasterBuildLog();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $page) {
$build_targets = array();
$build_target_phids = array_filter(mpull($page, 'getBuildTargetPHID'));
if ($build_target_phids) {
$build_targets = id(new HarbormasterBuildTargetQuery())
->setViewer($this->getViewer())
->withPHIDs($build_target_phids)
->setParentQuery($this)
->execute();
$build_targets = mpull($build_targets, null, 'getPHID');
}
foreach ($page as $key => $build_log) {
$build_target_phid = $build_log->getBuildTargetPHID();
if (empty($build_targets[$build_target_phid])) {
unset($page[$key]);
continue;
}
$build_log->attachBuildTarget($build_targets[$build_target_phid]);
}
return $page;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->buildTargetPHIDs !== null) {
$where[] = qsprintf(
$conn,
'buildTargetPHID IN (%Ls)',
$this->buildTargetPHIDs);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorHarbormasterApplication';
}
}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php b/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php
index 134c68ce04..393ca0a493 100644
--- a/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php
+++ b/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php
@@ -1,92 +1,88 @@
<?php
final class HarbormasterBuildMessageQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $receiverPHIDs;
private $consumed;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withReceiverPHIDs(array $phids) {
$this->receiverPHIDs = $phids;
return $this;
}
public function withConsumed($consumed) {
$this->consumed = $consumed;
return $this;
}
public function newResultObject() {
return new HarbormasterBuildMessage();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $page) {
$receiver_phids = array_filter(mpull($page, 'getReceiverPHID'));
if ($receiver_phids) {
$receivers = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withPHIDs($receiver_phids)
->setParentQuery($this)
->execute();
$receivers = mpull($receivers, null, 'getPHID');
} else {
$receivers = array();
}
foreach ($page as $key => $message) {
$receiver_phid = $message->getReceiverPHID();
if (empty($receivers[$receiver_phid])) {
unset($page[$key]);
$this->didRejectResult($message);
continue;
}
$message->attachReceiver($receivers[$receiver_phid]);
}
return $page;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->receiverPHIDs !== null) {
$where[] = qsprintf(
$conn,
'receiverPHID IN (%Ls)',
$this->receiverPHIDs);
}
if ($this->consumed !== null) {
$where[] = qsprintf(
$conn,
'isConsumed = %d',
(int)$this->consumed);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorHarbormasterApplication';
}
}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php b/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php
index c903fbb37f..c8514b2186 100644
--- a/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php
+++ b/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php
@@ -1,152 +1,148 @@
<?php
final class HarbormasterBuildPlanQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $statuses;
private $datasourceQuery;
private $planAutoKeys;
private $needBuildSteps;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function withDatasourceQuery($query) {
$this->datasourceQuery = $query;
return $this;
}
public function withPlanAutoKeys(array $keys) {
$this->planAutoKeys = $keys;
return $this;
}
public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint(
new HarbormasterBuildPlanNameNgrams(),
$ngrams);
}
public function needBuildSteps($need) {
$this->needBuildSteps = $need;
return $this;
}
public function newResultObject() {
return new HarbormasterBuildPlan();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function didFilterPage(array $page) {
if ($this->needBuildSteps) {
$plan_phids = mpull($page, 'getPHID');
$steps = id(new HarbormasterBuildStepQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withBuildPlanPHIDs($plan_phids)
->execute();
$steps = mgroup($steps, 'getBuildPlanPHID');
foreach ($page as $plan) {
$plan_steps = idx($steps, $plan->getPHID(), array());
$plan->attachBuildSteps($plan_steps);
}
}
return $page;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'plan.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'plan.phid IN (%Ls)',
$this->phids);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'plan.planStatus IN (%Ls)',
$this->statuses);
}
- if (strlen($this->datasourceQuery)) {
+ if (!phutil_nonempty_string($this->datasourceQuery)) {
$where[] = qsprintf(
$conn,
'plan.name LIKE %>',
$this->datasourceQuery);
}
if ($this->planAutoKeys !== null) {
$where[] = qsprintf(
$conn,
'plan.planAutoKey IN (%Ls)',
$this->planAutoKeys);
}
return $where;
}
protected function getPrimaryTableAlias() {
return 'plan';
}
public function getQueryApplicationClass() {
return 'PhabricatorHarbormasterApplication';
}
public function getOrderableColumns() {
return parent::getOrderableColumns() + array(
'name' => array(
'column' => 'name',
'type' => 'string',
'reverse' => true,
),
);
}
protected function newPagingMapFromPartialObject($object) {
return array(
'id' => (int)$object->getID(),
'name' => $object->getName(),
);
}
public function getBuiltinOrders() {
return array(
'name' => array(
'vector' => array('name', 'id'),
'name' => pht('Name'),
),
) + parent::getBuiltinOrders();
}
}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildQuery.php b/src/applications/harbormaster/query/HarbormasterBuildQuery.php
index 5ff5fe2eb3..a397f5968a 100644
--- a/src/applications/harbormaster/query/HarbormasterBuildQuery.php
+++ b/src/applications/harbormaster/query/HarbormasterBuildQuery.php
@@ -1,233 +1,229 @@
<?php
final class HarbormasterBuildQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $buildStatuses;
private $buildablePHIDs;
private $buildPlanPHIDs;
private $initiatorPHIDs;
private $needBuildTargets;
private $autobuilds;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withBuildStatuses(array $build_statuses) {
$this->buildStatuses = $build_statuses;
return $this;
}
public function withBuildablePHIDs(array $buildable_phids) {
$this->buildablePHIDs = $buildable_phids;
return $this;
}
public function withBuildPlanPHIDs(array $build_plan_phids) {
$this->buildPlanPHIDs = $build_plan_phids;
return $this;
}
public function withInitiatorPHIDs(array $initiator_phids) {
$this->initiatorPHIDs = $initiator_phids;
return $this;
}
public function withAutobuilds($with_autobuilds) {
$this->autobuilds = $with_autobuilds;
return $this;
}
public function needBuildTargets($need_targets) {
$this->needBuildTargets = $need_targets;
return $this;
}
public function newResultObject() {
return new HarbormasterBuild();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $page) {
$buildables = array();
$buildable_phids = array_filter(mpull($page, 'getBuildablePHID'));
if ($buildable_phids) {
$buildables = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withPHIDs($buildable_phids)
->setParentQuery($this)
->execute();
$buildables = mpull($buildables, null, 'getPHID');
}
foreach ($page as $key => $build) {
$buildable_phid = $build->getBuildablePHID();
if (empty($buildables[$buildable_phid])) {
unset($page[$key]);
continue;
}
$build->attachBuildable($buildables[$buildable_phid]);
}
return $page;
}
protected function didFilterPage(array $page) {
$plans = array();
$plan_phids = array_filter(mpull($page, 'getBuildPlanPHID'));
if ($plan_phids) {
$plans = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withPHIDs($plan_phids)
->setParentQuery($this)
->execute();
$plans = mpull($plans, null, 'getPHID');
}
foreach ($page as $key => $build) {
$plan_phid = $build->getBuildPlanPHID();
$build->attachBuildPlan(idx($plans, $plan_phid));
}
$build_phids = mpull($page, 'getPHID');
$messages = id(new HarbormasterBuildMessage())->loadAllWhere(
'receiverPHID IN (%Ls) AND isConsumed = 0 ORDER BY id ASC',
$build_phids);
$messages = mgroup($messages, 'getReceiverPHID');
foreach ($page as $build) {
$unprocessed_messages = idx($messages, $build->getPHID(), array());
$build->attachUnprocessedMessages($unprocessed_messages);
}
if ($this->needBuildTargets) {
$targets = id(new HarbormasterBuildTargetQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withBuildPHIDs($build_phids)
->execute();
// TODO: Some day, when targets have dependencies, we should toposort
// these. For now, just put them into chronological order.
$targets = array_reverse($targets);
$targets = mgroup($targets, 'getBuildPHID');
foreach ($page as $build) {
$build_targets = idx($targets, $build->getPHID(), array());
foreach ($build_targets as $phid => $target) {
if ($target->getBuildGeneration() !== $build->getBuildGeneration()) {
unset($build_targets[$phid]);
}
}
$build->attachBuildTargets($build_targets);
}
}
return $page;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'b.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'b.phid in (%Ls)',
$this->phids);
}
if ($this->buildStatuses !== null) {
$where[] = qsprintf(
$conn,
'b.buildStatus in (%Ls)',
$this->buildStatuses);
}
if ($this->buildablePHIDs !== null) {
$where[] = qsprintf(
$conn,
'b.buildablePHID IN (%Ls)',
$this->buildablePHIDs);
}
if ($this->buildPlanPHIDs !== null) {
$where[] = qsprintf(
$conn,
'b.buildPlanPHID IN (%Ls)',
$this->buildPlanPHIDs);
}
if ($this->initiatorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'b.initiatorPHID IN (%Ls)',
$this->initiatorPHIDs);
}
if ($this->autobuilds !== null) {
if ($this->autobuilds) {
$where[] = qsprintf(
$conn,
'p.planAutoKey IS NOT NULL');
} else {
$where[] = qsprintf(
$conn,
'p.planAutoKey IS NULL');
}
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->shouldJoinPlanTable()) {
$joins[] = qsprintf(
$conn,
'JOIN %T p ON b.buildPlanPHID = p.phid',
id(new HarbormasterBuildPlan())->getTableName());
}
return $joins;
}
private function shouldJoinPlanTable() {
if ($this->autobuilds !== null) {
return true;
}
return false;
}
public function getQueryApplicationClass() {
return 'PhabricatorHarbormasterApplication';
}
protected function getPrimaryTableAlias() {
return 'b';
}
}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildStepQuery.php b/src/applications/harbormaster/query/HarbormasterBuildStepQuery.php
index 992dd4fad1..93010071e2 100644
--- a/src/applications/harbormaster/query/HarbormasterBuildStepQuery.php
+++ b/src/applications/harbormaster/query/HarbormasterBuildStepQuery.php
@@ -1,89 +1,85 @@
<?php
final class HarbormasterBuildStepQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $buildPlanPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withBuildPlanPHIDs(array $phids) {
$this->buildPlanPHIDs = $phids;
return $this;
}
public function newResultObject() {
return new HarbormasterBuildStep();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid in (%Ls)',
$this->phids);
}
if ($this->buildPlanPHIDs !== null) {
$where[] = qsprintf(
$conn,
'buildPlanPHID in (%Ls)',
$this->buildPlanPHIDs);
}
return $where;
}
protected function willFilterPage(array $page) {
$plans = array();
$buildplan_phids = array_filter(mpull($page, 'getBuildPlanPHID'));
if ($buildplan_phids) {
$plans = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withPHIDs($buildplan_phids)
->setParentQuery($this)
->execute();
$plans = mpull($plans, null, 'getPHID');
}
foreach ($page as $key => $build) {
$buildable_phid = $build->getBuildPlanPHID();
if (empty($plans[$buildable_phid])) {
unset($page[$key]);
continue;
}
$build->attachBuildPlan($plans[$buildable_phid]);
}
return $page;
}
public function getQueryApplicationClass() {
return 'PhabricatorHarbormasterApplication';
}
}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php b/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php
index 54b9c4ee16..a93aff60bd 100644
--- a/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php
+++ b/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php
@@ -1,213 +1,209 @@
<?php
final class HarbormasterBuildTargetQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $buildPHIDs;
private $buildGenerations;
private $dateCreatedMin;
private $dateCreatedMax;
private $dateStartedMin;
private $dateStartedMax;
private $dateCompletedMin;
private $dateCompletedMax;
private $statuses;
private $needBuildSteps;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withBuildPHIDs(array $build_phids) {
$this->buildPHIDs = $build_phids;
return $this;
}
public function withBuildGenerations(array $build_generations) {
$this->buildGenerations = $build_generations;
return $this;
}
public function withDateCreatedBetween($min, $max) {
$this->dateCreatedMin = $min;
$this->dateCreatedMax = $max;
return $this;
}
public function withDateStartedBetween($min, $max) {
$this->dateStartedMin = $min;
$this->dateStartedMax = $max;
return $this;
}
public function withDateCompletedBetween($min, $max) {
$this->dateCompletedMin = $min;
$this->dateCompletedMax = $max;
return $this;
}
public function withTargetStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function needBuildSteps($need_build_steps) {
$this->needBuildSteps = $need_build_steps;
return $this;
}
public function newResultObject() {
return new HarbormasterBuildTarget();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid in (%Ls)',
$this->phids);
}
if ($this->buildPHIDs !== null) {
$where[] = qsprintf(
$conn,
'buildPHID in (%Ls)',
$this->buildPHIDs);
}
if ($this->buildGenerations !== null) {
$where[] = qsprintf(
$conn,
'buildGeneration in (%Ld)',
$this->buildGenerations);
}
if ($this->dateCreatedMin !== null) {
$where[] = qsprintf(
$conn,
'dateCreated >= %d',
$this->dateCreatedMin);
}
if ($this->dateCreatedMax !== null) {
$where[] = qsprintf(
$conn,
'dateCreated <= %d',
$this->dateCreatedMax);
}
if ($this->dateStartedMin !== null) {
$where[] = qsprintf(
$conn,
'dateStarted >= %d',
$this->dateStartedMin);
}
if ($this->dateStartedMax !== null) {
$where[] = qsprintf(
$conn,
'dateStarted <= %d',
$this->dateStartedMax);
}
if ($this->dateCompletedMin !== null) {
$where[] = qsprintf(
$conn,
'dateCompleted >= %d',
$this->dateCompletedMin);
}
if ($this->dateCompletedMax !== null) {
$where[] = qsprintf(
$conn,
'dateCompleted <= %d',
$this->dateCompletedMax);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'targetStatus IN (%Ls)',
$this->statuses);
}
return $where;
}
protected function didFilterPage(array $page) {
if ($this->needBuildSteps) {
$step_phids = array();
foreach ($page as $target) {
$step_phids[] = $target->getBuildStepPHID();
}
$steps = id(new HarbormasterBuildStepQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($step_phids)
->execute();
$steps = mpull($steps, null, 'getPHID');
foreach ($page as $target) {
$target->attachBuildStep(
idx($steps, $target->getBuildStepPHID()));
}
}
return $page;
}
protected function willFilterPage(array $page) {
$builds = array();
$build_phids = array_filter(mpull($page, 'getBuildPHID'));
if ($build_phids) {
$builds = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withPHIDs($build_phids)
->setParentQuery($this)
->execute();
$builds = mpull($builds, null, 'getPHID');
}
foreach ($page as $key => $build_target) {
$build_phid = $build_target->getBuildPHID();
if (empty($builds[$build_phid])) {
unset($page[$key]);
continue;
}
$build_target->attachBuild($builds[$build_phid]);
}
return $page;
}
public function getQueryApplicationClass() {
return 'PhabricatorHarbormasterApplication';
}
}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildUnitMessageQuery.php b/src/applications/harbormaster/query/HarbormasterBuildUnitMessageQuery.php
index f73016a29f..edfe102ca2 100644
--- a/src/applications/harbormaster/query/HarbormasterBuildUnitMessageQuery.php
+++ b/src/applications/harbormaster/query/HarbormasterBuildUnitMessageQuery.php
@@ -1,95 +1,91 @@
<?php
final class HarbormasterBuildUnitMessageQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $targetPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withBuildTargetPHIDs(array $target_phids) {
$this->targetPHIDs = $target_phids;
return $this;
}
public function newResultObject() {
return new HarbormasterBuildUnitMessage();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid in (%Ls)',
$this->phids);
}
if ($this->targetPHIDs !== null) {
$where[] = qsprintf(
$conn,
'buildTargetPHID in (%Ls)',
$this->targetPHIDs);
}
return $where;
}
protected function didFilterPage(array $messages) {
$indexes = array();
foreach ($messages as $message) {
$index = $message->getNameIndex();
if (strlen($index)) {
$indexes[$index] = $index;
}
}
if ($indexes) {
$map = HarbormasterString::newIndexMap($indexes);
foreach ($messages as $message) {
$index = $message->getNameIndex();
if (!strlen($index)) {
continue;
}
$name = idx($map, $index);
if ($name === null) {
$name = pht('Unknown Unit Message ("%s")', $index);
}
$message->setName($name);
}
}
return $messages;
}
public function getQueryApplicationClass() {
return 'PhabricatorHarbormasterApplication';
}
}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildableQuery.php b/src/applications/harbormaster/query/HarbormasterBuildableQuery.php
index b1a643cac7..cf907a2dc3 100644
--- a/src/applications/harbormaster/query/HarbormasterBuildableQuery.php
+++ b/src/applications/harbormaster/query/HarbormasterBuildableQuery.php
@@ -1,184 +1,180 @@
<?php
final class HarbormasterBuildableQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $buildablePHIDs;
private $containerPHIDs;
private $statuses;
private $manualBuildables;
private $needContainerObjects;
private $needBuilds;
private $needTargets;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withBuildablePHIDs(array $buildable_phids) {
$this->buildablePHIDs = $buildable_phids;
return $this;
}
public function withContainerPHIDs(array $container_phids) {
$this->containerPHIDs = $container_phids;
return $this;
}
public function withManualBuildables($manual) {
$this->manualBuildables = $manual;
return $this;
}
public function needContainerObjects($need) {
$this->needContainerObjects = $need;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function needBuilds($need) {
$this->needBuilds = $need;
return $this;
}
public function needTargets($need) {
$this->needTargets = $need;
return $this;
}
public function newResultObject() {
return new HarbormasterBuildable();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $page) {
$buildables = array();
$buildable_phids = array_filter(mpull($page, 'getBuildablePHID'));
if ($buildable_phids) {
$buildables = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withPHIDs($buildable_phids)
->setParentQuery($this)
->execute();
$buildables = mpull($buildables, null, 'getPHID');
}
foreach ($page as $key => $buildable) {
$buildable_phid = $buildable->getBuildablePHID();
if (empty($buildables[$buildable_phid])) {
unset($page[$key]);
continue;
}
$buildable->attachBuildableObject($buildables[$buildable_phid]);
}
return $page;
}
protected function didFilterPage(array $page) {
if ($this->needContainerObjects) {
$container_phids = array_filter(mpull($page, 'getContainerPHID'));
if ($container_phids) {
$containers = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withPHIDs($container_phids)
->setParentQuery($this)
->execute();
$containers = mpull($containers, null, 'getPHID');
} else {
$containers = array();
}
foreach ($page as $key => $buildable) {
$container_phid = $buildable->getContainerPHID();
$buildable->attachContainerObject(idx($containers, $container_phid));
}
}
if ($this->needBuilds || $this->needTargets) {
$builds = id(new HarbormasterBuildQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withBuildablePHIDs(mpull($page, 'getPHID'))
->needBuildTargets($this->needTargets)
->execute();
$builds = mgroup($builds, 'getBuildablePHID');
foreach ($page as $key => $buildable) {
$buildable->attachBuilds(idx($builds, $buildable->getPHID(), array()));
}
}
return $page;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->buildablePHIDs !== null) {
$where[] = qsprintf(
$conn,
'buildablePHID IN (%Ls)',
$this->buildablePHIDs);
}
if ($this->containerPHIDs !== null) {
$where[] = qsprintf(
$conn,
'containerPHID in (%Ls)',
$this->containerPHIDs);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'buildableStatus in (%Ls)',
$this->statuses);
}
if ($this->manualBuildables !== null) {
$where[] = qsprintf(
$conn,
'isManualBuildable = %d',
(int)$this->manualBuildables);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorHarbormasterApplication';
}
}
diff --git a/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php
deleted file mode 100644
index fc0c830150..0000000000
--- a/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php
+++ /dev/null
@@ -1,89 +0,0 @@
-<?php
-
-final class HarbormasterPublishFragmentBuildStepImplementation
- extends HarbormasterBuildStepImplementation {
-
- public function getName() {
- return pht('Publish Fragment');
- }
-
- public function getGenericDescription() {
- return pht('Publish a fragment based on a file artifact.');
- }
-
-
- public function getBuildStepGroupKey() {
- return HarbormasterPrototypeBuildStepGroup::GROUPKEY;
- }
-
- public function getDescription() {
- return pht(
- 'Publish file artifact %s as fragment %s.',
- $this->formatSettingForDescription('artifact'),
- $this->formatSettingForDescription('path'));
- }
-
- public function execute(
- HarbormasterBuild $build,
- HarbormasterBuildTarget $build_target) {
-
- $settings = $this->getSettings();
- $variables = $build_target->getVariables();
- $viewer = PhabricatorUser::getOmnipotentUser();
-
- $path = $this->mergeVariables(
- 'vsprintf',
- $settings['path'],
- $variables);
-
- $artifact = $build_target->loadArtifact($settings['artifact']);
- $impl = $artifact->getArtifactImplementation();
- $file = $impl->loadArtifactFile($viewer);
-
- $fragment = id(new PhragmentFragmentQuery())
- ->setViewer($viewer)
- ->withPaths(array($path))
- ->executeOne();
-
- if ($fragment === null) {
- PhragmentFragment::createFromFile(
- $viewer,
- $file,
- $path,
- PhabricatorPolicies::getMostOpenPolicy(),
- PhabricatorPolicies::POLICY_USER);
- } else {
- if ($file->getMimeType() === 'application/zip') {
- $fragment->updateFromZIP($viewer, $file);
- } else {
- $fragment->updateFromFile($viewer, $file);
- }
- }
- }
-
- public function getArtifactInputs() {
- return array(
- array(
- 'name' => pht('Publishes File'),
- 'key' => $this->getSetting('artifact'),
- 'type' => HarbormasterFileArtifact::ARTIFACTCONST,
- ),
- );
- }
-
- public function getFieldSpecifications() {
- return array(
- 'path' => array(
- 'name' => pht('Path'),
- 'type' => 'text',
- 'required' => true,
- ),
- 'artifact' => array(
- 'name' => pht('File Artifact'),
- 'type' => 'text',
- 'required' => true,
- ),
- );
- }
-
-}
diff --git a/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php
index f8bdb7fdfd..0779ccb186 100644
--- a/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php
+++ b/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php
@@ -1,97 +1,97 @@
<?php
final class HarbormasterUploadArtifactBuildStepImplementation
extends HarbormasterBuildStepImplementation {
public function getName() {
return pht('Upload File');
}
public function getGenericDescription() {
- return pht('Upload a file from a host to Phabricator.');
+ return pht('Upload a file.');
}
public function getBuildStepGroupKey() {
return HarbormasterPrototypeBuildStepGroup::GROUPKEY;
}
public function getDescription() {
return pht(
'Upload %s from %s.',
$this->formatSettingForDescription('path'),
$this->formatSettingForDescription('hostartifact'));
}
public function execute(
HarbormasterBuild $build,
HarbormasterBuildTarget $build_target) {
$viewer = PhabricatorUser::getOmnipotentUser();
$settings = $this->getSettings();
$variables = $build_target->getVariables();
$path = $this->mergeVariables(
'vsprintf',
$settings['path'],
$variables);
$artifact = $build_target->loadArtifact($settings['hostartifact']);
$impl = $artifact->getArtifactImplementation();
$lease = $impl->loadArtifactLease($viewer);
$interface = $lease->getInterface('filesystem');
// TODO: Handle exceptions.
$file = $interface->saveFile($path, $settings['name']);
// Insert the artifact record.
$artifact = $build_target->createArtifact(
$viewer,
$settings['name'],
HarbormasterFileArtifact::ARTIFACTCONST,
array(
'filePHID' => $file->getPHID(),
));
}
public function getArtifactInputs() {
return array(
array(
'name' => pht('Upload From Host'),
'key' => $this->getSetting('hostartifact'),
'type' => HarbormasterHostArtifact::ARTIFACTCONST,
),
);
}
public function getArtifactOutputs() {
return array(
array(
'name' => pht('Uploaded File'),
'key' => $this->getSetting('name'),
'type' => HarbormasterHostArtifact::ARTIFACTCONST,
),
);
}
public function getFieldSpecifications() {
return array(
'path' => array(
'name' => pht('Path'),
'type' => 'text',
'required' => true,
),
'name' => array(
'name' => pht('Local Name'),
'type' => 'text',
'required' => true,
),
'hostartifact' => array(
'name' => pht('Host Artifact'),
'type' => 'text',
'required' => true,
),
);
}
}
diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php
index 5e13522b6e..be8c50f5ba 100644
--- a/src/applications/herald/controller/HeraldRuleController.php
+++ b/src/applications/herald/controller/HeraldRuleController.php
@@ -1,740 +1,739 @@
<?php
final class HeraldRuleController extends HeraldController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$content_type_map = HeraldAdapter::getEnabledAdapterMap($viewer);
$rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap();
if ($id) {
$rule = id(new HeraldRuleQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$rule) {
return new Aphront404Response();
}
$cancel_uri = '/'.$rule->getMonogram();
} else {
$new_uri = $this->getApplicationURI('new/');
$rule = new HeraldRule();
$rule->setAuthorPHID($viewer->getPHID());
$rule->setMustMatchAll(1);
$content_type = $request->getStr('content_type');
$rule->setContentType($content_type);
$rule_type = $request->getStr('rule_type');
if (!isset($rule_type_map[$rule_type])) {
return $this->newDialog()
->setTitle(pht('Invalid Rule Type'))
->appendParagraph(
pht(
'The selected rule type ("%s") is not recognized by Herald.',
$rule_type))
->addCancelButton($new_uri);
}
$rule->setRuleType($rule_type);
try {
$adapter = HeraldAdapter::getAdapterForContentType(
$rule->getContentType());
} catch (Exception $ex) {
return $this->newDialog()
->setTitle(pht('Invalid Content Type'))
->appendParagraph(
pht(
'The selected content type ("%s") is not recognized by '.
'Herald.',
$rule->getContentType()))
->addCancelButton($new_uri);
}
if (!$adapter->supportsRuleType($rule->getRuleType())) {
return $this->newDialog()
->setTitle(pht('Rule/Content Mismatch'))
->appendParagraph(
pht(
'The selected rule type ("%s") is not supported by the selected '.
'content type ("%s").',
$rule->getRuleType(),
$rule->getContentType()))
->addCancelButton($new_uri);
}
if ($rule->isObjectRule()) {
$rule->setTriggerObjectPHID($request->getStr('targetPHID'));
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(array($rule->getTriggerObjectPHID()))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$object) {
throw new Exception(
pht('No valid object provided for object rule!'));
}
if (!$adapter->canTriggerOnObject($object)) {
throw new Exception(
pht('Object is of wrong type for adapter!'));
}
}
$cancel_uri = $this->getApplicationURI();
}
if ($rule->isGlobalRule()) {
$this->requireApplicationCapability(
HeraldManageGlobalRulesCapability::CAPABILITY);
}
$adapter = HeraldAdapter::getAdapterForContentType($rule->getContentType());
$local_version = id(new HeraldRule())->getConfigVersion();
if ($rule->getConfigVersion() > $local_version) {
throw new Exception(
pht(
'This rule was created with a newer version of Herald. You can not '.
- 'view or edit it in this older version. Upgrade your Phabricator '.
- 'deployment.'));
+ 'view or edit it in this older version. Upgrade your software.'));
}
// Upgrade rule version to our version, since we might add newly-defined
// conditions, etc.
$rule->setConfigVersion($local_version);
$rule_conditions = $rule->loadConditions();
$rule_actions = $rule->loadActions();
$rule->attachConditions($rule_conditions);
$rule->attachActions($rule_actions);
$e_name = true;
$errors = array();
if ($request->isFormPost() && $request->getStr('save')) {
list($e_name, $errors) = $this->saveRule($adapter, $rule, $request);
if (!$errors) {
$id = $rule->getID();
$uri = '/'.$rule->getMonogram();
return id(new AphrontRedirectResponse())->setURI($uri);
}
}
$must_match_selector = $this->renderMustMatchSelector($rule);
$repetition_selector = $this->renderRepetitionSelector($rule, $adapter);
$handles = $this->loadHandlesForRule($rule);
require_celerity_resource('herald-css');
$content_type_name = $content_type_map[$rule->getContentType()];
$rule_type_name = $rule_type_map[$rule->getRuleType()];
$form = id(new AphrontFormView())
->setUser($viewer)
->setID('herald-rule-edit-form')
->addHiddenInput('content_type', $rule->getContentType())
->addHiddenInput('rule_type', $rule->getRuleType())
->addHiddenInput('save', 1)
->appendChild(
// Build this explicitly (instead of using addHiddenInput())
// so we can add a sigil to it.
javelin_tag(
'input',
array(
'type' => 'hidden',
'name' => 'rule',
'sigil' => 'rule',
)))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Rule Name'))
->setName('name')
->setError($e_name)
->setValue($rule->getName()));
$trigger_object_control = false;
if ($rule->isObjectRule()) {
$trigger_object_control = id(new AphrontFormStaticControl())
->setValue(
pht(
'This rule triggers for %s.',
$handles[$rule->getTriggerObjectPHID()]->renderLink()));
}
$form
->appendChild(
id(new AphrontFormMarkupControl())
->setValue(pht(
'This %s rule triggers for %s.',
phutil_tag('strong', array(), $rule_type_name),
phutil_tag('strong', array(), $content_type_name))))
->appendChild($trigger_object_control)
->appendChild(
id(new PHUIFormInsetView())
->setTitle(pht('Conditions'))
->setRightButton(javelin_tag(
'a',
array(
'href' => '#',
'class' => 'button button-green',
'sigil' => 'create-condition',
'mustcapture' => true,
),
pht('New Condition')))
->setDescription(
pht('When %s these conditions are met:', $must_match_selector))
->setContent(javelin_tag(
'table',
array(
'sigil' => 'rule-conditions',
'class' => 'herald-condition-table',
),
'')))
->appendChild(
id(new PHUIFormInsetView())
->setTitle(pht('Action'))
->setRightButton(javelin_tag(
'a',
array(
'href' => '#',
'class' => 'button button-green',
'sigil' => 'create-action',
'mustcapture' => true,
),
pht('New Action')))
->setDescription(pht(
'Take these actions %s',
$repetition_selector))
->setContent(javelin_tag(
'table',
array(
'sigil' => 'rule-actions',
'class' => 'herald-action-table',
),
'')))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save Rule'))
->addCancelButton($cancel_uri));
$this->setupEditorBehavior($rule, $handles, $adapter);
$title = $rule->getID()
? pht('Edit Herald Rule: %s', $rule->getName())
: pht('Create Herald Rule: %s', idx($content_type_map, $content_type));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->setFormErrors($errors)
->setForm($form);
$crumbs = $this
->buildApplicationCrumbs()
->addTextCrumb($title)
->setBorder(true);
$view = id(new PHUITwoColumnView())
->setFooter($form_box);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild(
array(
$view,
));
}
private function saveRule(HeraldAdapter $adapter, $rule, $request) {
$new_name = $request->getStr('name');
$match_all = ($request->getStr('must_match') == 'all');
$repetition_policy = $request->getStr('repetition_policy');
// If the user selected an invalid policy, or there's only one possible
// value so we didn't render a control, adjust the value to the first
// valid policy value.
$repetition_options = $this->getRepetitionOptionMap($adapter);
if (!isset($repetition_options[$repetition_policy])) {
$repetition_policy = head_key($repetition_options);
}
$e_name = true;
$errors = array();
if (!strlen($new_name)) {
$e_name = pht('Required');
$errors[] = pht('Rule must have a name.');
}
$data = null;
try {
$data = phutil_json_decode($request->getStr('rule'));
} catch (PhutilJSONParserException $ex) {
throw new PhutilProxyException(
pht('Failed to decode rule data.'),
$ex);
}
if (!is_array($data) ||
!$data['conditions'] ||
!$data['actions']) {
throw new Exception(pht('Failed to decode rule data.'));
}
$conditions = array();
foreach ($data['conditions'] as $condition) {
if ($condition === null) {
// We manage this as a sparse array on the client, so may receive
// NULL if conditions have been removed.
continue;
}
$obj = new HeraldCondition();
$obj->setFieldName($condition[0]);
$obj->setFieldCondition($condition[1]);
if (is_array($condition[2])) {
$obj->setValue(array_keys($condition[2]));
} else {
$obj->setValue($condition[2]);
}
try {
$adapter->willSaveCondition($obj);
} catch (HeraldInvalidConditionException $ex) {
$errors[] = $ex->getMessage();
}
$conditions[] = $obj;
}
$actions = array();
foreach ($data['actions'] as $action) {
if ($action === null) {
// Sparse on the client; removals can give us NULLs.
continue;
}
if (!isset($action[1])) {
// Legitimate for any action which doesn't need a target, like
// "Do nothing".
$action[1] = null;
}
$obj = new HeraldActionRecord();
$obj->setAction($action[0]);
$obj->setTarget($action[1]);
try {
$adapter->willSaveAction($rule, $obj);
} catch (HeraldInvalidActionException $ex) {
$errors[] = $ex->getMessage();
}
$actions[] = $obj;
}
if (!$errors) {
$new_state = id(new HeraldRuleSerializer())->serializeRuleComponents(
$match_all,
$conditions,
$actions,
$repetition_policy);
$xactions = array();
// Until this moves to EditEngine, manually add a "CREATE" transaction
// if we're creating a new rule. This improves rendering of the initial
// group of transactions.
$is_new = (bool)(!$rule->getID());
if ($is_new) {
$xactions[] = id(new HeraldRuleTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_CREATE);
}
$xactions[] = id(new HeraldRuleTransaction())
->setTransactionType(HeraldRuleEditTransaction::TRANSACTIONTYPE)
->setNewValue($new_state);
$xactions[] = id(new HeraldRuleTransaction())
->setTransactionType(HeraldRuleNameTransaction::TRANSACTIONTYPE)
->setNewValue($new_name);
try {
id(new HeraldRuleEditor())
->setActor($this->getViewer())
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request)
->applyTransactions($rule, $xactions);
return array(null, null);
} catch (Exception $ex) {
$errors[] = $ex->getMessage();
}
}
// mutate current rule, so it would be sent to the client in the right state
$rule->setMustMatchAll((int)$match_all);
$rule->setName($new_name);
$rule->setRepetitionPolicyStringConstant($repetition_policy);
$rule->attachConditions($conditions);
$rule->attachActions($actions);
return array($e_name, $errors);
}
private function setupEditorBehavior(
HeraldRule $rule,
array $handles,
HeraldAdapter $adapter) {
$all_rules = $this->loadRulesThisRuleMayDependUpon($rule);
$all_rules = msortv($all_rules, 'getEditorSortVector');
$all_rules = mpull($all_rules, 'getEditorDisplayName', 'getPHID');
$all_fields = $adapter->getFieldNameMap();
$all_conditions = $adapter->getConditionNameMap();
$all_actions = $adapter->getActionNameMap($rule->getRuleType());
$fields = $adapter->getFields();
$field_map = array_select_keys($all_fields, $fields);
// Populate any fields which exist in the rule but which we don't know the
// names of, so that saving a rule without touching anything doesn't change
// it.
foreach ($rule->getConditions() as $condition) {
$field_name = $condition->getFieldName();
if (empty($field_map[$field_name])) {
$field_map[$field_name] = pht('<Unknown Field "%s">', $field_name);
}
}
$actions = $adapter->getActions($rule->getRuleType());
$action_map = array_select_keys($all_actions, $actions);
// Populate any actions which exist in the rule but which we don't know the
// names of, so that saving a rule without touching anything doesn't change
// it.
foreach ($rule->getActions() as $action) {
$action_name = $action->getAction();
if (empty($action_map[$action_name])) {
$action_map[$action_name] = pht('<Unknown Action "%s">', $action_name);
}
}
$config_info = array();
$config_info['fields'] = $this->getFieldGroups($adapter, $field_map);
$config_info['conditions'] = $all_conditions;
$config_info['actions'] = $this->getActionGroups($adapter, $action_map);
$config_info['valueMap'] = array();
foreach ($field_map as $field => $name) {
try {
$field_conditions = $adapter->getConditionsForField($field);
} catch (Exception $ex) {
$field_conditions = array(HeraldAdapter::CONDITION_UNCONDITIONALLY);
}
$config_info['conditionMap'][$field] = $field_conditions;
}
foreach ($field_map as $field => $fname) {
foreach ($config_info['conditionMap'][$field] as $condition) {
$value_key = $adapter->getValueTypeForFieldAndCondition(
$field,
$condition);
if ($value_key instanceof HeraldFieldValue) {
$value_key->setViewer($this->getViewer());
$spec = $value_key->getControlSpecificationDictionary();
$value_key = $value_key->getFieldValueKey();
$config_info['valueMap'][$value_key] = $spec;
}
$config_info['values'][$field][$condition] = $value_key;
}
}
$config_info['rule_type'] = $rule->getRuleType();
foreach ($action_map as $action => $name) {
try {
$value_key = $adapter->getValueTypeForAction(
$action,
$rule->getRuleType());
} catch (Exception $ex) {
$value_key = new HeraldEmptyFieldValue();
}
if ($value_key instanceof HeraldFieldValue) {
$value_key->setViewer($this->getViewer());
$spec = $value_key->getControlSpecificationDictionary();
$value_key = $value_key->getFieldValueKey();
$config_info['valueMap'][$value_key] = $spec;
}
$config_info['targets'][$action] = $value_key;
}
$default_group = head($config_info['fields']);
$default_field = head_key($default_group['options']);
$default_condition = head($config_info['conditionMap'][$default_field]);
$default_actions = head($config_info['actions']);
$default_action = head_key($default_actions['options']);
if ($rule->getConditions()) {
$serial_conditions = array();
foreach ($rule->getConditions() as $condition) {
$value = $adapter->getEditorValueForCondition(
$this->getViewer(),
$condition);
$serial_conditions[] = array(
$condition->getFieldName(),
$condition->getFieldCondition(),
$value,
);
}
} else {
$serial_conditions = array(
array($default_field, $default_condition, null),
);
}
if ($rule->getActions()) {
$serial_actions = array();
foreach ($rule->getActions() as $action) {
$value = $adapter->getEditorValueForAction(
$this->getViewer(),
$action);
$serial_actions[] = array(
$action->getAction(),
$value,
);
}
} else {
$serial_actions = array(
array($default_action, null),
);
}
Javelin::initBehavior(
'herald-rule-editor',
array(
'root' => 'herald-rule-edit-form',
'default' => array(
'field' => $default_field,
'condition' => $default_condition,
'action' => $default_action,
),
'conditions' => (object)$serial_conditions,
'actions' => (object)$serial_actions,
'template' => $this->buildTokenizerTemplates() + array(
'rules' => $all_rules,
),
'info' => $config_info,
));
}
private function loadHandlesForRule($rule) {
$phids = array();
foreach ($rule->getActions() as $action) {
if (!is_array($action->getTarget())) {
continue;
}
foreach ($action->getTarget() as $target) {
$target = (array)$target;
foreach ($target as $phid) {
$phids[] = $phid;
}
}
}
foreach ($rule->getConditions() as $condition) {
$value = $condition->getValue();
if (is_array($value)) {
foreach ($value as $phid) {
$phids[] = $phid;
}
}
}
$phids[] = $rule->getAuthorPHID();
if ($rule->isObjectRule()) {
$phids[] = $rule->getTriggerObjectPHID();
}
return $this->loadViewerHandles($phids);
}
/**
* Render the selector for the "When (all of | any of) these conditions are
* met:" element.
*/
private function renderMustMatchSelector($rule) {
return AphrontFormSelectControl::renderSelectTag(
$rule->getMustMatchAll() ? 'all' : 'any',
array(
'all' => pht('all of'),
'any' => pht('any of'),
),
array(
'name' => 'must_match',
));
}
/**
* Render the selector for "Take these actions (every time | only the first
* time) this rule matches..." element.
*/
private function renderRepetitionSelector($rule, HeraldAdapter $adapter) {
$repetition_policy = $rule->getRepetitionPolicyStringConstant();
$repetition_map = $this->getRepetitionOptionMap($adapter);
if (count($repetition_map) < 2) {
return head($repetition_map);
} else {
return AphrontFormSelectControl::renderSelectTag(
$repetition_policy,
$repetition_map,
array(
'name' => 'repetition_policy',
));
}
}
private function getRepetitionOptionMap(HeraldAdapter $adapter) {
$repetition_options = $adapter->getRepetitionOptions();
$repetition_names = HeraldRule::getRepetitionPolicySelectOptionMap();
return array_select_keys($repetition_names, $repetition_options);
}
protected function buildTokenizerTemplates() {
$template = new AphrontTokenizerTemplateView();
$template = $template->render();
return array(
'markup' => $template,
);
}
/**
* Load rules for the "Another Herald rule..." condition dropdown, which
* allows one rule to depend upon the success or failure of another rule.
*/
private function loadRulesThisRuleMayDependUpon(HeraldRule $rule) {
$viewer = $this->getRequest()->getUser();
// Any rule can depend on a global rule.
$all_rules = id(new HeraldRuleQuery())
->setViewer($viewer)
->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_GLOBAL))
->withContentTypes(array($rule->getContentType()))
->execute();
if ($rule->isObjectRule()) {
// Object rules may depend on other rules for the same object.
$all_rules += id(new HeraldRuleQuery())
->setViewer($viewer)
->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_OBJECT))
->withContentTypes(array($rule->getContentType()))
->withTriggerObjectPHIDs(array($rule->getTriggerObjectPHID()))
->execute();
}
if ($rule->isPersonalRule()) {
// Personal rules may depend upon your other personal rules.
$all_rules += id(new HeraldRuleQuery())
->setViewer($viewer)
->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_PERSONAL))
->withContentTypes(array($rule->getContentType()))
->withAuthorPHIDs(array($rule->getAuthorPHID()))
->execute();
}
// A rule can not depend upon itself.
unset($all_rules[$rule->getID()]);
return $all_rules;
}
private function getFieldGroups(HeraldAdapter $adapter, array $field_map) {
$group_map = array();
foreach ($field_map as $field_key => $field_name) {
$group_key = $adapter->getFieldGroupKey($field_key);
$group_map[$group_key][$field_key] = array(
'name' => $field_name,
'available' => $adapter->isFieldAvailable($field_key),
);
}
return $this->getGroups(
$group_map,
HeraldFieldGroup::getAllFieldGroups());
}
private function getActionGroups(HeraldAdapter $adapter, array $action_map) {
$group_map = array();
foreach ($action_map as $action_key => $action_name) {
$group_key = $adapter->getActionGroupKey($action_key);
$group_map[$group_key][$action_key] = array(
'name' => $action_name,
'available' => $adapter->isActionAvailable($action_key),
);
}
return $this->getGroups(
$group_map,
HeraldActionGroup::getAllActionGroups());
}
private function getGroups(array $item_map, array $group_list) {
assert_instances_of($group_list, 'HeraldGroup');
$groups = array();
foreach ($item_map as $group_key => $options) {
asort($options);
$group_object = idx($group_list, $group_key);
if ($group_object) {
$group_label = $group_object->getGroupLabel();
$group_order = $group_object->getSortKey();
} else {
$group_label = nonempty($group_key, pht('Other'));
$group_order = 'Z';
}
$groups[] = array(
'label' => $group_label,
'options' => $options,
'order' => $group_order,
);
}
return array_values(isort($groups, 'order'));
}
}
diff --git a/src/applications/herald/controller/HeraldWebhookViewController.php b/src/applications/herald/controller/HeraldWebhookViewController.php
index 5f6be9816c..dac4831c23 100644
--- a/src/applications/herald/controller/HeraldWebhookViewController.php
+++ b/src/applications/herald/controller/HeraldWebhookViewController.php
@@ -1,238 +1,238 @@
<?php
final class HeraldWebhookViewController
extends HeraldWebhookController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$hook = id(new HeraldWebhookQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->executeOne();
if (!$hook) {
return new Aphront404Response();
}
$header = $this->buildHeaderView($hook);
$warnings = null;
if ($hook->isInErrorBackoff($viewer)) {
$message = pht(
'Many requests to this webhook have failed recently (at least %s '.
'errors in the last %s seconds). New requests are temporarily paused.',
$hook->getErrorBackoffThreshold(),
$hook->getErrorBackoffWindow());
$warnings = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors(
array(
$message,
));
}
$curtain = $this->buildCurtain($hook);
$properties_view = $this->buildPropertiesView($hook);
$timeline = $this->buildTransactionTimeline(
$hook,
new HeraldWebhookTransactionQuery());
$timeline->setShouldTerminate(true);
$requests = id(new HeraldWebhookRequestQuery())
->setViewer($viewer)
->withWebhookPHIDs(array($hook->getPHID()))
->setLimit(20)
->execute();
$warnings = array();
if (PhabricatorEnv::getEnvConfig('phabricator.silent')) {
$message = pht(
- 'Phabricator is currently configured in silent mode, so it will not '.
+ 'This server is running in silent mode, so it will not '.
'publish webhooks. To adjust this setting, see '.
'@{config:phabricator.silent} in Config.');
$warnings[] = id(new PHUIInfoView())
->setTitle(pht('Silent Mode'))
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->appendChild(new PHUIRemarkupView($viewer, $message));
}
$requests_table = id(new HeraldWebhookRequestListView())
->setViewer($viewer)
->setRequests($requests)
->setHighlightID($request->getURIData('requestID'));
$requests_view = id(new PHUIObjectBoxView())
->setHeaderText(pht('Recent Requests'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($requests_table);
$rules_view = $this->newRulesView($hook);
$hook_view = id(new PHUITwoColumnView())
->setHeader($header)
->setMainColumn(
array(
$warnings,
$properties_view,
$rules_view,
$requests_view,
$timeline,
))
->setCurtain($curtain);
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Webhook %d', $hook->getID()))
->setBorder(true);
return $this->newPage()
->setTitle(
array(
pht('Webhook %d', $hook->getID()),
$hook->getName(),
))
->setCrumbs($crumbs)
->setPageObjectPHIDs(
array(
$hook->getPHID(),
))
->appendChild($hook_view);
}
private function buildHeaderView(HeraldWebhook $hook) {
$viewer = $this->getViewer();
$title = $hook->getName();
$status_icon = $hook->getStatusIcon();
$status_color = $hook->getStatusColor();
$status_name = $hook->getStatusDisplayName();
$header = id(new PHUIHeaderView())
->setHeader($title)
->setViewer($viewer)
->setPolicyObject($hook)
->setStatus($status_icon, $status_color, $status_name)
->setHeaderIcon('fa-cloud-upload');
return $header;
}
private function buildCurtain(HeraldWebhook $hook) {
$viewer = $this->getViewer();
$curtain = $this->newCurtainView($hook);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$hook,
PhabricatorPolicyCapability::CAN_EDIT);
$id = $hook->getID();
$edit_uri = $this->getApplicationURI("webhook/edit/{$id}/");
$test_uri = $this->getApplicationURI("webhook/test/{$id}/");
$key_view_uri = $this->getApplicationURI("webhook/key/view/{$id}/");
$key_cycle_uri = $this->getApplicationURI("webhook/key/cycle/{$id}/");
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Webhook'))
->setIcon('fa-pencil')
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit)
->setHref($edit_uri));
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('New Test Request'))
->setIcon('fa-cloud-upload')
->setDisabled(!$can_edit)
->setWorkflow(true)
->setHref($test_uri));
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('View HMAC Key'))
->setIcon('fa-key')
->setDisabled(!$can_edit)
->setWorkflow(true)
->setHref($key_view_uri));
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Regenerate HMAC Key'))
->setIcon('fa-refresh')
->setDisabled(!$can_edit)
->setWorkflow(true)
->setHref($key_cycle_uri));
return $curtain;
}
private function buildPropertiesView(HeraldWebhook $hook) {
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setViewer($viewer);
$properties->addProperty(
pht('URI'),
$hook->getWebhookURI());
$properties->addProperty(
pht('Status'),
$hook->getStatusDisplayName());
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Details'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($properties);
}
private function newRulesView(HeraldWebhook $hook) {
$viewer = $this->getViewer();
$rules = id(new HeraldRuleQuery())
->setViewer($viewer)
->withDisabled(false)
->withAffectedObjectPHIDs(array($hook->getPHID()))
->needValidateAuthors(true)
->setLimit(10)
->execute();
$list = id(new HeraldRuleListView())
->setViewer($viewer)
->setRules($rules)
->newObjectList();
$list->setNoDataString(pht('No active Herald rules call this webhook.'));
$more_href = new PhutilURI(
'/herald/',
array('affectedPHID' => $hook->getPHID()));
$more_link = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-list-ul')
->setText(pht('View All Rules'))
->setHref($more_href);
$header = id(new PHUIHeaderView())
->setHeader(pht('Called By Herald Rules'))
->addActionLink($more_link);
return id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($list);
}
}
diff --git a/src/applications/herald/query/HeraldRuleQuery.php b/src/applications/herald/query/HeraldRuleQuery.php
index e346a998d4..e104c44122 100644
--- a/src/applications/herald/query/HeraldRuleQuery.php
+++ b/src/applications/herald/query/HeraldRuleQuery.php
@@ -1,341 +1,337 @@
<?php
final class HeraldRuleQuery extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $authorPHIDs;
private $ruleTypes;
private $contentTypes;
private $disabled;
private $active;
private $datasourceQuery;
private $triggerObjectPHIDs;
private $affectedObjectPHIDs;
private $needConditionsAndActions;
private $needAppliedToPHIDs;
private $needValidateAuthors;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withAuthorPHIDs(array $author_phids) {
$this->authorPHIDs = $author_phids;
return $this;
}
public function withRuleTypes(array $types) {
$this->ruleTypes = $types;
return $this;
}
public function withContentTypes(array $types) {
$this->contentTypes = $types;
return $this;
}
public function withDisabled($disabled) {
$this->disabled = $disabled;
return $this;
}
public function withActive($active) {
$this->active = $active;
return $this;
}
public function withDatasourceQuery($query) {
$this->datasourceQuery = $query;
return $this;
}
public function withTriggerObjectPHIDs(array $phids) {
$this->triggerObjectPHIDs = $phids;
return $this;
}
public function withAffectedObjectPHIDs(array $phids) {
$this->affectedObjectPHIDs = $phids;
return $this;
}
public function needConditionsAndActions($need) {
$this->needConditionsAndActions = $need;
return $this;
}
public function needAppliedToPHIDs(array $phids) {
$this->needAppliedToPHIDs = $phids;
return $this;
}
public function needValidateAuthors($need) {
$this->needValidateAuthors = $need;
return $this;
}
public function newResultObject() {
return new HeraldRule();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $rules) {
$rule_ids = mpull($rules, 'getID');
// Filter out any rules that have invalid adapters, or have adapters the
// viewer isn't permitted to see or use (for example, Differential rules
// if the user can't use Differential or Differential is not installed).
$types = HeraldAdapter::getEnabledAdapterMap($this->getViewer());
foreach ($rules as $key => $rule) {
if (empty($types[$rule->getContentType()])) {
$this->didRejectResult($rule);
unset($rules[$key]);
}
}
if ($this->needValidateAuthors || ($this->active !== null)) {
$this->validateRuleAuthors($rules);
}
if ($this->active !== null) {
$need_active = (bool)$this->active;
foreach ($rules as $key => $rule) {
if ($rule->getIsDisabled()) {
$is_active = false;
} else if (!$rule->hasValidAuthor()) {
$is_active = false;
} else {
$is_active = true;
}
if ($is_active != $need_active) {
unset($rules[$key]);
}
}
}
if (!$rules) {
return array();
}
if ($this->needConditionsAndActions) {
$conditions = id(new HeraldCondition())->loadAllWhere(
'ruleID IN (%Ld)',
$rule_ids);
$conditions = mgroup($conditions, 'getRuleID');
$actions = id(new HeraldActionRecord())->loadAllWhere(
'ruleID IN (%Ld)',
$rule_ids);
$actions = mgroup($actions, 'getRuleID');
foreach ($rules as $rule) {
$rule->attachActions(idx($actions, $rule->getID(), array()));
$rule->attachConditions(idx($conditions, $rule->getID(), array()));
}
}
if ($this->needAppliedToPHIDs) {
$conn_r = id(new HeraldRule())->establishConnection('r');
$applied = queryfx_all(
$conn_r,
'SELECT * FROM %T WHERE ruleID IN (%Ld) AND phid IN (%Ls)',
HeraldRule::TABLE_RULE_APPLIED,
$rule_ids,
$this->needAppliedToPHIDs);
$map = array();
foreach ($applied as $row) {
$map[$row['ruleID']][$row['phid']] = true;
}
foreach ($rules as $rule) {
foreach ($this->needAppliedToPHIDs as $phid) {
$rule->setRuleApplied(
$phid,
isset($map[$rule->getID()][$phid]));
}
}
}
$object_phids = array();
foreach ($rules as $rule) {
if ($rule->isObjectRule()) {
$object_phids[] = $rule->getTriggerObjectPHID();
}
}
if ($object_phids) {
$objects = id(new PhabricatorObjectQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($object_phids)
->execute();
$objects = mpull($objects, null, 'getPHID');
} else {
$objects = array();
}
foreach ($rules as $key => $rule) {
if ($rule->isObjectRule()) {
$object = idx($objects, $rule->getTriggerObjectPHID());
if (!$object) {
unset($rules[$key]);
continue;
}
$rule->attachTriggerObject($object);
}
}
return $rules;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'rule.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'rule.phid IN (%Ls)',
$this->phids);
}
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'rule.authorPHID IN (%Ls)',
$this->authorPHIDs);
}
if ($this->ruleTypes !== null) {
$where[] = qsprintf(
$conn,
'rule.ruleType IN (%Ls)',
$this->ruleTypes);
}
if ($this->contentTypes !== null) {
$where[] = qsprintf(
$conn,
'rule.contentType IN (%Ls)',
$this->contentTypes);
}
if ($this->disabled !== null) {
$where[] = qsprintf(
$conn,
'rule.isDisabled = %d',
(int)$this->disabled);
}
if ($this->active !== null) {
$where[] = qsprintf(
$conn,
'rule.isDisabled = %d',
(int)(!$this->active));
}
if ($this->datasourceQuery !== null) {
$where[] = qsprintf(
$conn,
'rule.name LIKE %>',
$this->datasourceQuery);
}
if ($this->triggerObjectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'rule.triggerObjectPHID IN (%Ls)',
$this->triggerObjectPHIDs);
}
if ($this->affectedObjectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'edge_affects.dst IN (%Ls)',
$this->affectedObjectPHIDs);
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->affectedObjectPHIDs !== null) {
$joins[] = qsprintf(
$conn,
'JOIN %T edge_affects ON rule.phid = edge_affects.src
AND edge_affects.type = %d',
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
HeraldRuleActionAffectsObjectEdgeType::EDGECONST);
}
return $joins;
}
private function validateRuleAuthors(array $rules) {
// "Global" and "Object" rules always have valid authors.
foreach ($rules as $key => $rule) {
if ($rule->isGlobalRule() || $rule->isObjectRule()) {
$rule->attachValidAuthor(true);
unset($rules[$key]);
continue;
}
}
if (!$rules) {
return;
}
// For personal rules, the author needs to exist and not be disabled.
$user_phids = mpull($rules, 'getAuthorPHID');
$users = id(new PhabricatorPeopleQuery())
->setViewer($this->getViewer())
->withPHIDs($user_phids)
->execute();
$users = mpull($users, null, 'getPHID');
foreach ($rules as $key => $rule) {
$author_phid = $rule->getAuthorPHID();
if (empty($users[$author_phid])) {
$rule->attachValidAuthor(false);
continue;
}
if (!$users[$author_phid]->isUserActivated()) {
$rule->attachValidAuthor(false);
continue;
}
$rule->attachValidAuthor(true);
$rule->attachAuthor($users[$author_phid]);
}
}
public function getQueryApplicationClass() {
return 'PhabricatorHeraldApplication';
}
protected function getPrimaryTableAlias() {
return 'rule';
}
}
diff --git a/src/applications/herald/query/HeraldWebhookQuery.php b/src/applications/herald/query/HeraldWebhookQuery.php
index ca46880613..77307a71e6 100644
--- a/src/applications/herald/query/HeraldWebhookQuery.php
+++ b/src/applications/herald/query/HeraldWebhookQuery.php
@@ -1,64 +1,60 @@
<?php
final class HeraldWebhookQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $statuses;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function newResultObject() {
return new HeraldWebhook();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'status IN (%Ls)',
$this->statuses);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorHeraldApplication';
}
}
diff --git a/src/applications/herald/query/HeraldWebhookRequestQuery.php b/src/applications/herald/query/HeraldWebhookRequestQuery.php
index 4c71d48e05..f0a61a2dc5 100644
--- a/src/applications/herald/query/HeraldWebhookRequestQuery.php
+++ b/src/applications/herald/query/HeraldWebhookRequestQuery.php
@@ -1,126 +1,122 @@
<?php
final class HeraldWebhookRequestQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $webhookPHIDs;
private $lastRequestEpochMin;
private $lastRequestEpochMax;
private $lastRequestResults;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withWebhookPHIDs(array $phids) {
$this->webhookPHIDs = $phids;
return $this;
}
public function newResultObject() {
return new HeraldWebhookRequest();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
public function withLastRequestEpochBetween($epoch_min, $epoch_max) {
$this->lastRequestEpochMin = $epoch_min;
$this->lastRequestEpochMax = $epoch_max;
return $this;
}
public function withLastRequestResults(array $results) {
$this->lastRequestResults = $results;
return $this;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->webhookPHIDs !== null) {
$where[] = qsprintf(
$conn,
'webhookPHID IN (%Ls)',
$this->webhookPHIDs);
}
if ($this->lastRequestEpochMin !== null) {
$where[] = qsprintf(
$conn,
'lastRequestEpoch >= %d',
$this->lastRequestEpochMin);
}
if ($this->lastRequestEpochMax !== null) {
$where[] = qsprintf(
$conn,
'lastRequestEpoch <= %d',
$this->lastRequestEpochMax);
}
if ($this->lastRequestResults !== null) {
$where[] = qsprintf(
$conn,
'lastRequestResult IN (%Ls)',
$this->lastRequestResults);
}
return $where;
}
protected function willFilterPage(array $requests) {
$hook_phids = mpull($requests, 'getWebhookPHID');
$hooks = id(new HeraldWebhookQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($hook_phids)
->execute();
$hooks = mpull($hooks, null, 'getPHID');
foreach ($requests as $key => $request) {
$hook_phid = $request->getWebhookPHID();
$hook = idx($hooks, $hook_phid);
if (!$hook) {
unset($requests[$key]);
$this->didRejectResult($request);
continue;
}
$request->attachWebhook($hook);
}
return $requests;
}
public function getQueryApplicationClass() {
return 'PhabricatorHeraldApplication';
}
}
diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignController.php b/src/applications/legalpad/controller/LegalpadDocumentSignController.php
index fb15e2af8f..7798a2aa9d 100644
--- a/src/applications/legalpad/controller/LegalpadDocumentSignController.php
+++ b/src/applications/legalpad/controller/LegalpadDocumentSignController.php
@@ -1,714 +1,715 @@
<?php
final class LegalpadDocumentSignController extends LegalpadController {
private $isSessionGate;
public function shouldAllowPublic() {
return true;
}
public function shouldAllowLegallyNonCompliantUsers() {
return true;
}
public function setIsSessionGate($is_session_gate) {
$this->isSessionGate = $is_session_gate;
return $this;
}
public function getIsSessionGate() {
return $this->isSessionGate;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getUser();
$document = id(new LegalpadDocumentQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->needDocumentBodies(true)
->executeOne();
if (!$document) {
return new Aphront404Response();
}
$information = $this->readSignerInformation(
$document,
$request);
if ($information instanceof AphrontResponse) {
return $information;
}
list($signer_phid, $signature_data) = $information;
$signature = null;
$type_individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL;
$is_individual = ($document->getSignatureType() == $type_individual);
switch ($document->getSignatureType()) {
case LegalpadDocument::SIGNATURE_TYPE_NONE:
// nothing to sign means this should be true
$has_signed = true;
// this is a status UI element
$signed_status = null;
break;
case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL:
if ($signer_phid) {
// TODO: This is odd and should probably be adjusted after
// grey/external accounts work better, but use the omnipotent
// viewer to check for a signature so we can pick up
// anonymous/grey signatures.
$signature = id(new LegalpadDocumentSignatureQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withDocumentPHIDs(array($document->getPHID()))
->withSignerPHIDs(array($signer_phid))
->executeOne();
if ($signature && !$viewer->isLoggedIn()) {
return $this->newDialog()
->setTitle(pht('Already Signed'))
->appendParagraph(pht('You have already signed this document!'))
->addCancelButton('/'.$document->getMonogram(), pht('Okay'));
}
}
$signed_status = null;
if (!$signature) {
$has_signed = false;
$signature = id(new LegalpadDocumentSignature())
->setSignerPHID($signer_phid)
->setDocumentPHID($document->getPHID())
->setDocumentVersion($document->getVersions());
// If the user is logged in, show a notice that they haven't signed.
// If they aren't logged in, we can't be as sure, so don't show
// anything.
if ($viewer->isLoggedIn()) {
$signed_status = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors(
array(
pht('You have not signed this document yet.'),
));
}
} else {
$has_signed = true;
$signature_data = $signature->getSignatureData();
// In this case, we know they've signed.
$signed_at = $signature->getDateCreated();
if ($signature->getIsExemption()) {
$exemption_phid = $signature->getExemptionPHID();
$handles = $this->loadViewerHandles(array($exemption_phid));
$exemption_handle = $handles[$exemption_phid];
$signed_text = pht(
'You do not need to sign this document. '.
'%s added a signature exemption for you on %s.',
$exemption_handle->renderLink(),
phabricator_datetime($signed_at, $viewer));
} else {
$signed_text = pht(
'You signed this document on %s.',
phabricator_datetime($signed_at, $viewer));
}
$signed_status = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->setErrors(array($signed_text));
}
$field_errors = array(
'name' => true,
'email' => true,
'agree' => true,
);
$signature->setSignatureData($signature_data);
break;
case LegalpadDocument::SIGNATURE_TYPE_CORPORATION:
$signature = id(new LegalpadDocumentSignature())
->setDocumentPHID($document->getPHID())
->setDocumentVersion($document->getVersions());
if ($viewer->isLoggedIn()) {
$has_signed = false;
$signed_status = null;
} else {
// This just hides the form.
$has_signed = true;
$login_text = pht(
'This document requires a corporate signatory. You must log in to '.
'accept this document on behalf of a company you represent.');
$signed_status = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors(array($login_text));
}
$field_errors = array(
'name' => true,
'address' => true,
'contact.name' => true,
'email' => true,
);
$signature->setSignatureData($signature_data);
break;
}
$errors = array();
$hisec_token = null;
if ($request->isFormOrHisecPost() && !$has_signed) {
list($form_data, $errors, $field_errors) = $this->readSignatureForm(
$document,
$request);
$signature_data = $form_data + $signature_data;
$signature->setSignatureData($signature_data);
$signature->setSignatureType($document->getSignatureType());
$signature->setSignerName((string)idx($signature_data, 'name'));
$signature->setSignerEmail((string)idx($signature_data, 'email'));
$agree = $request->getExists('agree');
if (!$agree) {
$errors[] = pht(
'You must check "I agree to the terms laid forth above."');
$field_errors['agree'] = pht('Required');
}
if ($viewer->isLoggedIn() && $is_individual) {
$verified = LegalpadDocumentSignature::VERIFIED;
} else {
$verified = LegalpadDocumentSignature::UNVERIFIED;
}
$signature->setVerified($verified);
if (!$errors) {
// Require MFA to sign legal documents.
if ($viewer->isLoggedIn()) {
$workflow_key = sprintf(
'legalpad.sign(%s)',
$document->getPHID());
$hisec_token = id(new PhabricatorAuthSessionEngine())
->setWorkflowKey($workflow_key)
->requireHighSecurityToken(
$viewer,
$request,
$document->getURI());
}
$signature->save();
// If the viewer is logged in, signing for themselves, send them to
// the document page, which will show that they have signed the
// document. Unless of course they were required to sign the
// document to use Phabricator; in that case try really hard to
// re-direct them to where they wanted to go.
//
// Otherwise, send them to a completion page.
if ($viewer->isLoggedIn() && $is_individual) {
$next_uri = '/'.$document->getMonogram();
if ($document->getRequireSignature()) {
$request_uri = $request->getRequestURI();
$next_uri = (string)$request_uri;
}
} else {
$this->sendVerifySignatureEmail(
$document,
$signature);
$next_uri = $this->getApplicationURI('done/');
}
return id(new AphrontRedirectResponse())->setURI($next_uri);
}
}
$document_body = $document->getDocumentBody();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($viewer);
$engine->addObject(
$document_body,
LegalpadDocumentBody::MARKUP_FIELD_TEXT);
$engine->process();
$document_markup = $engine->getOutput(
$document_body,
LegalpadDocumentBody::MARKUP_FIELD_TEXT);
$title = $document_body->getTitle();
$manage_uri = $this->getApplicationURI('view/'.$document->getID().'/');
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$document,
PhabricatorPolicyCapability::CAN_EDIT);
// Use the last content update as the modified date. We don't want to
// show that a document like a TOS was "updated" by an incidental change
// to a field like the preamble or privacy settings which does not actually
// affect the content of the agreement.
$content_updated = $document_body->getDateCreated();
// NOTE: We're avoiding `setPolicyObject()` here so we don't pick up
// extra UI elements that are unnecessary and clutter the signature page.
// These details are available on the "Manage" page.
$header = id(new PHUIHeaderView())
->setHeader($title)
->setUser($viewer)
->setEpoch($content_updated);
// If we're showing the user this document because it's required to use
// Phabricator and they haven't signed it, don't show the "Manage" button,
// since it won't work.
$is_gate = $this->getIsSessionGate();
if (!$is_gate) {
$header->addActionLink(
id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-pencil')
->setText(pht('Manage'))
->setHref($manage_uri)
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
}
$preamble_box = null;
if (strlen($document->getPreamble())) {
$preamble_text = new PHUIRemarkupView($viewer, $document->getPreamble());
// NOTE: We're avoiding `setObject()` here so we don't pick up extra UI
// elements like "Subscribers". This information is available on the
// "Manage" page, but just clutters up the "Signature" page.
$preamble = id(new PHUIPropertyListView())
->setUser($viewer)
->addSectionHeader(pht('Preamble'))
->addTextContent($preamble_text);
$preamble_box = new PHUIPropertyGroupView();
$preamble_box->addPropertyList($preamble);
}
$content = id(new PHUIDocumentView())
->addClass('legalpad')
->setHeader($header)
->appendChild(
array(
$signed_status,
$preamble_box,
$document_markup,
));
$signature_box = null;
if (!$has_signed) {
$error_view = null;
if ($errors) {
$error_view = id(new PHUIInfoView())
->setErrors($errors);
}
$signature_form = $this->buildSignatureForm(
$document,
$signature,
$field_errors);
switch ($document->getSignatureType()) {
default:
break;
case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL:
case LegalpadDocument::SIGNATURE_TYPE_CORPORATION:
$box = id(new PHUIObjectBoxView())
->addClass('document-sign-box')
->setHeaderText(pht('Agree and Sign Document'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($signature_form);
if ($error_view) {
$box->setInfoView($error_view);
}
$signature_box = phutil_tag_div(
'phui-document-view-pro-box plt', $box);
break;
}
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setBorder(true);
$crumbs->addTextCrumb($document->getMonogram());
$box = id(new PHUITwoColumnView())
->setFooter($signature_box);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->setPageObjectPHIDs(array($document->getPHID()))
->appendChild(array(
$content,
$box,
));
}
private function readSignerInformation(
LegalpadDocument $document,
AphrontRequest $request) {
$viewer = $request->getUser();
$signer_phid = null;
$signature_data = array();
switch ($document->getSignatureType()) {
case LegalpadDocument::SIGNATURE_TYPE_NONE:
break;
case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL:
if ($viewer->isLoggedIn()) {
$signer_phid = $viewer->getPHID();
$signature_data = array(
'name' => $viewer->getRealName(),
'email' => $viewer->loadPrimaryEmailAddress(),
);
} else if ($request->isFormPost()) {
$email = new PhutilEmailAddress($request->getStr('email'));
if (strlen($email->getDomainName())) {
$email_obj = id(new PhabricatorUserEmail())
->loadOneWhere('address = %s', $email->getAddress());
if ($email_obj) {
return $this->signInResponse();
}
}
}
break;
case LegalpadDocument::SIGNATURE_TYPE_CORPORATION:
$signer_phid = $viewer->getPHID();
if ($signer_phid) {
$signature_data = array(
'contact.name' => $viewer->getRealName(),
'email' => $viewer->loadPrimaryEmailAddress(),
'actorPHID' => $viewer->getPHID(),
);
}
break;
}
return array($signer_phid, $signature_data);
}
private function buildSignatureForm(
LegalpadDocument $document,
LegalpadDocumentSignature $signature,
array $errors) {
$viewer = $this->getRequest()->getUser();
$data = $signature->getSignatureData();
$form = id(new AphrontFormView())
->setUser($viewer);
$signature_type = $document->getSignatureType();
switch ($signature_type) {
case LegalpadDocument::SIGNATURE_TYPE_NONE:
// bail out of here quick
return;
case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL:
$this->buildIndividualSignatureForm(
$form,
$document,
$signature,
$errors);
break;
case LegalpadDocument::SIGNATURE_TYPE_CORPORATION:
$this->buildCorporateSignatureForm(
$form,
$document,
$signature,
$errors);
break;
default:
throw new Exception(
pht(
'This document has an unknown signature type ("%s").',
$signature_type));
}
$form
->appendChild(
id(new AphrontFormCheckboxControl())
->setError(idx($errors, 'agree', null))
->addCheckbox(
'agree',
'agree',
pht('I agree to the terms laid forth above.'),
false));
if ($document->getRequireSignature()) {
$cancel_uri = '/logout/';
$cancel_text = pht('Log Out');
} else {
$cancel_uri = $this->getApplicationURI();
$cancel_text = pht('Cancel');
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Sign Document'))
->addCancelButton($cancel_uri, $cancel_text));
return $form;
}
private function buildIndividualSignatureForm(
AphrontFormView $form,
LegalpadDocument $document,
LegalpadDocumentSignature $signature,
array $errors) {
$data = $signature->getSignatureData();
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setValue(idx($data, 'name', ''))
->setName('name')
->setError(idx($errors, 'name', null)));
$viewer = $this->getRequest()->getUser();
if (!$viewer->isLoggedIn()) {
$form->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Email'))
->setValue(idx($data, 'email', ''))
->setName('email')
->setError(idx($errors, 'email', null)));
}
return $form;
}
private function buildCorporateSignatureForm(
AphrontFormView $form,
LegalpadDocument $document,
LegalpadDocumentSignature $signature,
array $errors) {
$data = $signature->getSignatureData();
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Company Name'))
->setValue(idx($data, 'name', ''))
->setName('name')
->setError(idx($errors, 'name', null)))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Company Address'))
->setValue(idx($data, 'address', ''))
->setName('address')
->setError(idx($errors, 'address', null)))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Contact Name'))
->setValue(idx($data, 'contact.name', ''))
->setName('contact.name')
->setError(idx($errors, 'contact.name', null)))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Contact Email'))
->setValue(idx($data, 'email', ''))
->setName('email')
->setError(idx($errors, 'email', null)));
return $form;
}
private function readSignatureForm(
LegalpadDocument $document,
AphrontRequest $request) {
$signature_type = $document->getSignatureType();
switch ($signature_type) {
case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL:
$result = $this->readIndividualSignatureForm(
$document,
$request);
break;
case LegalpadDocument::SIGNATURE_TYPE_CORPORATION:
$result = $this->readCorporateSignatureForm(
$document,
$request);
break;
default:
throw new Exception(
pht(
'This document has an unknown signature type ("%s").',
$signature_type));
}
return $result;
}
private function readIndividualSignatureForm(
LegalpadDocument $document,
AphrontRequest $request) {
$signature_data = array();
$errors = array();
$field_errors = array();
$name = $request->getStr('name');
if (!strlen($name)) {
$field_errors['name'] = pht('Required');
$errors[] = pht('Name field is required.');
} else {
$field_errors['name'] = null;
}
$signature_data['name'] = $name;
$viewer = $request->getUser();
if ($viewer->isLoggedIn()) {
$email = $viewer->loadPrimaryEmailAddress();
} else {
$email = $request->getStr('email');
$addr_obj = null;
if (!strlen($email)) {
$field_errors['email'] = pht('Required');
$errors[] = pht('Email field is required.');
} else {
$addr_obj = new PhutilEmailAddress($email);
$domain = $addr_obj->getDomainName();
if (!$domain) {
$field_errors['email'] = pht('Invalid');
$errors[] = pht('A valid email is required.');
} else {
$field_errors['email'] = null;
}
}
}
$signature_data['email'] = $email;
return array($signature_data, $errors, $field_errors);
}
private function readCorporateSignatureForm(
LegalpadDocument $document,
AphrontRequest $request) {
$viewer = $request->getUser();
if (!$viewer->isLoggedIn()) {
throw new Exception(
pht(
'You can not sign a document on behalf of a corporation unless '.
'you are logged in.'));
}
$signature_data = array();
$errors = array();
$field_errors = array();
$name = $request->getStr('name');
if (!strlen($name)) {
$field_errors['name'] = pht('Required');
$errors[] = pht('Company name is required.');
} else {
$field_errors['name'] = null;
}
$signature_data['name'] = $name;
$address = $request->getStr('address');
if (!strlen($address)) {
$field_errors['address'] = pht('Required');
$errors[] = pht('Company address is required.');
} else {
$field_errors['address'] = null;
}
$signature_data['address'] = $address;
$contact_name = $request->getStr('contact.name');
if (!strlen($contact_name)) {
$field_errors['contact.name'] = pht('Required');
$errors[] = pht('Contact name is required.');
} else {
$field_errors['contact.name'] = null;
}
$signature_data['contact.name'] = $contact_name;
$email = $request->getStr('email');
$addr_obj = null;
if (!strlen($email)) {
$field_errors['email'] = pht('Required');
$errors[] = pht('Contact email is required.');
} else {
$addr_obj = new PhutilEmailAddress($email);
$domain = $addr_obj->getDomainName();
if (!$domain) {
$field_errors['email'] = pht('Invalid');
$errors[] = pht('A valid email is required.');
} else {
$field_errors['email'] = null;
}
}
$signature_data['email'] = $email;
return array($signature_data, $errors, $field_errors);
}
private function sendVerifySignatureEmail(
LegalpadDocument $doc,
LegalpadDocumentSignature $signature) {
$signature_data = $signature->getSignatureData();
$email = new PhutilEmailAddress($signature_data['email']);
$doc_name = $doc->getTitle();
$doc_link = PhabricatorEnv::getProductionURI('/'.$doc->getMonogram());
$path = $this->getApplicationURI(sprintf(
'/verify/%s/',
$signature->getSecretKey()));
$link = PhabricatorEnv::getProductionURI($path);
$name = idx($signature_data, 'name');
$body = pht(
"%s:\n\n".
"This email address was used to sign a Legalpad document ".
- "in Phabricator:\n\n".
+ "in %s:\n\n".
" %s\n\n".
"Please verify you own this email address and accept the ".
"agreement by clicking this link:\n\n".
" %s\n\n".
"Your signature is not valid until you complete this ".
"verification step.\n\nYou can review the document here:\n\n".
" %s\n",
$name,
+ PlatformSymbols::getPlatformServerName(),
$doc_name,
$link,
$doc_link);
id(new PhabricatorMetaMTAMail())
->addRawTos(array($email->getAddress()))
->setSubject(pht('[Legalpad] Signature Verification'))
->setForceDelivery(true)
->setBody($body)
->setRelatedPHID($signature->getDocumentPHID())
->saveAndSend();
}
private function signInResponse() {
return id(new Aphront403Response())
->setForbiddenText(
pht(
'The email address specified is associated with an account. '.
'Please login to that account and sign this document again.'));
}
}
diff --git a/src/applications/legalpad/editor/LegalpadDocumentEditEngine.php b/src/applications/legalpad/editor/LegalpadDocumentEditEngine.php
index 814647b82a..fb3f54275a 100644
--- a/src/applications/legalpad/editor/LegalpadDocumentEditEngine.php
+++ b/src/applications/legalpad/editor/LegalpadDocumentEditEngine.php
@@ -1,169 +1,169 @@
<?php
final class LegalpadDocumentEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'legalpad.document';
public function getEngineName() {
return pht('Legalpad');
}
public function getEngineApplicationClass() {
return 'PhabricatorLegalpadApplication';
}
public function getSummaryHeader() {
return pht('Configure Legalpad Forms');
}
public function getSummaryText() {
return pht('Configure creation and editing documents in Legalpad.');
}
public function isEngineConfigurable() {
return false;
}
protected function newEditableObject() {
$viewer = $this->getViewer();
$document = LegalpadDocument::initializeNewDocument($viewer);
$body = id(new LegalpadDocumentBody())
->setCreatorPHID($viewer->getPHID());
$document->attachDocumentBody($body);
$document->setDocumentBodyPHID(PhabricatorPHIDConstants::PHID_VOID);
return $document;
}
protected function newObjectQuery() {
return id(new LegalpadDocumentQuery())
->needDocumentBodies(true);
}
protected function getObjectCreateTitleText($object) {
return pht('Create New Document');
}
protected function getObjectEditTitleText($object) {
$body = $object->getDocumentBody();
$title = $body->getTitle();
return pht('Edit Document: %s', $title);
}
protected function getObjectEditShortText($object) {
$body = $object->getDocumentBody();
return $body->getTitle();
}
protected function getObjectCreateShortText() {
return pht('Create Document');
}
protected function getObjectName() {
return pht('Document');
}
protected function getObjectCreateCancelURI($object) {
return $this->getApplication()->getApplicationURI('/');
}
protected function getEditorURI() {
return $this->getApplication()->getApplicationURI('edit/');
}
protected function getObjectViewURI($object) {
$id = $object->getID();
return $this->getApplication()->getApplicationURI('view/'.$id.'/');
}
protected function getCreateNewObjectPolicy() {
return $this->getApplication()->getPolicy(
LegalpadCreateDocumentsCapability::CAPABILITY);
}
protected function buildCustomEditFields($object) {
$viewer = $this->getViewer();
$body = $object->getDocumentBody();
$document_body = $body->getText();
$is_create = $this->getIsCreate();
$is_admin = $viewer->getIsAdmin();
$fields = array();
$fields[] =
id(new PhabricatorTextEditField())
->setKey('title')
->setLabel(pht('Title'))
->setDescription(pht('Document Title.'))
->setConduitTypeDescription(pht('New document title.'))
->setValue($object->getTitle())
->setIsRequired(true)
->setTransactionType(
LegalpadDocumentTitleTransaction::TRANSACTIONTYPE);
if ($is_create) {
$fields[] =
id(new PhabricatorSelectEditField())
->setKey('signatureType')
->setLabel(pht('Who Should Sign?'))
->setDescription(pht('Type of signature required'))
->setConduitTypeDescription(pht('New document signature type.'))
->setValue($object->getSignatureType())
->setOptions(LegalpadDocument::getSignatureTypeMap())
->setTransactionType(
LegalpadDocumentSignatureTypeTransaction::TRANSACTIONTYPE);
$show_require = true;
} else {
$fields[] = id(new PhabricatorStaticEditField())
->setLabel(pht('Who Should Sign?'))
->setValue($object->getSignatureTypeName());
$individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL;
$show_require = $object->getSignatureType() == $individual;
}
if ($show_require && $is_admin) {
$fields[] =
id(new PhabricatorBoolEditField())
->setKey('requireSignature')
->setOptions(
pht('No Signature Required'),
- pht('Signature Required to use Phabricator'))
+ pht('Signature Required to Log In'))
->setAsCheckbox(true)
->setTransactionType(
LegalpadDocumentRequireSignatureTransaction::TRANSACTIONTYPE)
->setDescription(pht('Marks this document as required signing.'))
->setConduitDescription(
pht('Marks this document as required signing.'))
->setValue($object->getRequireSignature());
}
$fields[] =
id(new PhabricatorRemarkupEditField())
->setKey('preamble')
->setLabel(pht('Preamble'))
->setDescription(pht('The preamble of the document.'))
->setConduitTypeDescription(pht('New document preamble.'))
->setValue($object->getPreamble())
->setTransactionType(
LegalpadDocumentPreambleTransaction::TRANSACTIONTYPE);
$fields[] =
id(new PhabricatorRemarkupEditField())
->setKey('text')
->setLabel(pht('Document Body'))
->setDescription(pht('The body of text of the document.'))
->setConduitTypeDescription(pht('New document body.'))
->setValue($document_body)
->setIsRequired(true)
->setTransactionType(
LegalpadDocumentTextTransaction::TRANSACTIONTYPE);
return $fields;
}
}
diff --git a/src/applications/legalpad/editor/LegalpadDocumentEditor.php b/src/applications/legalpad/editor/LegalpadDocumentEditor.php
index 4ede196e39..90f50564de 100644
--- a/src/applications/legalpad/editor/LegalpadDocumentEditor.php
+++ b/src/applications/legalpad/editor/LegalpadDocumentEditor.php
@@ -1,183 +1,183 @@
<?php
final class LegalpadDocumentEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorLegalpadApplication';
}
public function getEditorObjectsDescription() {
return pht('Legalpad Documents');
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorTransactions::TYPE_COMMENT;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
return $types;
}
public function getCreateObjectTitle($author, $object) {
return pht('%s created this document.', $author);
}
public function getCreateObjectTitleForFeed($author, $object) {
return pht('%s created %s.', $author, $object);
}
protected function applyFinalEffects(
PhabricatorLiskDAO $object,
array $xactions) {
$is_contribution = false;
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case LegalpadDocumentTitleTransaction::TRANSACTIONTYPE:
case LegalpadDocumentTextTransaction::TRANSACTIONTYPE:
$is_contribution = true;
break;
}
}
if ($is_contribution) {
$text = $object->getDocumentBody()->getText();
$title = $object->getDocumentBody()->getTitle();
$object->setVersions($object->getVersions() + 1);
$body = new LegalpadDocumentBody();
$body->setCreatorPHID($this->getActingAsPHID());
$body->setText($text);
$body->setTitle($title);
$body->setVersion($object->getVersions());
$body->setDocumentPHID($object->getPHID());
$body->save();
$object->setDocumentBodyPHID($body->getPHID());
$type = PhabricatorContributedToObjectEdgeType::EDGECONST;
id(new PhabricatorEdgeEditor())
->addEdge($this->getActingAsPHID(), $type, $object->getPHID())
->save();
$type = PhabricatorObjectHasContributorEdgeType::EDGECONST;
$contributors = PhabricatorEdgeQuery::loadDestinationPHIDs(
$object->getPHID(),
$type);
$object->setRecentContributorPHIDs(array_slice($contributors, 0, 3));
$object->setContributorCount(count($contributors));
$object->save();
}
return $xactions;
}
protected function validateAllTransactions(PhabricatorLiskDAO $object,
array $xactions) {
$errors = array();
$is_required = (bool)$object->getRequireSignature();
$document_type = $object->getSignatureType();
$individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL;
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case LegalpadDocumentRequireSignatureTransaction::TRANSACTIONTYPE:
$is_required = (bool)$xaction->getNewValue();
break;
case LegalpadDocumentSignatureTypeTransaction::TRANSACTIONTYPE:
$document_type = $xaction->getNewValue();
break;
}
}
if ($is_required && ($document_type != $individual)) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
LegalpadDocumentRequireSignatureTransaction::TRANSACTIONTYPE,
pht('Invalid'),
pht('Only documents with signature type "individual" may '.
- 'require signing to use Phabricator.'),
+ 'require signing to log in.'),
null);
}
return $errors;
}
/* -( Sending Mail )------------------------------------------------------- */
protected function shouldSendMail(
PhabricatorLiskDAO $object,
array $xactions) {
return true;
}
protected function buildReplyHandler(PhabricatorLiskDAO $object) {
return id(new LegalpadReplyHandler())
->setMailReceiver($object);
}
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$id = $object->getID();
$title = $object->getDocumentBody()->getTitle();
return id(new PhabricatorMetaMTAMail())
->setSubject("L{$id}: {$title}");
}
protected function getMailTo(PhabricatorLiskDAO $object) {
return array(
$object->getCreatorPHID(),
$this->requireActor()->getPHID(),
);
}
protected function shouldImplyCC(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case LegalpadDocumentTextTransaction::TRANSACTIONTYPE:
case LegalpadDocumentTitleTransaction::TRANSACTIONTYPE:
case LegalpadDocumentPreambleTransaction::TRANSACTIONTYPE:
case LegalpadDocumentRequireSignatureTransaction::TRANSACTIONTYPE:
return true;
}
return parent::shouldImplyCC($object, $xaction);
}
protected function buildMailBody(
PhabricatorLiskDAO $object,
array $xactions) {
$body = parent::buildMailBody($object, $xactions);
$body->addLinkSection(
pht('DOCUMENT DETAIL'),
PhabricatorEnv::getProductionURI('/legalpad/view/'.$object->getID().'/'));
return $body;
}
protected function getMailSubjectPrefix() {
return pht('[Legalpad]');
}
protected function shouldPublishFeedStory(
PhabricatorLiskDAO $object,
array $xactions) {
return false;
}
protected function supportsSearch() {
return false;
}
}
diff --git a/src/applications/legalpad/query/LegalpadDocumentQuery.php b/src/applications/legalpad/query/LegalpadDocumentQuery.php
index 3d79e9f3a1..854a187fab 100644
--- a/src/applications/legalpad/query/LegalpadDocumentQuery.php
+++ b/src/applications/legalpad/query/LegalpadDocumentQuery.php
@@ -1,273 +1,269 @@
<?php
final class LegalpadDocumentQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $creatorPHIDs;
private $contributorPHIDs;
private $signerPHIDs;
private $dateCreatedAfter;
private $dateCreatedBefore;
private $signatureRequired;
private $needDocumentBodies;
private $needContributors;
private $needSignatures;
private $needViewerSignatures;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withCreatorPHIDs(array $phids) {
$this->creatorPHIDs = $phids;
return $this;
}
public function withContributorPHIDs(array $phids) {
$this->contributorPHIDs = $phids;
return $this;
}
public function withSignerPHIDs(array $phids) {
$this->signerPHIDs = $phids;
return $this;
}
public function withSignatureRequired($bool) {
$this->signatureRequired = $bool;
return $this;
}
public function needDocumentBodies($need_bodies) {
$this->needDocumentBodies = $need_bodies;
return $this;
}
public function needContributors($need_contributors) {
$this->needContributors = $need_contributors;
return $this;
}
public function needSignatures($need_signatures) {
$this->needSignatures = $need_signatures;
return $this;
}
public function withDateCreatedBefore($date_created_before) {
$this->dateCreatedBefore = $date_created_before;
return $this;
}
public function withDateCreatedAfter($date_created_after) {
$this->dateCreatedAfter = $date_created_after;
return $this;
}
public function needViewerSignatures($need) {
$this->needViewerSignatures = $need;
return $this;
}
public function newResultObject() {
return new LegalpadDocument();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $documents) {
if ($this->needDocumentBodies) {
$documents = $this->loadDocumentBodies($documents);
}
if ($this->needContributors) {
$documents = $this->loadContributors($documents);
}
if ($this->needSignatures) {
$documents = $this->loadSignatures($documents);
}
if ($this->needViewerSignatures) {
if ($documents) {
if ($this->getViewer()->getPHID()) {
$signatures = id(new LegalpadDocumentSignatureQuery())
->setViewer($this->getViewer())
->withSignerPHIDs(array($this->getViewer()->getPHID()))
->withDocumentPHIDs(mpull($documents, 'getPHID'))
->execute();
$signatures = mpull($signatures, null, 'getDocumentPHID');
} else {
$signatures = array();
}
foreach ($documents as $document) {
$signature = idx($signatures, $document->getPHID());
$document->attachUserSignature(
$this->getViewer()->getPHID(),
$signature);
}
}
}
return $documents;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->contributorPHIDs !== null) {
$joins[] = qsprintf(
$conn,
'JOIN edge contributor ON contributor.src = d.phid
AND contributor.type = %d',
PhabricatorObjectHasContributorEdgeType::EDGECONST);
}
if ($this->signerPHIDs !== null) {
$joins[] = qsprintf(
$conn,
'JOIN %T signer ON signer.documentPHID = d.phid
AND signer.signerPHID IN (%Ls)',
id(new LegalpadDocumentSignature())->getTableName(),
$this->signerPHIDs);
}
return $joins;
}
protected function shouldGroupQueryResultRows() {
if ($this->contributorPHIDs) {
return true;
}
if ($this->signerPHIDs) {
return true;
}
return parent::shouldGroupQueryResultRows();
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'd.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'd.phid IN (%Ls)',
$this->phids);
}
if ($this->creatorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'd.creatorPHID IN (%Ls)',
$this->creatorPHIDs);
}
if ($this->dateCreatedAfter !== null) {
$where[] = qsprintf(
$conn,
'd.dateCreated >= %d',
$this->dateCreatedAfter);
}
if ($this->dateCreatedBefore !== null) {
$where[] = qsprintf(
$conn,
'd.dateCreated <= %d',
$this->dateCreatedBefore);
}
if ($this->contributorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'contributor.dst IN (%Ls)',
$this->contributorPHIDs);
}
if ($this->signatureRequired !== null) {
$where[] = qsprintf(
$conn,
'd.requireSignature = %d',
$this->signatureRequired);
}
return $where;
}
private function loadDocumentBodies(array $documents) {
$body_phids = mpull($documents, 'getDocumentBodyPHID');
$bodies = id(new LegalpadDocumentBody())->loadAllWhere(
'phid IN (%Ls)',
$body_phids);
$bodies = mpull($bodies, null, 'getPHID');
foreach ($documents as $document) {
$body = idx($bodies, $document->getDocumentBodyPHID());
$document->attachDocumentBody($body);
}
return $documents;
}
private function loadContributors(array $documents) {
$document_map = mpull($documents, null, 'getPHID');
$edge_type = PhabricatorObjectHasContributorEdgeType::EDGECONST;
$contributor_data = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array_keys($document_map))
->withEdgeTypes(array($edge_type))
->execute();
foreach ($document_map as $document_phid => $document) {
$data = $contributor_data[$document_phid];
$contributors = array_keys(idx($data, $edge_type, array()));
$document->attachContributors($contributors);
}
return $documents;
}
private function loadSignatures(array $documents) {
$document_map = mpull($documents, null, 'getPHID');
$signatures = id(new LegalpadDocumentSignatureQuery())
->setViewer($this->getViewer())
->withDocumentPHIDs(array_keys($document_map))
->execute();
$signatures = mgroup($signatures, 'getDocumentPHID');
foreach ($documents as $document) {
$sigs = idx($signatures, $document->getPHID(), array());
$document->attachSignatures($sigs);
}
return $documents;
}
public function getQueryApplicationClass() {
return 'PhabricatorLegalpadApplication';
}
protected function getPrimaryTableAlias() {
return 'd';
}
}
diff --git a/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php b/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php
index 8c4dd31ff1..591174be57 100644
--- a/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php
+++ b/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php
@@ -1,195 +1,194 @@
<?php
final class LegalpadDocumentSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
return pht('Legalpad Documents');
}
public function getApplicationClassName() {
return 'PhabricatorLegalpadApplication';
}
public function newQuery() {
return id(new LegalpadDocumentQuery())
->needViewerSignatures(true);
}
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorUsersSearchField())
->setLabel(pht('Signed By'))
->setKey('signerPHIDs')
->setAliases(array('signer', 'signers', 'signerPHID'))
->setDescription(
pht('Search for documents signed by given users.')),
id(new PhabricatorUsersSearchField())
->setLabel(pht('Creators'))
->setKey('creatorPHIDs')
->setAliases(array('creator', 'creators', 'creatorPHID'))
->setDescription(
pht('Search for documents with given creators.')),
id(new PhabricatorUsersSearchField())
->setLabel(pht('Contributors'))
->setKey('contributorPHIDs')
->setAliases(array('contributor', 'contributors', 'contributorPHID'))
->setDescription(
pht('Search for documents with given contributors.')),
id(new PhabricatorSearchDateField())
->setLabel(pht('Created After'))
->setKey('createdStart'),
id(new PhabricatorSearchDateField())
->setLabel(pht('Created Before'))
->setKey('createdEnd'),
);
}
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
if ($map['signerPHIDs']) {
$query->withSignerPHIDs($map['signerPHIDs']);
}
if ($map['contributorPHIDs']) {
$query->withContributorPHIDs($map['contributorPHIDs']);
}
if ($map['creatorPHIDs']) {
$query->withCreatorPHIDs($map['creatorPHIDs']);
}
if ($map['createdStart']) {
$query->withDateCreatedAfter($map['createdStart']);
}
if ($map['createdEnd']) {
$query->withDateCreatedAfter($map['createdStart']);
}
return $query;
}
protected function getURI($path) {
return '/legalpad/'.$path;
}
protected function getBuiltinQueryNames() {
$names = array();
if ($this->requireViewer()->isLoggedIn()) {
$names['signed'] = pht('Signed Documents');
}
$names['all'] = pht('All Documents');
return $names;
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
$viewer = $this->requireViewer();
switch ($query_key) {
case 'signed':
return $query->setParameter('signerPHIDs', array($viewer->getPHID()));
case 'all':
return $query;
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
protected function renderResultList(
array $documents,
PhabricatorSavedQuery $query,
array $handles) {
assert_instances_of($documents, 'LegalpadDocument');
$viewer = $this->requireViewer();
$list = new PHUIObjectItemListView();
$list->setUser($viewer);
foreach ($documents as $document) {
$last_updated = phabricator_date($document->getDateModified(), $viewer);
$title = $document->getTitle();
$item = id(new PHUIObjectItemView())
->setObjectName($document->getMonogram())
->setHeader($title)
->setHref('/'.$document->getMonogram())
->setObject($document);
$no_signatures = LegalpadDocument::SIGNATURE_TYPE_NONE;
if ($document->getSignatureType() == $no_signatures) {
$item->addIcon('none', pht('Not Signable'));
} else {
$type_name = $document->getSignatureTypeName();
$type_icon = $document->getSignatureTypeIcon();
$item->addIcon($type_icon, $type_name);
if ($viewer->getPHID()) {
$signature = $document->getUserSignature($viewer->getPHID());
} else {
$signature = null;
}
if ($signature) {
$item->addAttribute(
array(
id(new PHUIIconView())->setIcon('fa-check-square-o', 'green'),
' ',
pht(
'Signed on %s',
phabricator_date($signature->getDateCreated(), $viewer)),
));
} else {
$item->addAttribute(
array(
id(new PHUIIconView())->setIcon('fa-square-o', 'grey'),
' ',
pht('Not Signed'),
));
}
}
$item->addIcon(
'fa-pencil grey',
pht('Version %d (%s)', $document->getVersions(), $last_updated));
$list->addItem($item);
}
$result = new PhabricatorApplicationSearchResultView();
$result->setObjectList($list);
$result->setNoDataString(pht('No documents found.'));
return $result;
}
protected function getNewUserBody() {
$create_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('Create a Document'))
->setHref('/legalpad/edit/')
->setColor(PHUIButtonView::GREEN);
$icon = $this->getApplication()->getIcon();
$app_name = $this->getApplication()->getName();
$view = id(new PHUIBigInfoView())
->setIcon($icon)
->setTitle(pht('Welcome to %s', $app_name))
->setDescription(
- pht('Create documents and track signatures. Can also be re-used in '.
- 'other areas of Phabricator, like CLAs.'))
+ pht('Create documents and track signatures.'))
->addAction($create_button);
return $view;
}
}
diff --git a/src/applications/macro/query/PhabricatorMacroQuery.php b/src/applications/macro/query/PhabricatorMacroQuery.php
index 7635b68b73..70e7f7e688 100644
--- a/src/applications/macro/query/PhabricatorMacroQuery.php
+++ b/src/applications/macro/query/PhabricatorMacroQuery.php
@@ -1,268 +1,264 @@
<?php
final class PhabricatorMacroQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $authorPHIDs;
private $names;
private $nameLike;
private $namePrefix;
private $dateCreatedAfter;
private $dateCreatedBefore;
private $flagColor;
private $needFiles;
private $status = 'status-any';
const STATUS_ANY = 'status-any';
const STATUS_ACTIVE = 'status-active';
const STATUS_DISABLED = 'status-disabled';
public static function getStatusOptions() {
return array(
self::STATUS_ACTIVE => pht('Active Macros'),
self::STATUS_DISABLED => pht('Disabled Macros'),
self::STATUS_ANY => pht('Active and Disabled Macros'),
);
}
public static function getFlagColorsOptions() {
$options = array(
'-1' => pht('(No Filtering)'),
'-2' => pht('(Marked With Any Flag)'),
);
foreach (PhabricatorFlagColor::getColorNameMap() as $color => $name) {
$options[$color] = $name;
}
return $options;
}
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withAuthorPHIDs(array $author_phids) {
$this->authorPHIDs = $author_phids;
return $this;
}
public function withNameLike($name) {
$this->nameLike = $name;
return $this;
}
public function withNames(array $names) {
$this->names = $names;
return $this;
}
public function withNamePrefix($prefix) {
$this->namePrefix = $prefix;
return $this;
}
public function withStatus($status) {
$this->status = $status;
return $this;
}
public function withDateCreatedBefore($date_created_before) {
$this->dateCreatedBefore = $date_created_before;
return $this;
}
public function withDateCreatedAfter($date_created_after) {
$this->dateCreatedAfter = $date_created_after;
return $this;
}
public function withFlagColor($flag_color) {
$this->flagColor = $flag_color;
return $this;
}
public function needFiles($need_files) {
$this->needFiles = $need_files;
return $this;
}
public function newResultObject() {
return new PhabricatorFileImageMacro();
}
- protected function loadPage() {
- return $this->loadStandardPage(new PhabricatorFileImageMacro());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'm.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'm.phid IN (%Ls)',
$this->phids);
}
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'm.authorPHID IN (%Ls)',
$this->authorPHIDs);
}
- if (strlen($this->nameLike)) {
+ if (($this->nameLike !== null) && strlen($this->nameLike)) {
$where[] = qsprintf(
$conn,
'm.name LIKE %~',
$this->nameLike);
}
if ($this->names !== null) {
$where[] = qsprintf(
$conn,
'm.name IN (%Ls)',
$this->names);
}
- if (strlen($this->namePrefix)) {
+ if (($this->namePrefix !== null) && strlen($this->namePrefix)) {
$where[] = qsprintf(
$conn,
'm.name LIKE %>',
$this->namePrefix);
}
switch ($this->status) {
case self::STATUS_ACTIVE:
$where[] = qsprintf(
$conn,
'm.isDisabled = 0');
break;
case self::STATUS_DISABLED:
$where[] = qsprintf(
$conn,
'm.isDisabled = 1');
break;
case self::STATUS_ANY:
break;
default:
throw new Exception(pht("Unknown status '%s'!", $this->status));
}
if ($this->dateCreatedAfter) {
$where[] = qsprintf(
$conn,
'm.dateCreated >= %d',
$this->dateCreatedAfter);
}
if ($this->dateCreatedBefore) {
$where[] = qsprintf(
$conn,
'm.dateCreated <= %d',
$this->dateCreatedBefore);
}
if ($this->flagColor != '-1' && $this->flagColor !== null) {
if ($this->flagColor == '-2') {
$flag_colors = array_keys(PhabricatorFlagColor::getColorNameMap());
} else {
$flag_colors = array($this->flagColor);
}
$flags = id(new PhabricatorFlagQuery())
->withOwnerPHIDs(array($this->getViewer()->getPHID()))
->withTypes(array(PhabricatorMacroMacroPHIDType::TYPECONST))
->withColors($flag_colors)
->setViewer($this->getViewer())
->execute();
if (empty($flags)) {
throw new PhabricatorEmptyQueryException(pht('No matching flags.'));
} else {
$where[] = qsprintf(
$conn,
'm.phid IN (%Ls)',
mpull($flags, 'getObjectPHID'));
}
}
return $where;
}
protected function didFilterPage(array $macros) {
if ($this->needFiles) {
$file_phids = mpull($macros, 'getFilePHID');
$files = id(new PhabricatorFileQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($file_phids)
->execute();
$files = mpull($files, null, 'getPHID');
foreach ($macros as $key => $macro) {
$file = idx($files, $macro->getFilePHID());
if (!$file) {
unset($macros[$key]);
continue;
}
$macro->attachFile($file);
}
}
return $macros;
}
protected function getPrimaryTableAlias() {
return 'm';
}
public function getQueryApplicationClass() {
return 'PhabricatorMacroApplication';
}
public function getOrderableColumns() {
return parent::getOrderableColumns() + array(
'name' => array(
'table' => 'm',
'column' => 'name',
'type' => 'string',
'reverse' => true,
'unique' => true,
),
);
}
protected function newPagingMapFromPartialObject($object) {
return array(
'id' => (int)$object->getID(),
'name' => $object->getName(),
);
}
public function getBuiltinOrders() {
return array(
'name' => array(
'vector' => array('name'),
'name' => pht('Name'),
),
) + parent::getBuiltinOrders();
}
}
diff --git a/src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php b/src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php
index 26dc64c1f3..bb966a16cb 100644
--- a/src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php
+++ b/src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php
@@ -1,84 +1,66 @@
<?php
final class PhabricatorMacroAudioTransaction
extends PhabricatorMacroTransactionType {
const TRANSACTIONTYPE = 'macro:audio';
public function generateOldValue($object) {
return $object->getAudioPHID();
}
public function applyInternalEffects($object, $value) {
$object->setAudioPHID($value);
}
- public function applyExternalEffects($object, $value) {
- $old = $this->generateOldValue($object);
- $new = $value;
- $all = array();
- if ($old) {
- $all[] = $old;
- }
- if ($new) {
- $all[] = $new;
- }
+ public function extractFilePHIDs($object, $value) {
+ $file_phids = array();
- $files = id(new PhabricatorFileQuery())
- ->setViewer($this->getActor())
- ->withPHIDs($all)
- ->execute();
- $files = mpull($files, null, 'getPHID');
-
- $old_file = idx($files, $old);
- if ($old_file) {
- $old_file->detachFromObject($object->getPHID());
+ if ($value) {
+ $file_phids[] = $value;
}
- $new_file = idx($files, $new);
- if ($new_file) {
- $new_file->attachToObject($object->getPHID());
- }
+ return $file_phids;
}
public function getTitle() {
$new = $this->getNewValue();
$old = $this->getOldValue();
if (!$old) {
return pht(
'%s attached audio: %s.',
$this->renderAuthor(),
$this->renderHandle($new));
} else {
return pht(
'%s changed the audio for this macro from %s to %s.',
$this->renderAuthor(),
$this->renderHandle($old),
$this->renderHandle($new));
}
}
public function getTitleForFeed() {
$new = $this->getNewValue();
$old = $this->getOldValue();
if (!$old) {
return pht(
'%s attached audio to %s: %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderHandle($new));
} else {
return pht(
'%s changed the audio for %s from %s to %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderHandle($old),
$this->renderHandle($new));
}
}
public function getIcon() {
return 'fa-music';
}
}
diff --git a/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php b/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php
index fb0c56f1c1..4e9f019071 100644
--- a/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php
+++ b/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php
@@ -1,104 +1,80 @@
<?php
final class PhabricatorMacroFileTransaction
extends PhabricatorMacroTransactionType {
const TRANSACTIONTYPE = 'macro:file';
public function generateOldValue($object) {
return $object->getFilePHID();
}
public function applyInternalEffects($object, $value) {
$object->setFilePHID($value);
}
- public function applyExternalEffects($object, $value) {
- $old = $this->generateOldValue($object);
- $new = $value;
- $all = array();
- if ($old) {
- $all[] = $old;
- }
- if ($new) {
- $all[] = $new;
- }
-
- $files = id(new PhabricatorFileQuery())
- ->setViewer($this->getActor())
- ->withPHIDs($all)
- ->execute();
- $files = mpull($files, null, 'getPHID');
-
- $old_file = idx($files, $old);
- if ($old_file) {
- $old_file->detachFromObject($object->getPHID());
- }
-
- $new_file = idx($files, $new);
- if ($new_file) {
- $new_file->attachToObject($object->getPHID());
- }
+ public function extractFilePHIDs($object, $value) {
+ return array($value);
}
public function getTitle() {
return pht(
'%s changed the image for this macro.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s changed the image for %s.',
$this->renderAuthor(),
$this->renderObject());
}
public function validateTransactions($object, array $xactions) {
$errors = array();
$viewer = $this->getActor();
$old_phid = $object->getFilePHID();
foreach ($xactions as $xaction) {
$file_phid = $xaction->getNewValue();
if (!$old_phid) {
if ($this->isEmptyTextTransaction($file_phid, $xactions)) {
$errors[] = $this->newRequiredError(
pht('Image macros must have a file.'));
return $errors;
}
}
// Only validate if file was uploaded
if ($file_phid) {
$file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($file_phid))
->executeOne();
if (!$file) {
$errors[] = $this->newInvalidError(
pht('"%s" is not a valid file PHID.',
$file_phid));
} else {
if (!$file->isViewableImage()) {
$mime_type = $file->getMimeType();
$errors[] = $this->newInvalidError(
pht('File mime type of "%s" is not a valid viewable image.',
$mime_type));
}
}
}
}
return $errors;
}
public function getIcon() {
return 'fa-file-image-o';
}
}
diff --git a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
index 60a41a26ca..9bb3987415 100644
--- a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
+++ b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
@@ -1,530 +1,530 @@
<?php
final class PhabricatorManiphestConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Maniphest');
}
public function getDescription() {
return pht('Configure Maniphest.');
}
public function getIcon() {
return 'fa-anchor';
}
public function getGroup() {
return 'apps';
}
public function getOptions() {
$priority_type = 'maniphest.priorities';
$priority_defaults = array(
100 => array(
'name' => pht('Unbreak Now!'),
'keywords' => array('unbreak'),
'short' => pht('Unbreak!'),
'color' => 'pink',
),
90 => array(
'name' => pht('Needs Triage'),
'keywords' => array('triage'),
'short' => pht('Triage'),
'color' => 'violet',
),
80 => array(
'name' => pht('High'),
'keywords' => array('high'),
'short' => pht('High'),
'color' => 'red',
),
50 => array(
'name' => pht('Normal'),
'keywords' => array('normal'),
'short' => pht('Normal'),
'color' => 'orange',
),
25 => array(
'name' => pht('Low'),
'keywords' => array('low'),
'short' => pht('Low'),
'color' => 'yellow',
),
0 => array(
'name' => pht('Wishlist'),
'keywords' => array('wish', 'wishlist'),
'short' => pht('Wish'),
'color' => 'sky',
),
);
$status_type = 'maniphest.statuses';
$status_defaults = array(
'open' => array(
'name' => pht('Open'),
'special' => ManiphestTaskStatus::SPECIAL_DEFAULT,
'prefixes' => array(
'open',
'opens',
'reopen',
'reopens',
),
),
'resolved' => array(
'name' => pht('Resolved'),
'name.full' => pht('Closed, Resolved'),
'closed' => true,
'special' => ManiphestTaskStatus::SPECIAL_CLOSED,
'transaction.icon' => 'fa-check-circle',
'prefixes' => array(
'closed',
'closes',
'close',
'fix',
'fixes',
'fixed',
'resolve',
'resolves',
'resolved',
),
'suffixes' => array(
'as resolved',
'as fixed',
),
'keywords' => array('closed', 'fixed', 'resolved'),
),
'wontfix' => array(
'name' => pht('Wontfix'),
'name.full' => pht('Closed, Wontfix'),
'transaction.icon' => 'fa-ban',
'closed' => true,
'prefixes' => array(
'wontfix',
'wontfixes',
'wontfixed',
),
'suffixes' => array(
'as wontfix',
),
),
'invalid' => array(
'name' => pht('Invalid'),
'name.full' => pht('Closed, Invalid'),
'transaction.icon' => 'fa-minus-circle',
'closed' => true,
'claim' => false,
'prefixes' => array(
'invalidate',
'invalidates',
'invalidated',
),
'suffixes' => array(
'as invalid',
),
),
'duplicate' => array(
'name' => pht('Duplicate'),
'name.full' => pht('Closed, Duplicate'),
'transaction.icon' => 'fa-files-o',
'special' => ManiphestTaskStatus::SPECIAL_DUPLICATE,
'closed' => true,
'claim' => false,
),
'spite' => array(
'name' => pht('Spite'),
'name.full' => pht('Closed, Spite'),
'name.action' => pht('Spited'),
'transaction.icon' => 'fa-thumbs-o-down',
'silly' => true,
'closed' => true,
'prefixes' => array(
'spite',
'spites',
'spited',
),
'suffixes' => array(
'out of spite',
'as spite',
),
),
);
$status_description = $this->deformat(pht(<<<EOTEXT
Allows you to edit, add, or remove the task statuses available in Maniphest,
like "Open", "Resolved" and "Invalid". The configuration should contain a map
of status constants to status specifications (see defaults below for examples).
The constant for each status should be 1-12 characters long and contain only
lowercase letters and digits. Valid examples are "open", "closed", and
"invalid". Users will not normally see these values.
The keys you can provide in a specification are:
- `name` //Required string.// Name of the status, like "Invalid".
- `name.full` //Optional string.// Longer name, like "Closed, Invalid". This
appears on the task detail view in the header.
- `name.action` //Optional string.// Action name for email subjects, like
"Marked Invalid".
- `closed` //Optional bool.// Statuses are either "open" or "closed".
Specifying `true` here will mark the status as closed (like "Resolved" or
"Invalid"). By default, statuses are open.
- `special` //Optional string.// Mark this status as special. The special
statuses are:
- `default` This is the default status for newly created tasks. You must
designate one status as default, and it must be an open status.
- `closed` This is the default status for closed tasks (for example, tasks
closed via the "!close" action in email or via the quick close button in
Maniphest). You must designate one status as the default closed status,
and it must be a closed status.
- `duplicate` This is the status used when tasks are merged into one
another as duplicates. You must designate one status for duplicates,
and it must be a closed status.
- `transaction.icon` //Optional string.// Allows you to choose a different
icon to use for this status when showing status changes in the transaction
log. Please see UIExamples, Icons and Images for a list.
- `transaction.color` //Optional string.// Allows you to choose a different
color to use for this status when showing status changes in the transaction
log.
- `silly` //Optional bool.// Marks this status as silly, and thus wholly
inappropriate for use by serious businesses.
- `prefixes` //Optional list<string>.// Allows you to specify a list of
text prefixes which will trigger a task transition into this status
when mentioned in a commit message. For example, providing "closes" here
will allow users to move tasks to this status by writing `Closes T123` in
commit messages.
- `suffixes` //Optional list<string>.// Allows you to specify a list of
text suffixes which will trigger a task transition into this status
when mentioned in a commit message, after a valid prefix. For example,
providing "as invalid" here will allow users to move tasks
to this status by writing `Closes T123 as invalid`, even if another status
is selected by the "Closes" prefix.
- `keywords` //Optional list<string>.// Allows you to specify a list
of keywords which can be used with `!status` commands in email to select
this status.
- `disabled` //Optional bool.// Marks this status as no longer in use so
tasks can not be created or edited to have this status. Existing tasks with
this status will not be affected, but you can batch edit them or let them
die out on their own.
- `claim` //Optional bool.// By default, closing an unassigned task claims
it. You can set this to `false` to disable this behavior for a particular
status.
- `locked` //Optional string.// Lock tasks in this status. Specify "comments"
to lock comments (users who can edit the task may override this lock).
Specify "edits" to prevent anyone except the task owner from making edits.
- `mfa` //Optional bool.// Require all edits to this task to be signed with
multi-factor authentication.
Statuses will appear in the UI in the order specified. Note the status marked
`special` as `duplicate` is not settable directly and will not appear in UI
-elements, and that any status marked `silly` does not appear if Phabricator
+elements, and that any status marked `silly` does not appear if the software
is configured with `phabricator.serious-business` set to true.
Examining the default configuration and examples below will probably be helpful
in understanding these options.
EOTEXT
));
$status_example = array(
'open' => array(
'name' => pht('Open'),
'special' => 'default',
),
'closed' => array(
'name' => pht('Closed'),
'special' => 'closed',
'closed' => true,
),
'duplicate' => array(
'name' => pht('Duplicate'),
'special' => 'duplicate',
'closed' => true,
),
);
$json = new PhutilJSON();
$status_example = $json->encodeFormatted($status_example);
// This is intentionally blank for now, until we can move more Maniphest
// logic to custom fields.
$default_fields = array();
foreach ($default_fields as $key => $enabled) {
$default_fields[$key] = array(
'disabled' => !$enabled,
);
}
$custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType';
$fields_example = array(
'mycompany.estimated-hours' => array(
'name' => pht('Estimated Hours'),
'type' => 'int',
'caption' => pht('Estimated number of hours this will take.'),
),
);
$fields_json = id(new PhutilJSON())->encodeFormatted($fields_example);
$points_type = 'maniphest.points';
$points_example_1 = array(
'enabled' => true,
'label' => pht('Story Points'),
'action' => pht('Change Story Points'),
);
$points_json_1 = id(new PhutilJSON())->encodeFormatted($points_example_1);
$points_example_2 = array(
'enabled' => true,
'label' => pht('Estimated Hours'),
'action' => pht('Change Estimate'),
);
$points_json_2 = id(new PhutilJSON())->encodeFormatted($points_example_2);
$points_description = $this->deformat(pht(<<<EOTEXT
Activates a points field on tasks. You can use points for estimation or
planning. If configured, points will appear on workboards.
To activate points, set this value to a map with these keys:
- `enabled` //Optional bool.// Use `true` to enable points, or
`false` to disable them.
- `label` //Optional string.// Label for points, like "Story Points" or
"Estimated Hours". If omitted, points will be called "Points".
- `action` //Optional string.// Label for the action which changes points
in Maniphest, like "Change Estimate". If omitted, the action will
be called "Change Points".
See the example below for a starting point.
EOTEXT
));
$subtype_type = 'maniphest.subtypes';
$subtype_default_key = PhabricatorEditEngineSubtype::SUBTYPE_DEFAULT;
$subtype_example = array(
array(
'key' => $subtype_default_key,
'name' => pht('Task'),
),
array(
'key' => 'bug',
'name' => pht('Bug'),
),
array(
'key' => 'feature',
'name' => pht('Feature Request'),
),
);
$subtype_example = id(new PhutilJSON())->encodeAsList($subtype_example);
$subtype_default = array(
array(
'key' => $subtype_default_key,
'name' => pht('Task'),
),
);
$subtype_description = $this->deformat(pht(<<<EOTEXT
Allows you to define task subtypes. Subtypes let you hide fields you don't
need to simplify the workflows for editing tasks.
To define subtypes, provide a list of subtypes. Each subtype should be a
dictionary with these keys:
- `key` //Required string.// Internal identifier for the subtype, like
"task", "feature", or "bug".
- `name` //Required string.// Human-readable name for this subtype, like
"Task", "Feature Request" or "Bug Report".
- `tag` //Optional string.// Tag text for this subtype.
- `color` //Optional string.// Display color for this subtype.
- `icon` //Optional string.// Icon for the subtype.
- `children` //Optional map.// Configure options shown to the user when
they "Create Subtask". See below.
- `fields` //Optional map.// Configure field behaviors. See below.
- `mutations` //Optional list.// Configure which subtypes this subtype
can easily be converted to by using the "Change Subtype" action. See below.
Each subtype must have a unique key, and you must define a subtype with
the key "%s", which is used as a default subtype.
The tag text (`tag`) is used to set the text shown in the subtype tag on list
views and workboards. If you do not configure it, the default subtype will have
no subtype tag and other subtypes will use their name as tag text.
The `children` key allows you to configure which options are presented to the
user when they "Create Subtask" from a task of this subtype. You can specify
these keys:
- `subtypes`: //Optional list<string>.// Show users creation forms for these
task subtypes.
- `forms`: //Optional list<string|int>.// Show users these specific forms,
in order.
If you don't specify either constraint, users will be shown creation forms
for the same subtype.
For example, if you have a "quest" subtype and do not configure `children`,
users who click "Create Subtask" will be presented with all create forms for
"quest" tasks.
If you want to present them with forms for a different task subtype or set of
subtypes instead, use `subtypes`:
```
{
...
"children": {
"subtypes": ["objective", "boss", "reward"]
}
...
}
```
If you want to present them with specific forms, use `forms` and specify form
IDs:
```
{
...
"children": {
"forms": [12, 16]
}
...
}
```
When specifying forms by ID explicitly, the order you specify the forms in will
be used when presenting options to the user.
If only one option would be presented, the user will be taken directly to the
appropriate form instead of being prompted to choose a form.
The `fields` key can configure the behavior of custom fields on specific
task subtypes. For example:
```
{
...
"fields": {
"custom.some-field": {
"disabled": true
}
}
...
}
```
Each field supports these options:
- `disabled` //Optional bool.// Allows you to disable fields on certain
subtypes.
- `name` //Optional string.// Custom name of this field for the subtype.
The `mutations` key allows you to control the behavior of the "Change Subtype"
action above the comment area. By default, this action allows users to change
the task subtype into any other subtype.
If you'd prefer to make it more difficult to change subtypes or offer only a
subset of subtypes, you can specify the list of subtypes that "Change Subtypes"
offers. For example, if you have several similar subtypes and want to allow
tasks to be converted between them but not easily converted to other types,
you can make the "Change Subtypes" control show only these options like this:
```
{
...
"mutations": ["bug", "issue", "defect"]
...
}
```
If you specify an empty list, the "Change Subtypes" action will be completely
hidden.
This mutation list is advisory and only configures the UI. Tasks may still be
converted across subtypes freely by using the Bulk Editor or API.
EOTEXT
,
$subtype_default_key));
$priorities_description = $this->deformat(pht(<<<EOTEXT
Allows you to edit or override the default priorities available in Maniphest,
like "High", "Normal" and "Low". The configuration should contain a map of
numeric priority values (where larger numbers correspond to higher priorities)
to priority specifications (see defaults below for examples).
The keys you can define for a priority are:
- `name` //Required string.// Name of the priority.
- `keywords` //Required list<string>.// List of unique keywords which identify
this priority, like "high" or "low". Each priority must have at least one
keyword and two priorities may not share the same keyword.
- `short` //Optional string.// Alternate shorter name, used in UIs where
there is less space available.
- `color` //Optional string.// Color for this priority, like "red" or
"blue".
- `disabled` //Optional bool.// Set to true to prevent users from choosing
this priority when creating or editing tasks. Existing tasks will not be
affected, and can be batch edited to a different priority or left to
eventually die out.
You can choose the default priority for newly created tasks with
"maniphest.default-priority".
EOTEXT
));
$fields_description = $this->deformat(pht(<<<EOTEXT
List of custom fields for Maniphest tasks.
For details on adding custom fields to Maniphest, see [[ %s | %s ]] in the
documentation.
EOTEXT
,
PhabricatorEnv::getDoclink('Configuring Custom Fields'),
pht('Configuring Custom Fields')));
return array(
$this->newOption('maniphest.custom-field-definitions', 'wild', array())
->setSummary(pht('Custom Maniphest fields.'))
->setDescription($fields_description)
->addExample($fields_json, pht('Valid setting')),
$this->newOption('maniphest.fields', $custom_field_type, $default_fields)
->setCustomData(id(new ManiphestTask())->getCustomFieldBaseClass())
->setDescription(pht('Select and reorder task fields.')),
$this->newOption(
'maniphest.priorities',
$priority_type,
$priority_defaults)
->setSummary(pht('Configure Maniphest priority names.'))
->setDescription($priorities_description),
$this->newOption('maniphest.statuses', $status_type, $status_defaults)
->setSummary(pht('Configure Maniphest task statuses.'))
->setDescription($status_description)
->addExample($status_example, pht('Minimal Valid Config')),
$this->newOption('maniphest.default-priority', 'int', 90)
->setSummary(pht('Default task priority for create flows.'))
->setDescription(
pht(
'Choose a default priority for newly created tasks. You can '.
'review and adjust available priorities by using the '.
'%s configuration option. The default value (`90`) '.
'corresponds to the default "Needs Triage" priority.',
'maniphest.priorities')),
$this->newOption('maniphest.points', $points_type, array())
->setSummary(pht('Configure point values for tasks.'))
->setDescription($points_description)
->addExample($points_json_1, pht('Points Config'))
->addExample($points_json_2, pht('Hours Config')),
$this->newOption('maniphest.subtypes', $subtype_type, $subtype_default)
->setSummary(pht('Define task subtypes.'))
->setDescription($subtype_description)
->addExample($subtype_example, pht('Simple Subtypes')),
);
}
}
diff --git a/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php
index 0ebef78976..e1d2749acc 100644
--- a/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php
+++ b/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php
@@ -1,167 +1,168 @@
<?php
final class ManiphestTaskOwnerTransaction
extends ManiphestTaskTransactionType {
const TRANSACTIONTYPE = 'reassign';
public function generateOldValue($object) {
return nonempty($object->getOwnerPHID(), null);
}
public function applyInternalEffects($object, $value) {
// Update the "ownerOrdering" column to contain the full name of the
// owner, if the task is assigned.
$handle = null;
if ($value) {
$handle = id(new PhabricatorHandleQuery())
->setViewer($this->getActor())
->withPHIDs(array($value))
->executeOne();
}
if ($handle) {
$object->setOwnerOrdering($handle->getName());
} else {
$object->setOwnerOrdering(null);
}
$object->setOwnerPHID($value);
}
public function getActionStrength() {
return 120;
}
public function getActionName() {
$old = $this->getOldValue();
$new = $this->getNewValue();
if ($this->getAuthorPHID() == $new) {
return pht('Claimed');
} else if (!$new) {
return pht('Unassigned');
} else if (!$old) {
return pht('Assigned');
} else {
return pht('Reassigned');
}
}
public function getTitle() {
$old = $this->getOldValue();
$new = $this->getNewValue();
if ($this->getAuthorPHID() == $new) {
return pht(
'%s claimed this task.',
$this->renderAuthor());
} else if (!$new) {
return pht(
'%s removed %s as the assignee of this task.',
$this->renderAuthor(),
$this->renderHandle($old));
} else if (!$old) {
return pht(
'%s assigned this task to %s.',
$this->renderAuthor(),
$this->renderHandle($new));
} else {
return pht(
'%s reassigned this task from %s to %s.',
$this->renderAuthor(),
$this->renderHandle($old),
$this->renderHandle($new));
}
}
public function getTitleForFeed() {
$old = $this->getOldValue();
$new = $this->getNewValue();
if ($this->getAuthorPHID() == $new) {
return pht(
'%s claimed %s.',
$this->renderAuthor(),
$this->renderObject());
} else if (!$new) {
return pht(
'%s placed %s up for grabs.',
$this->renderAuthor(),
$this->renderObject());
} else if (!$old) {
return pht(
'%s assigned %s to %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderHandle($new));
} else {
return pht(
'%s reassigned %s from %s to %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderHandle($old),
$this->renderHandle($new));
}
}
public function validateTransactions($object, array $xactions) {
$errors = array();
foreach ($xactions as $xaction) {
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
- if (!strlen($new)) {
+
+ if (!phutil_nonempty_string($new)) {
continue;
}
if ($new === $old) {
continue;
}
$assignee_list = id(new PhabricatorPeopleQuery())
->setViewer($this->getActor())
->withPHIDs(array($new))
->execute();
if (!$assignee_list) {
$errors[] = $this->newInvalidError(
pht('User "%s" is not a valid user.', $new));
}
}
return $errors;
}
public function getIcon() {
return 'fa-user';
}
public function getColor() {
$old = $this->getOldValue();
$new = $this->getNewValue();
if ($this->getAuthorPHID() == $new) {
return 'green';
} else if (!$new) {
return 'black';
} else if (!$old) {
return 'green';
} else {
return 'green';
}
}
public function getTransactionTypeForConduit($xaction) {
return 'owner';
}
public function getFieldValuesForConduit($xaction, $data) {
return array(
'old' => $xaction->getOldValue(),
'new' => $xaction->getNewValue(),
);
}
}
diff --git a/src/applications/meta/controller/PhabricatorApplicationEmailCommandsController.php b/src/applications/meta/controller/PhabricatorApplicationEmailCommandsController.php
index 43ed2e957e..596ed5755b 100644
--- a/src/applications/meta/controller/PhabricatorApplicationEmailCommandsController.php
+++ b/src/applications/meta/controller/PhabricatorApplicationEmailCommandsController.php
@@ -1,148 +1,149 @@
<?php
final class PhabricatorApplicationEmailCommandsController
extends PhabricatorApplicationsController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$application = $request->getURIData('application');
$selected = id(new PhabricatorApplicationQuery())
->setViewer($viewer)
->withClasses(array($application))
->executeOne();
if (!$selected) {
return new Aphront404Response();
}
$specs = $selected->getMailCommandObjects();
$type = $request->getURIData('type');
if (empty($specs[$type])) {
return new Aphront404Response();
}
$spec = $specs[$type];
$commands = MetaMTAEmailTransactionCommand::getAllCommandsForObject(
$spec['object']);
$commands = msort($commands, 'getCommand');
$content = array();
$content[] = '= '.pht('Mail Commands Overview');
$content[] = pht(
- 'After configuring Phabricator to process inbound mail, you can '.
+ 'After configuring processing for inbound mail, you can '.
'interact with objects (like tasks and revisions) over email. For '.
- 'information on configuring Phabricator, see '.
+ 'information on configuring inbound mail, see '.
'**[[ %s | Configuring Inbound Email ]]**.'.
"\n\n".
- 'In most cases, you can reply to email you receive from Phabricator '.
+ 'In most cases, you can reply to email you receive from this server '.
'to leave comments. You can also use **mail commands** to take a '.
'greater range of actions (like claiming a task or requesting changes '.
'to a revision) without needing to log in to the web UI.'.
"\n\n".
'Mail commands are keywords which start with an exclamation point, '.
'like `!claim`. Some commands may take parameters, like '.
"`!assign alincoln`.\n\n".
'To use mail commands, write one command per line at the beginning '.
'or end of your mail message. For example, you could write this in a '.
'reply to task email to claim the task:'.
"\n\n```\n!claim\n\nI'll take care of this.\n```\n\n\n".
- "When Phabricator receives your mail, it will process any commands ".
+ "When %s receives your mail, it will process any commands ".
"first, then post the remaining message body as a comment. You can ".
"execute multiple commands at once:".
"\n\n```\n!assign alincoln\n!close\n\nI just talked to @alincoln, ".
"and he showed me that he fixed this.\n```\n",
- PhabricatorEnv::getDoclink('Configuring Inbound Email'));
+ PhabricatorEnv::getDoclink('Configuring Inbound Email'),
+ PlatformSymbols::getPlatformServerName());
$content[] = '= '.$spec['header'];
$content[] = $spec['summary'];
$content[] = '= '.pht('Quick Reference');
$content[] = pht(
'This table summarizes the available mail commands. For details on a '.
'specific command, see the command section below.');
$table = array();
$table[] = '| '.pht('Command').' | '.pht('Summary').' |';
$table[] = '|---|---|';
foreach ($commands as $command) {
$summary = $command->getCommandSummary();
$table[] = '| '.$command->getCommandSyntax().' | '.$summary;
}
$table = implode("\n", $table);
$content[] = $table;
foreach ($commands as $command) {
$content[] = '== !'.$command->getCommand().' ==';
$content[] = $command->getCommandSummary();
$aliases = $command->getCommandAliases();
if ($aliases) {
foreach ($aliases as $key => $alias) {
$aliases[$key] = '!'.$alias;
}
$aliases = implode(', ', $aliases);
} else {
$aliases = '//None//';
}
$syntax = $command->getCommandSyntax();
$table = array();
$table[] = '| '.pht('Property').' | '.pht('Value');
$table[] = '|---|---|';
$table[] = '| **'.pht('Syntax').'** | '.$syntax;
$table[] = '| **'.pht('Aliases').'** | '.$aliases;
$table[] = '| **'.pht('Class').'** | `'.get_class($command).'`';
$table = implode("\n", $table);
$content[] = $table;
$description = $command->getCommandDescription();
if ($description) {
$content[] = $description;
}
}
$content = implode("\n\n", $content);
$title = $spec['name'];
$crumbs = $this->buildApplicationCrumbs();
$this->addApplicationCrumb($crumbs, $selected);
$crumbs->addTextCrumb($title);
$crumbs->setBorder(true);
$content_box = new PHUIRemarkupView($viewer, $content);
$info_view = null;
if (!PhabricatorEnv::getEnvConfig('metamta.reply-handler-domain')) {
$error = pht(
- "Phabricator is not currently configured to accept inbound mail. ".
+ "This server is not currently configured to accept inbound mail. ".
"You won't be able to interact with objects over email until ".
"inbound mail is set up.");
$info_view = id(new PHUIInfoView())
->setErrors(array($error));
}
$header = id(new PHUIHeaderView())
->setHeader($title);
$document = id(new PHUIDocumentView())
->setHeader($header)
->appendChild($info_view)
->appendChild($content_box);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($document);
}
}
diff --git a/src/applications/meta/controller/PhabricatorApplicationUninstallController.php b/src/applications/meta/controller/PhabricatorApplicationUninstallController.php
index 09f3e92ee5..e89976c42c 100644
--- a/src/applications/meta/controller/PhabricatorApplicationUninstallController.php
+++ b/src/applications/meta/controller/PhabricatorApplicationUninstallController.php
@@ -1,124 +1,123 @@
<?php
final class PhabricatorApplicationUninstallController
extends PhabricatorApplicationsController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$user = $request->getUser();
$action = $request->getURIData('action');
$application_name = $request->getURIData('application');
$application = id(new PhabricatorApplicationQuery())
->setViewer($viewer)
->withClasses(array($application_name))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$application) {
return new Aphront404Response();
}
$view_uri = $this->getApplicationURI('view/'.$application_name);
$prototypes_enabled = PhabricatorEnv::getEnvConfig(
'phabricator.show-prototypes');
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->addCancelButton($view_uri);
if ($application->isPrototype() && !$prototypes_enabled) {
$dialog
->setTitle(pht('Prototypes Not Enabled'))
->appendChild(
pht(
'To manage prototypes, enable them by setting %s in your '.
- 'Phabricator configuration.',
+ 'configuration.',
phutil_tag('tt', array(), 'phabricator.show-prototypes')));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
if ($request->isDialogFormPost()) {
$xactions = array();
$template = $application->getApplicationTransactionTemplate();
$xactions[] = id(clone $template)
->setTransactionType(
PhabricatorApplicationUninstallTransaction::TRANSACTIONTYPE)
->setNewValue($action);
$editor = id(new PhabricatorApplicationEditor())
->setActor($user)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
try {
$editor->applyTransactions($application, $xactions);
return id(new AphrontRedirectResponse())->setURI($view_uri);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
}
return $this->newDialog()
->setTitle(pht('Validation Failed'))
->setValidationException($validation_exception)
->addCancelButton($view_uri);
}
if ($action == 'install') {
if ($application->canUninstall()) {
$dialog
->setTitle(pht('Confirmation'))
->appendChild(
pht(
'Install %s application?',
$application->getName()))
->addSubmitButton(pht('Install'));
} else {
$dialog
->setTitle(pht('Information'))
->appendChild(pht('You cannot install an installed application.'));
}
} else {
if ($application->canUninstall()) {
$dialog->setTitle(pht('Really Uninstall Application?'));
if ($application instanceof PhabricatorHomeApplication) {
$dialog
->appendParagraph(
pht(
'Are you absolutely certain you want to uninstall the Home '.
'application?'))
->appendParagraph(
pht(
'This is very unusual and will leave you without any '.
- 'content on the Phabricator home page. You should only '.
- 'do this if you are certain you know what you are doing.'))
- ->addSubmitButton(pht('Completely Break Phabricator'));
+ 'content on the home page. You should only do this if you '.
+ 'are certain you know what you are doing.'))
+ ->addSubmitButton(pht('Completely Break Everything'));
} else {
$dialog
->appendParagraph(
pht(
'Really uninstall the %s application?',
$application->getName()))
->addSubmitButton(pht('Uninstall'));
}
} else {
$dialog
->setTitle(pht('Information'))
->appendChild(
pht(
- 'This application cannot be uninstalled, '.
- 'because it is required for Phabricator to work.'));
+ 'This application is required and cannot be uninstalled.'));
}
}
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}
diff --git a/src/applications/metamta/adapter/PhabricatorMailTestAdapter.php b/src/applications/metamta/adapter/PhabricatorMailTestAdapter.php
index a6258a8874..311dd78b99 100644
--- a/src/applications/metamta/adapter/PhabricatorMailTestAdapter.php
+++ b/src/applications/metamta/adapter/PhabricatorMailTestAdapter.php
@@ -1,161 +1,161 @@
<?php
/**
* Mail adapter that doesn't actually send any email, for writing unit tests
* against.
*/
final class PhabricatorMailTestAdapter
extends PhabricatorMailAdapter {
const ADAPTERTYPE = 'test';
private $guts = array();
private $supportsMessageID;
private $failPermanently;
private $failTemporarily;
public function setSupportsMessageID($support) {
$this->supportsMessageID = $support;
return $this;
}
public function setFailPermanently($fail) {
$this->failPermanently = true;
return $this;
}
public function setFailTemporarily($fail) {
$this->failTemporarily = true;
return $this;
}
public function getSupportedMessageTypes() {
return array(
PhabricatorMailEmailMessage::MESSAGETYPE,
PhabricatorMailSMSMessage::MESSAGETYPE,
);
}
protected function validateOptions(array $options) {
PhutilTypeSpec::checkMap($options, array());
}
public function newDefaultOptions() {
return array();
}
public function supportsMessageIDHeader() {
return $this->supportsMessageID;
}
public function getGuts() {
return $this->guts;
}
public function sendMessage(PhabricatorMailExternalMessage $message) {
if ($this->failPermanently) {
throw new PhabricatorMetaMTAPermanentFailureException(
pht('Unit Test (Permanent)'));
}
if ($this->failTemporarily) {
throw new Exception(
pht('Unit Test (Temporary)'));
}
switch ($message->getMessageType()) {
case PhabricatorMailEmailMessage::MESSAGETYPE:
$guts = $this->newEmailGuts($message);
break;
case PhabricatorMailSMSMessage::MESSAGETYPE:
$guts = $this->newSMSGuts($message);
break;
}
$guts['did-send'] = true;
$this->guts = $guts;
}
public function getBody() {
return idx($this->guts, 'body');
}
public function getHTMLBody() {
return idx($this->guts, 'html-body');
}
private function newEmailGuts(PhabricatorMailExternalMessage $message) {
$guts = array();
$from = $message->getFromAddress();
$guts['from'] = (string)$from;
$reply_to = $message->getReplyToAddress();
if ($reply_to) {
$guts['reply-to'] = (string)$reply_to;
}
$to_addresses = $message->getToAddresses();
$to = array();
foreach ($to_addresses as $address) {
$to[] = (string)$address;
}
$guts['tos'] = $to;
$cc_addresses = $message->getCCAddresses();
$cc = array();
foreach ($cc_addresses as $address) {
$cc[] = (string)$address;
}
$guts['ccs'] = $cc;
$subject = $message->getSubject();
if (strlen($subject)) {
$guts['subject'] = $subject;
}
$headers = $message->getHeaders();
$header_list = array();
foreach ($headers as $header) {
$header_list[] = array(
$header->getName(),
$header->getValue(),
);
}
$guts['headers'] = $header_list;
$text_body = $message->getTextBody();
- if (strlen($text_body)) {
+ if (phutil_nonempty_string($text_body)) {
$guts['body'] = $text_body;
}
$html_body = $message->getHTMLBody();
- if (strlen($html_body)) {
+ if (phutil_nonempty_string($html_body)) {
$guts['html-body'] = $html_body;
}
$attachments = $message->getAttachments();
$file_list = array();
foreach ($attachments as $attachment) {
$file_list[] = array(
'data' => $attachment->getData(),
'filename' => $attachment->getFilename(),
'mimetype' => $attachment->getMimeType(),
);
}
$guts['attachments'] = $file_list;
return $guts;
}
private function newSMSGuts(PhabricatorMailExternalMessage $message) {
$guts = array();
$guts['to'] = $message->getToNumber();
$guts['body'] = $message->getTextBody();
return $guts;
}
}
diff --git a/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php b/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php
index 2f9ddcf22b..5f6911db0f 100644
--- a/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php
+++ b/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php
@@ -1,422 +1,422 @@
<?php
final class PhabricatorMetaMTAApplicationEmailPanel
extends PhabricatorApplicationConfigurationPanel {
public function getPanelKey() {
return 'email';
}
public function shouldShowForApplication(
PhabricatorApplication $application) {
return $application->supportsEmailIntegration();
}
public function buildConfigurationPagePanel() {
$viewer = $this->getViewer();
$application = $this->getApplication();
$table = $this->buildEmailTable($is_edit = false, null);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$application,
PhabricatorPolicyCapability::CAN_EDIT);
$header = id(new PHUIHeaderView())
->setHeader(pht('Application Emails'))
->addActionLink(
id(new PHUIButtonView())
->setTag('a')
->setText(pht('Edit Application Emails'))
->setIcon('fa-pencil')
->setHref($this->getPanelURI())
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($table);
return $box;
}
public function handlePanelRequest(
AphrontRequest $request,
PhabricatorController $controller) {
$viewer = $request->getViewer();
$application = $this->getApplication();
$path = $request->getURIData('path');
if (strlen($path)) {
return new Aphront404Response();
}
$uri = new PhutilURI($request->getPath());
$new = $request->getStr('new');
$edit = $request->getInt('edit');
$delete = $request->getInt('delete');
if ($new) {
return $this->returnNewAddressResponse($request, $uri, $application);
}
if ($edit) {
return $this->returnEditAddressResponse($request, $uri, $edit);
}
if ($delete) {
return $this->returnDeleteAddressResponse($request, $uri, $delete);
}
$table = $this->buildEmailTable(
$is_edit = true,
$request->getInt('id'));
$form = id(new AphrontFormView())
->setUser($viewer);
$crumbs = $controller->buildPanelCrumbs($this);
$crumbs->addTextCrumb(pht('Edit Application Emails'));
$crumbs->setBorder(true);
$header = id(new PHUIHeaderView())
->setHeader(pht('Edit Application Emails: %s', $application->getName()))
->setSubheader($application->getAppEmailBlurb())
->setHeaderIcon('fa-pencil');
$icon = id(new PHUIIconView())
->setIcon('fa-plus');
$button = new PHUIButtonView();
$button->setText(pht('Add New Address'));
$button->setTag('a');
$button->setHref($uri->alter('new', 'true'));
$button->setIcon($icon);
$button->addSigil('workflow');
$header->addActionLink($button);
$object_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Emails'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($table);
$title = $application->getName();
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter($object_box);
return $controller->buildPanelPage(
$this,
$title,
$crumbs,
$view);
}
private function returnNewAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
PhabricatorApplication $application) {
$viewer = $request->getUser();
$email_object =
PhabricatorMetaMTAApplicationEmail::initializeNewAppEmail($viewer)
->setApplicationPHID($application->getPHID());
return $this->returnSaveAddressResponse(
$request,
$uri,
$email_object,
$is_new = true);
}
private function returnEditAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
$email_object_id) {
$viewer = $request->getUser();
$email_object = id(new PhabricatorMetaMTAApplicationEmailQuery())
->setViewer($viewer)
->withIDs(array($email_object_id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$email_object) {
return new Aphront404Response();
}
return $this->returnSaveAddressResponse(
$request,
$uri,
$email_object,
$is_new = false);
}
private function returnSaveAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
PhabricatorMetaMTAApplicationEmail $email_object,
$is_new) {
$viewer = $request->getUser();
$config_default =
PhabricatorMetaMTAApplicationEmail::CONFIG_DEFAULT_AUTHOR;
$e_email = true;
$v_email = $email_object->getAddress();
$e_space = null;
$v_space = $email_object->getSpacePHID();
$v_default = $email_object->getConfigValue($config_default);
$validation_exception = null;
if ($request->isDialogFormPost()) {
$e_email = null;
$v_email = trim($request->getStr('email'));
$v_space = $request->getStr('spacePHID');
$v_default = $request->getArr($config_default);
$v_default = nonempty(head($v_default), null);
$type_address =
PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS;
$type_space = PhabricatorTransactions::TYPE_SPACE;
$type_config =
PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG;
$key_config = PhabricatorMetaMTAApplicationEmailTransaction::KEY_CONFIG;
$xactions = array();
$xactions[] = id(new PhabricatorMetaMTAApplicationEmailTransaction())
->setTransactionType($type_address)
->setNewValue($v_email);
$xactions[] = id(new PhabricatorMetaMTAApplicationEmailTransaction())
->setTransactionType($type_space)
->setNewValue($v_space);
$xactions[] = id(new PhabricatorMetaMTAApplicationEmailTransaction())
->setTransactionType($type_config)
->setMetadataValue($key_config, $config_default)
->setNewValue($v_default);
$editor = id(new PhabricatorMetaMTAApplicationEmailEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true);
try {
$editor->applyTransactions($email_object, $xactions);
return id(new AphrontRedirectResponse())->setURI(
$uri->alter('highlight', $email_object->getID()));
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
$e_email = $ex->getShortMessage($type_address);
$e_space = $ex->getShortMessage($type_space);
}
}
if ($v_default) {
$v_default = array($v_default);
} else {
$v_default = array();
}
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Email'))
->setName('email')
->setValue($v_email)
->setError($e_email));
if (PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($viewer)) {
$form->appendControl(
id(new AphrontFormSelectControl())
->setLabel(pht('Space'))
->setName('spacePHID')
->setValue($v_space)
->setError($e_space)
->setOptions(
PhabricatorSpacesNamespaceQuery::getSpaceOptionsForViewer(
$viewer,
$v_space)));
}
$form
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new PhabricatorPeopleDatasource())
->setLabel(pht('Default Author'))
->setName($config_default)
->setLimit(1)
->setValue($v_default)
->setCaption(
pht(
'Used if the "From:" address does not map to a user account. '.
'Setting a default author will allow anyone on the public '.
- 'internet to create objects in Phabricator by sending email to '.
+ 'internet to create objects by sending email to '.
'this address.')));
if ($is_new) {
$title = pht('New Address');
} else {
$title = pht('Edit Address');
}
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setWidth(AphrontDialogView::WIDTH_FORM)
->setTitle($title)
->setValidationException($validation_exception)
->appendForm($form)
->addSubmitButton(pht('Save'))
->addCancelButton($uri);
if ($is_new) {
$dialog->addHiddenInput('new', 'true');
}
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function returnDeleteAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
$email_object_id) {
$viewer = $this->getViewer();
$email_object = id(new PhabricatorMetaMTAApplicationEmailQuery())
->setViewer($viewer)
->withIDs(array($email_object_id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$email_object) {
return new Aphront404Response();
}
if ($request->isDialogFormPost()) {
$engine = new PhabricatorDestructionEngine();
$engine->destroyObject($email_object);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->addHiddenInput('delete', $email_object_id)
->setTitle(pht('Delete Address'))
->appendParagraph(pht(
'Are you sure you want to delete this email address?'))
->addSubmitButton(pht('Delete'))
->addCancelButton($uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function buildEmailTable($is_edit, $highlight) {
$viewer = $this->getViewer();
$application = $this->getApplication();
$uri = new PhutilURI($this->getPanelURI());
$emails = id(new PhabricatorMetaMTAApplicationEmailQuery())
->setViewer($viewer)
->withApplicationPHIDs(array($application->getPHID()))
->execute();
$rowc = array();
$rows = array();
foreach ($emails as $email) {
$button_edit = javelin_tag(
'a',
array(
'class' => 'button small button-grey',
'href' => $uri->alter('edit', $email->getID()),
'sigil' => 'workflow',
),
pht('Edit'));
$button_remove = javelin_tag(
'a',
array(
'class' => 'button small button-grey',
'href' => $uri->alter('delete', $email->getID()),
'sigil' => 'workflow',
),
pht('Delete'));
if ($highlight == $email->getID()) {
$rowc[] = 'highlighted';
} else {
$rowc[] = null;
}
$space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID($email);
if ($space_phid) {
$email_space = $viewer->renderHandle($space_phid);
} else {
$email_space = null;
}
$default_author_phid = $email->getDefaultAuthorPHID();
if (!$default_author_phid) {
$default_author = phutil_tag('em', array(), pht('None'));
} else {
$default_author = $viewer->renderHandle($default_author_phid);
}
$rows[] = array(
$email_space,
$email->getAddress(),
$default_author,
$button_edit,
$button_remove,
);
}
$table = id(new AphrontTableView($rows))
->setNoDataString(pht('No application emails created yet.'));
$table->setHeaders(
array(
pht('Space'),
pht('Email'),
pht('Default'),
pht('Edit'),
pht('Delete'),
));
$table->setColumnClasses(
array(
'',
'',
'wide',
'action',
'action',
));
$table->setRowClasses($rowc);
$table->setColumnVisibility(
array(
PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($viewer),
true,
true,
$is_edit,
$is_edit,
));
return $table;
}
}
diff --git a/src/applications/metamta/constants/MetaMTAReceivedMailStatus.php b/src/applications/metamta/constants/MetaMTAReceivedMailStatus.php
index faacdc2cfc..871d47f97f 100644
--- a/src/applications/metamta/constants/MetaMTAReceivedMailStatus.php
+++ b/src/applications/metamta/constants/MetaMTAReceivedMailStatus.php
@@ -1,42 +1,42 @@
<?php
final class MetaMTAReceivedMailStatus
extends Phobject {
const STATUS_DUPLICATE = 'err:duplicate';
const STATUS_FROM_PHABRICATOR = 'err:self';
const STATUS_NO_RECEIVERS = 'err:no-receivers';
const STATUS_UNKNOWN_SENDER = 'err:unknown-sender';
const STATUS_DISABLED_SENDER = 'err:disabled-sender';
const STATUS_NO_PUBLIC_MAIL = 'err:no-public-mail';
const STATUS_USER_MISMATCH = 'err:bad-user';
const STATUS_POLICY_PROBLEM = 'err:policy';
const STATUS_NO_SUCH_OBJECT = 'err:not-found';
const STATUS_HASH_MISMATCH = 'err:bad-hash';
const STATUS_UNHANDLED_EXCEPTION = 'err:exception';
const STATUS_EMPTY = 'err:empty';
const STATUS_EMPTY_IGNORED = 'err:empty-ignored';
const STATUS_RESERVED = 'err:reserved-recipient';
public static function getHumanReadableName($status) {
$map = array(
self::STATUS_DUPLICATE => pht('Duplicate Message'),
- self::STATUS_FROM_PHABRICATOR => pht('Phabricator Mail'),
+ self::STATUS_FROM_PHABRICATOR => pht('Mail From Self'),
self::STATUS_NO_RECEIVERS => pht('No Receivers'),
self::STATUS_UNKNOWN_SENDER => pht('Unknown Sender'),
self::STATUS_DISABLED_SENDER => pht('Disabled Sender'),
self::STATUS_NO_PUBLIC_MAIL => pht('No Public Mail'),
self::STATUS_USER_MISMATCH => pht('User Mismatch'),
self::STATUS_POLICY_PROBLEM => pht('Policy Error'),
self::STATUS_NO_SUCH_OBJECT => pht('No Such Object'),
self::STATUS_HASH_MISMATCH => pht('Bad Address'),
self::STATUS_UNHANDLED_EXCEPTION => pht('Unhandled Exception'),
self::STATUS_EMPTY => pht('Empty Mail'),
self::STATUS_EMPTY_IGNORED => pht('Ignored Empty Mail'),
self::STATUS_RESERVED => pht('Reserved Recipient'),
);
return idx($map, $status, pht('Processing Exception'));
}
}
diff --git a/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php b/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php
index d7d31ba254..e38c7f9801 100644
--- a/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php
+++ b/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php
@@ -1,450 +1,452 @@
<?php
final class PhabricatorMetaMTAMailViewController
extends PhabricatorMetaMTAController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$mail = id(new PhabricatorMetaMTAMailQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->executeOne();
if (!$mail) {
return new Aphront404Response();
}
if ($mail->hasSensitiveContent()) {
$title = pht('Content Redacted');
} else {
$title = $mail->getSubject();
}
$header = id(new PHUIHeaderView())
->setHeader($title)
->setUser($viewer)
->setPolicyObject($mail)
->setHeaderIcon('fa-envelope');
$status = $mail->getStatus();
$name = PhabricatorMailOutboundStatus::getStatusName($status);
$icon = PhabricatorMailOutboundStatus::getStatusIcon($status);
$color = PhabricatorMailOutboundStatus::getStatusColor($status);
$header->setStatus($icon, $color, $name);
if ($mail->getMustEncrypt()) {
Javelin::initBehavior('phabricator-tooltips');
$header->addTag(
id(new PHUITagView())
->setType(PHUITagView::TYPE_SHADE)
->setColor('blue')
->setName(pht('Must Encrypt'))
->setIcon('fa-shield blue')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht(
'Message content can only be transmitted over secure '.
'channels.'),
)));
}
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Mail %d', $mail->getID()))
->setBorder(true);
$tab_group = id(new PHUITabGroupView())
->addTab(
id(new PHUITabView())
->setName(pht('Message'))
->setKey('message')
->appendChild($this->buildMessageProperties($mail)))
->addTab(
id(new PHUITabView())
->setName(pht('Headers'))
->setKey('headers')
->appendChild($this->buildHeaderProperties($mail)))
->addTab(
id(new PHUITabView())
->setName(pht('Delivery'))
->setKey('delivery')
->appendChild($this->buildDeliveryProperties($mail)))
->addTab(
id(new PHUITabView())
->setName(pht('Metadata'))
->setKey('metadata')
->appendChild($this->buildMetadataProperties($mail)));
$header_view = id(new PHUIHeaderView())
->setHeader(pht('Mail'));
$object_phid = $mail->getRelatedPHID();
if ($object_phid) {
$handles = $viewer->loadHandles(array($object_phid));
$handle = $handles[$object_phid];
if ($handle->isComplete() && $handle->getURI()) {
$view_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('View Object'))
->setIcon('fa-chevron-right')
->setHref($handle->getURI());
$header_view->addActionLink($view_button);
}
}
$object_box = id(new PHUIObjectBoxView())
->setHeader($header_view)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addTabGroup($tab_group);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter($object_box);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->setPageObjectPHIDs(array($mail->getPHID()))
->appendChild($view);
}
private function buildMessageProperties(PhabricatorMetaMTAMail $mail) {
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($mail);
if ($mail->getFrom()) {
$from_str = $viewer->renderHandle($mail->getFrom());
} else {
- $from_str = pht('Sent by Phabricator');
+ $from_str = pht(
+ 'Sent by %s',
+ PlatformSymbols::getPlatformServerName());
}
$properties->addProperty(
pht('From'),
$from_str);
if ($mail->getToPHIDs()) {
$to_list = $viewer->renderHandleList($mail->getToPHIDs());
} else {
$to_list = pht('None');
}
$properties->addProperty(
pht('To'),
$to_list);
if ($mail->getCcPHIDs()) {
$cc_list = $viewer->renderHandleList($mail->getCcPHIDs());
} else {
$cc_list = pht('None');
}
$properties->addProperty(
pht('Cc'),
$cc_list);
$properties->addProperty(
pht('Sent'),
phabricator_datetime($mail->getDateCreated(), $viewer));
$properties->addSectionHeader(
pht('Message'),
PHUIPropertyListView::ICON_SUMMARY);
if ($mail->hasSensitiveContent()) {
$body = phutil_tag(
'em',
array(),
pht(
'The content of this mail is sensitive and it can not be '.
'viewed from the web UI.'));
} else {
$body = phutil_tag(
'div',
array(
'style' => 'white-space: pre-wrap',
),
$mail->getBody());
}
$properties->addTextContent($body);
$file_phids = $mail->getAttachmentFilePHIDs();
if ($file_phids) {
$properties->addProperty(
pht('Attached Files'),
$viewer->loadHandles($file_phids)->renderList());
}
return $properties;
}
private function buildHeaderProperties(PhabricatorMetaMTAMail $mail) {
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setUser($viewer)
->setStacked(true);
$headers = $mail->getDeliveredHeaders();
if (!$headers) {
$headers = array();
}
// Sort headers by name.
$headers = isort($headers, 0);
foreach ($headers as $header) {
list($key, $value) = $header;
$properties->addProperty($key, $value);
}
$encrypt_phids = $mail->getMustEncryptReasons();
if ($encrypt_phids) {
$properties->addProperty(
pht('Must Encrypt'),
$viewer->loadHandles($encrypt_phids)
->renderList());
}
return $properties;
}
private function buildDeliveryProperties(PhabricatorMetaMTAMail $mail) {
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setUser($viewer);
$actors = $mail->getDeliveredActors();
$reasons = null;
if (!$actors) {
if ($mail->getStatus() == PhabricatorMailOutboundStatus::STATUS_QUEUE) {
$delivery = $this->renderEmptyMessage(
pht(
'This message has not been delivered yet, so delivery information '.
'is not available.'));
} else {
$delivery = $this->renderEmptyMessage(
pht(
'This is an older message that predates recording delivery '.
'information, so none is available.'));
}
} else {
$actor = idx($actors, $viewer->getPHID());
if (!$actor) {
$delivery = phutil_tag(
'em',
array(),
pht('This message was not delivered to you.'));
} else {
$deliverable = $actor['deliverable'];
if ($deliverable) {
$delivery = pht('Delivered');
} else {
$delivery = pht('Voided');
}
$reasons = id(new PHUIStatusListView());
$reason_codes = $actor['reasons'];
if (!$reason_codes) {
$reason_codes = array(
PhabricatorMetaMTAActor::REASON_NONE,
);
}
$icon_yes = 'fa-check green';
$icon_no = 'fa-times red';
foreach ($reason_codes as $reason) {
$target = phutil_tag(
'strong',
array(),
PhabricatorMetaMTAActor::getReasonName($reason));
if (PhabricatorMetaMTAActor::isDeliveryReason($reason)) {
$icon = $icon_yes;
} else {
$icon = $icon_no;
}
$item = id(new PHUIStatusItemView())
->setIcon($icon)
->setTarget($target)
->setNote(PhabricatorMetaMTAActor::getReasonDescription($reason));
$reasons->addItem($item);
}
}
}
$properties->addProperty(pht('Delivery'), $delivery);
if ($reasons) {
$properties->addProperty(pht('Reasons'), $reasons);
$properties->addProperty(
null,
$this->renderEmptyMessage(
pht(
'Delivery reasons are listed from weakest to strongest.')));
}
$properties->addSectionHeader(
pht('Routing Rules'), 'fa-paper-plane-o');
$map = $mail->getDeliveredRoutingMap();
$routing_detail = null;
if ($map === null) {
if ($mail->getStatus() == PhabricatorMailOutboundStatus::STATUS_QUEUE) {
$routing_result = $this->renderEmptyMessage(
pht(
'This message has not been sent yet, so routing rules have '.
'not been computed.'));
} else {
$routing_result = $this->renderEmptyMessage(
pht(
'This is an older message which predates routing rules.'));
}
} else {
$rule = idx($map, $viewer->getPHID());
if ($rule === null) {
$rule = idx($map, 'default');
}
if ($rule === null) {
$routing_result = $this->renderEmptyMessage(
pht(
'No routing rules applied when delivering this message to you.'));
} else {
$rule_const = $rule['rule'];
$reason_phid = $rule['reason'];
switch ($rule_const) {
case PhabricatorMailRoutingRule::ROUTE_AS_NOTIFICATION:
$routing_result = pht(
'This message was routed as a notification because it '.
'matched %s.',
$viewer->renderHandle($reason_phid)->render());
break;
case PhabricatorMailRoutingRule::ROUTE_AS_MAIL:
$routing_result = pht(
'This message was routed as an email because it matched %s.',
$viewer->renderHandle($reason_phid)->render());
break;
default:
$routing_result = pht('Unknown routing rule "%s".', $rule_const);
break;
}
}
$routing_rules = $mail->getDeliveredRoutingRules();
if ($routing_rules) {
$rules = array();
foreach ($routing_rules as $rule) {
$phids = idx($rule, 'phids');
if ($phids === null) {
$rules[] = $rule;
} else if (in_array($viewer->getPHID(), $phids)) {
$rules[] = $rule;
}
}
// Reorder rules by strength.
foreach ($rules as $key => $rule) {
$const = $rule['routingRule'];
$phids = $rule['phids'];
if ($phids === null) {
$type = 'A';
} else {
$type = 'B';
}
$rules[$key]['strength'] = sprintf(
'~%s%08d',
$type,
PhabricatorMailRoutingRule::getRuleStrength($const));
}
$rules = isort($rules, 'strength');
$routing_detail = id(new PHUIStatusListView());
foreach ($rules as $rule) {
$const = $rule['routingRule'];
$phids = $rule['phids'];
$name = PhabricatorMailRoutingRule::getRuleName($const);
$icon = PhabricatorMailRoutingRule::getRuleIcon($const);
$color = PhabricatorMailRoutingRule::getRuleColor($const);
if ($phids === null) {
$kind = pht('Global');
} else {
$kind = pht('Personal');
}
$target = array($kind, ': ', $name);
$target = phutil_tag('strong', array(), $target);
$item = id(new PHUIStatusItemView())
->setTarget($target)
->setNote($viewer->renderHandle($rule['reasonPHID']))
->setIcon($icon, $color);
$routing_detail->addItem($item);
}
}
}
$properties->addProperty(pht('Effective Rule'), $routing_result);
if ($routing_detail !== null) {
$properties->addProperty(pht('All Matching Rules'), $routing_detail);
$properties->addProperty(
null,
$this->renderEmptyMessage(
pht(
'Matching rules are listed from weakest to strongest.')));
}
return $properties;
}
private function buildMetadataProperties(PhabricatorMetaMTAMail $mail) {
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setUser($viewer);
$properties->addProperty(pht('Message PHID'), $mail->getPHID());
$details = $mail->getMessage();
if (!strlen($details)) {
$details = phutil_tag('em', array(), pht('None'));
}
$properties->addProperty(pht('Status Details'), $details);
$actor_phid = $mail->getActorPHID();
if ($actor_phid) {
$actor_str = $viewer->renderHandle($actor_phid);
} else {
- $actor_str = pht('Generated by Phabricator');
+ $actor_str = pht('Generated by Server');
}
$properties->addProperty(pht('Actor'), $actor_str);
$related_phid = $mail->getRelatedPHID();
if ($related_phid) {
$related = $viewer->renderHandle($mail->getRelatedPHID());
} else {
$related = phutil_tag('em', array(), pht('None'));
}
$properties->addProperty(pht('Related Object'), $related);
return $properties;
}
private function renderEmptyMessage($message) {
return phutil_tag('em', array(), $message);
}
}
diff --git a/src/applications/metamta/engine/PhabricatorMailEmailEngine.php b/src/applications/metamta/engine/PhabricatorMailEmailEngine.php
index ef7b92a7d3..6c9cf1b356 100644
--- a/src/applications/metamta/engine/PhabricatorMailEmailEngine.php
+++ b/src/applications/metamta/engine/PhabricatorMailEmailEngine.php
@@ -1,648 +1,654 @@
<?php
final class PhabricatorMailEmailEngine
extends PhabricatorMailMessageEngine {
public function newMessage() {
$mailer = $this->getMailer();
$mail = $this->getMail();
$message = new PhabricatorMailEmailMessage();
$from_address = $this->newFromEmailAddress();
$message->setFromAddress($from_address);
$reply_address = $this->newReplyToEmailAddress();
if ($reply_address) {
$message->setReplyToAddress($reply_address);
}
$to_addresses = $this->newToEmailAddresses();
$cc_addresses = $this->newCCEmailAddresses();
if (!$to_addresses && !$cc_addresses) {
$mail->setMessage(
pht(
'Message has no valid recipients: all To/CC are disabled, '.
'invalid, or configured not to receive this mail.'));
return null;
}
// If this email describes a mail processing error, we rate limit outbound
// messages to each individual address. This prevents messes where
// something is stuck in a loop or dumps a ton of messages on us suddenly.
if ($mail->getIsErrorEmail()) {
$all_recipients = array();
foreach ($to_addresses as $to_address) {
$all_recipients[] = $to_address->getAddress();
}
foreach ($cc_addresses as $cc_address) {
$all_recipients[] = $cc_address->getAddress();
}
if ($this->shouldRateLimitMail($all_recipients)) {
$mail->setMessage(
pht(
'This is an error email, but one or more recipients have '.
'exceeded the error email rate limit. Declining to deliver '.
'message.'));
return null;
}
}
// Some mailers require a valid "To:" in order to deliver mail. If we
// don't have any "To:", try to fill it in with a placeholder "To:".
// If that also fails, move the "Cc:" line to "To:".
if (!$to_addresses) {
$void_address = $this->newVoidEmailAddress();
$to_addresses = array($void_address);
}
$to_addresses = $this->getUniqueEmailAddresses($to_addresses);
$cc_addresses = $this->getUniqueEmailAddresses(
$cc_addresses,
$to_addresses);
$message->setToAddresses($to_addresses);
$message->setCCAddresses($cc_addresses);
$attachments = $this->newEmailAttachments();
$message->setAttachments($attachments);
$subject = $this->newEmailSubject();
$message->setSubject($subject);
$headers = $this->newEmailHeaders();
foreach ($this->newEmailThreadingHeaders($mailer) as $threading_header) {
$headers[] = $threading_header;
}
$stamps = $mail->getMailStamps();
if ($stamps) {
$headers[] = $this->newEmailHeader(
'X-Phabricator-Stamps',
implode(' ', $stamps));
}
$must_encrypt = $mail->getMustEncrypt();
$raw_body = $mail->getBody();
$body = $raw_body;
if ($must_encrypt) {
$parts = array();
$encrypt_uri = $mail->getMustEncryptURI();
if (!strlen($encrypt_uri)) {
$encrypt_phid = $mail->getRelatedPHID();
if ($encrypt_phid) {
$encrypt_uri = urisprintf(
'/object/%s/',
$encrypt_phid);
}
}
if (strlen($encrypt_uri)) {
$parts[] = pht(
'This secure message is notifying you of a change to this object:');
$parts[] = PhabricatorEnv::getProductionURI($encrypt_uri);
}
$parts[] = pht(
'The content for this message can only be transmitted over a '.
'secure channel. To view the message content, follow this '.
'link:');
$parts[] = PhabricatorEnv::getProductionURI($mail->getURI());
$body = implode("\n\n", $parts);
} else {
$body = $raw_body;
}
$body_limit = PhabricatorEnv::getEnvConfig('metamta.email-body-limit');
+
+ $body = phutil_string_cast($body);
if (strlen($body) > $body_limit) {
$body = id(new PhutilUTF8StringTruncator())
->setMaximumBytes($body_limit)
->truncateString($body);
$body .= "\n";
$body .= pht('(This email was truncated at %d bytes.)', $body_limit);
}
$message->setTextBody($body);
$body_limit -= strlen($body);
// If we sent a different message body than we were asked to, record
// what we actually sent to make debugging and diagnostics easier.
if ($body !== $raw_body) {
$mail->setDeliveredBody($body);
}
if ($must_encrypt) {
$send_html = false;
} else {
$send_html = $this->shouldSendHTML();
}
if ($send_html) {
$html_body = $mail->getHTMLBody();
- if (strlen($html_body)) {
+ if (phutil_nonempty_string($html_body)) {
// NOTE: We just drop the entire HTML body if it won't fit. Safely
// truncating HTML is hard, and we already have the text body to fall
// back to.
if (strlen($html_body) <= $body_limit) {
$message->setHTMLBody($html_body);
$body_limit -= strlen($html_body);
}
}
}
// Pass the headers to the mailer, then save the state so we can show
// them in the web UI. If the mail must be encrypted, we remove headers
// which are not on a strict whitelist to avoid disclosing information.
$filtered_headers = $this->filterHeaders($headers, $must_encrypt);
$message->setHeaders($filtered_headers);
$mail->setUnfilteredHeaders($headers);
$mail->setDeliveredHeaders($headers);
if (PhabricatorEnv::getEnvConfig('phabricator.silent')) {
$mail->setMessage(
pht(
- 'Phabricator is running in silent mode. See `%s` '.
+ 'This software is running in silent mode. See `%s` '.
'in the configuration to change this setting.',
'phabricator.silent'));
return null;
}
return $message;
}
/* -( Message Components )------------------------------------------------- */
private function newFromEmailAddress() {
$from_address = $this->newDefaultEmailAddress();
$mail = $this->getMail();
// If the mail content must be encrypted, always disguise the sender.
$must_encrypt = $mail->getMustEncrypt();
if ($must_encrypt) {
return $from_address;
}
// If we have a raw "From" address, use that.
$raw_from = $mail->getRawFrom();
if ($raw_from) {
list($from_email, $from_name) = $raw_from;
return $this->newEmailAddress($from_email, $from_name);
}
// Otherwise, use as much of the information for any sending entity as
// we can.
$from_phid = $mail->getFrom();
$actor = $this->getActor($from_phid);
if ($actor) {
$actor_email = $actor->getEmailAddress();
$actor_name = $actor->getName();
} else {
$actor_email = null;
$actor_name = null;
}
$send_as_user = PhabricatorEnv::getEnvConfig('metamta.can-send-as-user');
if ($send_as_user) {
if ($actor_email !== null) {
$from_address->setAddress($actor_email);
}
}
if ($actor_name !== null) {
$from_address->setDisplayName($actor_name);
}
return $from_address;
}
private function newReplyToEmailAddress() {
$mail = $this->getMail();
$reply_raw = $mail->getReplyTo();
- if (!strlen($reply_raw)) {
+ if (!phutil_nonempty_string($reply_raw)) {
return null;
}
$reply_address = new PhutilEmailAddress($reply_raw);
// If we have a sending object, change the display name.
$from_phid = $mail->getFrom();
$actor = $this->getActor($from_phid);
if ($actor) {
$reply_address->setDisplayName($actor->getName());
}
// If we don't have a display name, fill in a default.
if (!strlen($reply_address->getDisplayName())) {
- $reply_address->setDisplayName(pht('Phabricator'));
+ $reply_address->setDisplayName(PlatformSymbols::getPlatformServerName());
}
return $reply_address;
}
private function newToEmailAddresses() {
$mail = $this->getMail();
$phids = $mail->getToPHIDs();
$addresses = $this->newEmailAddressesFromActorPHIDs($phids);
foreach ($mail->getRawToAddresses() as $raw_address) {
$addresses[] = new PhutilEmailAddress($raw_address);
}
return $addresses;
}
private function newCCEmailAddresses() {
$mail = $this->getMail();
$phids = $mail->getCcPHIDs();
return $this->newEmailAddressesFromActorPHIDs($phids);
}
private function newEmailAddressesFromActorPHIDs(array $phids) {
$mail = $this->getMail();
$phids = $mail->expandRecipients($phids);
$addresses = array();
foreach ($phids as $phid) {
$actor = $this->getActor($phid);
if (!$actor) {
continue;
}
if (!$actor->isDeliverable()) {
continue;
}
$addresses[] = new PhutilEmailAddress($actor->getEmailAddress());
}
return $addresses;
}
private function newEmailSubject() {
$mail = $this->getMail();
$is_threaded = (bool)$mail->getThreadID();
$must_encrypt = $mail->getMustEncrypt();
$subject = array();
if ($is_threaded) {
if ($this->shouldAddRePrefix()) {
$subject[] = 'Re:';
}
}
- $subject[] = trim($mail->getSubjectPrefix());
+ $subject_prefix = $mail->getSubjectPrefix();
+ $subject_prefix = phutil_string_cast($subject_prefix);
+ $subject_prefix = trim($subject_prefix);
+
+ $subject[] = $subject_prefix;
// If mail content must be encrypted, we replace the subject with
// a generic one.
if ($must_encrypt) {
$encrypt_subject = $mail->getMustEncryptSubject();
if (!strlen($encrypt_subject)) {
$encrypt_subject = pht('Object Updated');
}
$subject[] = $encrypt_subject;
} else {
$vary_prefix = $mail->getVarySubjectPrefix();
- if (strlen($vary_prefix)) {
+ if (phutil_nonempty_string($vary_prefix)) {
if ($this->shouldVarySubject()) {
$subject[] = $vary_prefix;
}
}
$subject[] = $mail->getSubject();
}
foreach ($subject as $key => $part) {
- if (!strlen($part)) {
+ if (!phutil_nonempty_string($part)) {
unset($subject[$key]);
}
}
$subject = implode(' ', $subject);
return $subject;
}
private function newEmailHeaders() {
$mail = $this->getMail();
$headers = array();
$headers[] = $this->newEmailHeader(
'X-Phabricator-Sent-This-Message',
'Yes');
$headers[] = $this->newEmailHeader(
'X-Mail-Transport-Agent',
'MetaMTA');
// Some clients respect this to suppress OOF and other auto-responses.
$headers[] = $this->newEmailHeader(
'X-Auto-Response-Suppress',
'All');
$mailtags = $mail->getMailTags();
if ($mailtags) {
$tag_header = array();
foreach ($mailtags as $mailtag) {
$tag_header[] = '<'.$mailtag.'>';
}
$tag_header = implode(', ', $tag_header);
$headers[] = $this->newEmailHeader(
'X-Phabricator-Mail-Tags',
$tag_header);
}
$value = $mail->getHeaders();
foreach ($value as $pair) {
list($header_key, $header_value) = $pair;
// NOTE: If we have \n in a header, SES rejects the email.
$header_value = str_replace("\n", ' ', $header_value);
$headers[] = $this->newEmailHeader($header_key, $header_value);
}
$is_bulk = $mail->getIsBulk();
if ($is_bulk) {
$headers[] = $this->newEmailHeader('Precedence', 'bulk');
}
if ($mail->getMustEncrypt()) {
$headers[] = $this->newEmailHeader('X-Phabricator-Must-Encrypt', 'Yes');
}
$related_phid = $mail->getRelatedPHID();
if ($related_phid) {
$headers[] = $this->newEmailHeader('Thread-Topic', $related_phid);
}
$headers[] = $this->newEmailHeader(
'X-Phabricator-Mail-ID',
$mail->getID());
$unique = Filesystem::readRandomCharacters(16);
$headers[] = $this->newEmailHeader(
'X-Phabricator-Send-Attempt',
$unique);
return $headers;
}
private function newEmailThreadingHeaders() {
$mailer = $this->getMailer();
$mail = $this->getMail();
$headers = array();
$thread_id = $mail->getThreadID();
- if (!strlen($thread_id)) {
+ if (!phutil_nonempty_string($thread_id)) {
return $headers;
}
$is_first = $mail->getIsFirstMessage();
// NOTE: Gmail freaks out about In-Reply-To and References which aren't in
// the form "<string@domain.tld>"; this is also required by RFC 2822,
// although some clients are more liberal in what they accept.
$domain = $this->newMailDomain();
$thread_id = '<'.$thread_id.'@'.$domain.'>';
if ($is_first && $mailer->supportsMessageIDHeader()) {
$headers[] = $this->newEmailHeader('Message-ID', $thread_id);
} else {
$in_reply_to = $thread_id;
$references = array($thread_id);
$parent_id = $mail->getParentMessageID();
if ($parent_id) {
$in_reply_to = $parent_id;
// By RFC 2822, the most immediate parent should appear last
// in the "References" header, so this order is intentional.
$references[] = $parent_id;
}
$references = implode(' ', $references);
$headers[] = $this->newEmailHeader('In-Reply-To', $in_reply_to);
$headers[] = $this->newEmailHeader('References', $references);
}
$thread_index = $this->generateThreadIndex($thread_id, $is_first);
$headers[] = $this->newEmailHeader('Thread-Index', $thread_index);
return $headers;
}
private function newEmailAttachments() {
$mail = $this->getMail();
// If the mail content must be encrypted, don't add attachments.
$must_encrypt = $mail->getMustEncrypt();
if ($must_encrypt) {
return array();
}
return $mail->getAttachments();
}
/* -( Preferences )-------------------------------------------------------- */
private function shouldAddRePrefix() {
$preferences = $this->getPreferences();
$value = $preferences->getSettingValue(
PhabricatorEmailRePrefixSetting::SETTINGKEY);
return ($value == PhabricatorEmailRePrefixSetting::VALUE_RE_PREFIX);
}
private function shouldVarySubject() {
$preferences = $this->getPreferences();
$value = $preferences->getSettingValue(
PhabricatorEmailVarySubjectsSetting::SETTINGKEY);
return ($value == PhabricatorEmailVarySubjectsSetting::VALUE_VARY_SUBJECTS);
}
private function shouldSendHTML() {
$preferences = $this->getPreferences();
$value = $preferences->getSettingValue(
PhabricatorEmailFormatSetting::SETTINGKEY);
return ($value == PhabricatorEmailFormatSetting::VALUE_HTML_EMAIL);
}
/* -( Utilities )---------------------------------------------------------- */
private function newEmailHeader($name, $value) {
return id(new PhabricatorMailHeader())
->setName($name)
->setValue($value);
}
private function newEmailAddress($address, $name = null) {
$object = id(new PhutilEmailAddress())
->setAddress($address);
if (strlen($name)) {
$object->setDisplayName($name);
}
return $object;
}
public function newDefaultEmailAddress() {
$raw_address = PhabricatorEnv::getEnvConfig('metamta.default-address');
if (!strlen($raw_address)) {
$domain = $this->newMailDomain();
$raw_address = "noreply@{$domain}";
}
$address = new PhutilEmailAddress($raw_address);
- if (!strlen($address->getDisplayName())) {
- $address->setDisplayName(pht('Phabricator'));
+ if (!phutil_nonempty_string($address->getDisplayName())) {
+ $address->setDisplayName(PlatformSymbols::getPlatformServerName());
}
return $address;
}
public function newVoidEmailAddress() {
return $this->newDefaultEmailAddress();
}
private function newMailDomain() {
$domain = PhabricatorEnv::getEnvConfig('metamta.reply-handler-domain');
if (strlen($domain)) {
return $domain;
}
$install_uri = PhabricatorEnv::getURI('/');
$install_uri = new PhutilURI($install_uri);
return $install_uri->getDomain();
}
private function filterHeaders(array $headers, $must_encrypt) {
assert_instances_of($headers, 'PhabricatorMailHeader');
if (!$must_encrypt) {
return $headers;
}
$whitelist = array(
'In-Reply-To',
'Message-ID',
'Precedence',
'References',
'Thread-Index',
'Thread-Topic',
'X-Mail-Transport-Agent',
'X-Auto-Response-Suppress',
'X-Phabricator-Sent-This-Message',
'X-Phabricator-Must-Encrypt',
'X-Phabricator-Mail-ID',
'X-Phabricator-Send-Attempt',
);
// NOTE: The major header we want to drop is "X-Phabricator-Mail-Tags".
// This header contains a significant amount of meaningful information
// about the object.
$whitelist_map = array();
foreach ($whitelist as $term) {
$whitelist_map[phutil_utf8_strtolower($term)] = true;
}
foreach ($headers as $key => $header) {
$name = $header->getName();
$name = phutil_utf8_strtolower($name);
if (!isset($whitelist_map[$name])) {
unset($headers[$key]);
}
}
return $headers;
}
private function getUniqueEmailAddresses(
array $addresses,
array $exclude = array()) {
assert_instances_of($addresses, 'PhutilEmailAddress');
assert_instances_of($exclude, 'PhutilEmailAddress');
$seen = array();
foreach ($exclude as $address) {
$seen[$address->getAddress()] = true;
}
foreach ($addresses as $key => $address) {
$raw_address = $address->getAddress();
if (isset($seen[$raw_address])) {
unset($addresses[$key]);
continue;
}
$seen[$raw_address] = true;
}
return array_values($addresses);
}
private function generateThreadIndex($seed, $is_first_mail) {
// When threading, Outlook ignores the 'References' and 'In-Reply-To'
// headers that most clients use. Instead, it uses a custom 'Thread-Index'
// header. The format of this header is something like this (from
// camel-exchange-folder.c in Evolution Exchange):
/* A new post to a folder gets a 27-byte-long thread index. (The value
* is apparently unique but meaningless.) Each reply to a post gets a
* 32-byte-long thread index whose first 27 bytes are the same as the
* parent's thread index. Each reply to any of those gets a
* 37-byte-long thread index, etc. The Thread-Index header contains a
* base64 representation of this value.
*/
// The specific implementation uses a 27-byte header for the first email
// a recipient receives, and a random 5-byte suffix (32 bytes total)
// thereafter. This means that all the replies are (incorrectly) siblings,
// but it would be very difficult to keep track of the entire tree and this
// gets us reasonable client behavior.
$base = substr(md5($seed), 0, 27);
if (!$is_first_mail) {
// Not totally sure, but it seems like outlook orders replies by
// thread-index rather than timestamp, so to get these to show up in the
// right order we use the time as the last 4 bytes.
$base .= ' '.pack('N', time());
}
return base64_encode($base);
}
private function shouldRateLimitMail(array $all_recipients) {
try {
PhabricatorSystemActionEngine::willTakeAction(
$all_recipients,
new PhabricatorMetaMTAErrorMailAction(),
1);
return false;
} catch (PhabricatorSystemActionRateLimitException $ex) {
return true;
}
}
}
diff --git a/src/applications/metamta/management/PhabricatorMailManagementListInboundWorkflow.php b/src/applications/metamta/management/PhabricatorMailManagementListInboundWorkflow.php
index 02ed82a6b0..c19923026d 100644
--- a/src/applications/metamta/management/PhabricatorMailManagementListInboundWorkflow.php
+++ b/src/applications/metamta/management/PhabricatorMailManagementListInboundWorkflow.php
@@ -1,71 +1,71 @@
<?php
final class PhabricatorMailManagementListInboundWorkflow
extends PhabricatorMailManagementWorkflow {
protected function didConstruct() {
$this
->setName('list-inbound')
- ->setSynopsis(pht('List inbound messages received by Phabricator.'))
+ ->setSynopsis(pht('List inbound messages.'))
->setExamples(
'**list-inbound**')
->setArguments(
array(
array(
'name' => 'limit',
'param' => 'N',
'default' => 100,
'help' => pht(
'Show a specific number of messages (default 100).'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$viewer = $this->getViewer();
$mails = id(new PhabricatorMetaMTAReceivedMail())->loadAllWhere(
'1 = 1 ORDER BY id DESC LIMIT %d',
$args->getArg('limit'));
if (!$mails) {
$console->writeErr("%s\n", pht('No received mail.'));
return 0;
}
$phids = array_merge(
mpull($mails, 'getRelatedPHID'),
mpull($mails, 'getAuthorPHID'));
$handles = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs($phids)
->execute();
$table = id(new PhutilConsoleTable())
->setShowHeader(false)
->addColumn('id', array('title' => pht('ID')))
->addColumn('author', array('title' => pht('Author')))
->addColumn('phid', array('title' => pht('Related PHID')))
->addColumn('subject', array('title' => pht('Subject')));
foreach (array_reverse($mails) as $mail) {
$table->addRow(array(
'id' => $mail->getID(),
'author' => $mail->getAuthorPHID()
? $handles[$mail->getAuthorPHID()]->getName()
: '-',
'phid' => $mail->getRelatedPHID()
? $handles[$mail->getRelatedPHID()]->getName()
: '-',
'subject' => $mail->getSubject()
? $mail->getSubject()
: pht('(No subject.)'),
));
}
$table->draw();
return 0;
}
}
diff --git a/src/applications/metamta/management/PhabricatorMailManagementListOutboundWorkflow.php b/src/applications/metamta/management/PhabricatorMailManagementListOutboundWorkflow.php
index 30939dd436..77c363a45b 100644
--- a/src/applications/metamta/management/PhabricatorMailManagementListOutboundWorkflow.php
+++ b/src/applications/metamta/management/PhabricatorMailManagementListOutboundWorkflow.php
@@ -1,60 +1,60 @@
<?php
final class PhabricatorMailManagementListOutboundWorkflow
extends PhabricatorMailManagementWorkflow {
protected function didConstruct() {
$this
->setName('list-outbound')
- ->setSynopsis(pht('List outbound messages sent by Phabricator.'))
+ ->setSynopsis(pht('List outbound messages.'))
->setExamples('**list-outbound**')
->setArguments(
array(
array(
'name' => 'limit',
'param' => 'N',
'default' => 100,
'help' => pht(
'Show a specific number of messages (default 100).'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$viewer = $this->getViewer();
$mails = id(new PhabricatorMetaMTAMail())->loadAllWhere(
'1 = 1 ORDER BY id DESC LIMIT %d',
$args->getArg('limit'));
if (!$mails) {
$console->writeErr("%s\n", pht('No sent mail.'));
return 0;
}
$table = id(new PhutilConsoleTable())
->setShowHeader(false)
->addColumn('id', array('title' => pht('ID')))
->addColumn('encrypt', array('title' => pht('#')))
->addColumn('status', array('title' => pht('Status')))
->addColumn('type', array('title' => pht('Type')))
->addColumn('subject', array('title' => pht('Subject')));
foreach (array_reverse($mails) as $mail) {
$status = $mail->getStatus();
$table->addRow(array(
'id' => $mail->getID(),
'encrypt' => ($mail->getMustEncrypt() ? '#' : ' '),
'status' => PhabricatorMailOutboundStatus::getStatusName($status),
'type' => $mail->getMessageType(),
'subject' => $mail->getSubject(),
));
}
$table->draw();
return 0;
}
}
diff --git a/src/applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php b/src/applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php
index 9b266a3ae7..f20e626573 100644
--- a/src/applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php
+++ b/src/applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php
@@ -1,167 +1,168 @@
<?php
final class PhabricatorMetaMTAEmailBodyParser extends Phobject {
/**
* Mails can have bodies such as
*
* !claim
*
* taking this task
*
* Or
*
* !assign alincoln
*
* please, take this task I took; its hard
*
* This function parses such an email body and returns a dictionary
* containing a clean body text (e.g. "taking this task"), and a list of
* commands. For example, this body above might parse as:
*
* array(
* 'body' => 'please, take this task I took; it's hard',
* 'commands' => array(
* array('assign', 'alincoln'),
* ),
* )
*
* @param string Raw mail text body.
* @return dict Parsed body.
*/
public function parseBody($body) {
$body = $this->stripTextBody($body);
$commands = array();
$lines = phutil_split_lines($body, $retain_endings = true);
// We'll match commands at the beginning and end of the mail, but not
// in the middle of the mail body.
list($top_commands, $lines) = $this->stripCommands($lines);
list($end_commands, $lines) = $this->stripCommands(array_reverse($lines));
$lines = array_reverse($lines);
$commands = array_merge($top_commands, array_reverse($end_commands));
$lines = rtrim(implode('', $lines));
return array(
'body' => $lines,
'commands' => $commands,
);
}
private function stripCommands(array $lines) {
$saw_command = false;
$commands = array();
foreach ($lines as $key => $line) {
if (!strlen(trim($line)) && $saw_command) {
unset($lines[$key]);
continue;
}
$matches = null;
if (!preg_match('/^\s*!(\w+.*$)/', $line, $matches)) {
break;
}
$arg_str = $matches[1];
$argv = preg_split('/[,\s]+/', trim($arg_str));
$commands[] = $argv;
unset($lines[$key]);
$saw_command = true;
}
return array($commands, $lines);
}
public function stripTextBody($body) {
return trim($this->stripSignature($this->stripQuotedText($body)));
}
private function stripQuotedText($body) {
+ $body = phutil_string_cast($body);
// Look for "On <date>, <user> wrote:". This may be split across multiple
// lines. We need to be careful not to remove all of a message like this:
//
// On which day do you want to meet?
//
// On <date>, <user> wrote:
// > Let's set up a meeting.
$start = null;
$lines = phutil_split_lines($body);
foreach ($lines as $key => $line) {
if (preg_match('/^\s*>?\s*On\b/', $line)) {
$start = $key;
}
if ($start !== null) {
if (preg_match('/\bwrote:/', $line)) {
$lines = array_slice($lines, 0, $start);
$body = implode('', $lines);
break;
}
}
}
// Outlook english
$body = preg_replace(
'/^\s*(> )?-----Original Message-----.*?/imsU',
'',
$body);
// Outlook danish
$body = preg_replace(
'/^\s*(> )?-----Oprindelig Meddelelse-----.*?/imsU',
'',
$body);
// See example in T3217.
$body = preg_replace(
'/^________________________________________\s+From:.*?/imsU',
'',
$body);
// French GMail quoted text. See T8199.
$body = preg_replace(
'/^\s*\d{4}-\d{2}-\d{2} \d+:\d+ GMT.*:.*?/imsU',
'',
$body);
return rtrim($body);
}
private function stripSignature($body) {
// Quasi-"standard" delimiter, for lols see:
// https://bugzilla.mozilla.org/show_bug.cgi?id=58406
$body = preg_replace(
'/^-- +$.*/sm',
'',
$body);
// Mailbox seems to make an attempt to comply with the "standard" but
// omits the leading newline and uses an em dash. This may or may not have
// the trailing space, but it's unique enough that there's no real ambiguity
// in detecting it.
$body = preg_replace(
"/\s*\xE2\x80\x94\s*\nSent from Mailbox\s*\z/su",
'',
$body);
// HTC Mail application (mobile)
$body = preg_replace(
'/^\s*^Sent from my HTC smartphone.*/sm',
'',
$body);
// Apple iPhone
$body = preg_replace(
'/^\s*^Sent from my iPhone\s*$.*/sm',
'',
$body);
return rtrim($body);
}
}
diff --git a/src/applications/metamta/query/PhabricatorMetaMTAActor.php b/src/applications/metamta/query/PhabricatorMetaMTAActor.php
index cf2060a8f7..676f0933ec 100644
--- a/src/applications/metamta/query/PhabricatorMetaMTAActor.php
+++ b/src/applications/metamta/query/PhabricatorMetaMTAActor.php
@@ -1,185 +1,185 @@
<?php
final class PhabricatorMetaMTAActor extends Phobject {
const STATUS_DELIVERABLE = 'deliverable';
const STATUS_UNDELIVERABLE = 'undeliverable';
const REASON_NONE = 'none';
const REASON_UNLOADABLE = 'unloadable';
const REASON_UNMAILABLE = 'unmailable';
const REASON_NO_ADDRESS = 'noaddress';
const REASON_DISABLED = 'disabled';
const REASON_MAIL_DISABLED = 'maildisabled';
const REASON_EXTERNAL_TYPE = 'exernaltype';
const REASON_RESPONSE = 'response';
const REASON_SELF = 'self';
const REASON_MAILTAGS = 'mailtags';
const REASON_BOT = 'bot';
const REASON_FORCE = 'force';
const REASON_FORCE_HERALD = 'force-herald';
const REASON_ROUTE_AS_NOTIFICATION = 'route-as-notification';
const REASON_ROUTE_AS_MAIL = 'route-as-mail';
const REASON_UNVERIFIED = 'unverified';
const REASON_MUTED = 'muted';
private $phid;
private $emailAddress;
private $name;
private $status = self::STATUS_DELIVERABLE;
private $reasons = array();
private $isVerified = false;
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setEmailAddress($email_address) {
$this->emailAddress = $email_address;
return $this;
}
public function getEmailAddress() {
return $this->emailAddress;
}
public function setIsVerified($is_verified) {
$this->isVerified = $is_verified;
return $this;
}
public function getIsVerified() {
return $this->isVerified;
}
public function setPHID($phid) {
$this->phid = $phid;
return $this;
}
public function getPHID() {
return $this->phid;
}
public function setUndeliverable($reason) {
$this->reasons[] = $reason;
$this->status = self::STATUS_UNDELIVERABLE;
return $this;
}
public function setDeliverable($reason) {
$this->reasons[] = $reason;
$this->status = self::STATUS_DELIVERABLE;
return $this;
}
public function isDeliverable() {
return ($this->status === self::STATUS_DELIVERABLE);
}
public function getDeliverabilityReasons() {
return $this->reasons;
}
public static function isDeliveryReason($reason) {
switch ($reason) {
case self::REASON_NONE:
case self::REASON_FORCE:
case self::REASON_FORCE_HERALD:
case self::REASON_ROUTE_AS_MAIL:
return true;
default:
// All other reasons cause the message to not be delivered.
return false;
}
}
public static function getReasonName($reason) {
$names = array(
self::REASON_NONE => pht('None'),
self::REASON_DISABLED => pht('Disabled Recipient'),
self::REASON_BOT => pht('Bot Recipient'),
self::REASON_NO_ADDRESS => pht('No Address'),
self::REASON_EXTERNAL_TYPE => pht('External Recipient'),
self::REASON_UNMAILABLE => pht('Not Mailable'),
self::REASON_RESPONSE => pht('Similar Reply'),
self::REASON_SELF => pht('Self Mail'),
self::REASON_MAIL_DISABLED => pht('Mail Disabled'),
self::REASON_MAILTAGS => pht('Mail Tags'),
self::REASON_UNLOADABLE => pht('Bad Recipient'),
self::REASON_FORCE => pht('Forced Mail'),
self::REASON_FORCE_HERALD => pht('Forced by Herald'),
self::REASON_ROUTE_AS_NOTIFICATION => pht('Route as Notification'),
self::REASON_ROUTE_AS_MAIL => pht('Route as Mail'),
self::REASON_UNVERIFIED => pht('Address Not Verified'),
self::REASON_MUTED => pht('Muted'),
);
return idx($names, $reason, pht('Unknown ("%s")', $reason));
}
public static function getReasonDescription($reason) {
$descriptions = array(
self::REASON_NONE => pht(
'No special rules affected this mail.'),
self::REASON_DISABLED => pht(
'This user is disabled; disabled users do not receive mail.'),
self::REASON_BOT => pht(
'This user is a bot; bot accounts do not receive mail.'),
self::REASON_NO_ADDRESS => pht(
'Unable to load an email address for this PHID.'),
self::REASON_EXTERNAL_TYPE => pht(
'Only external accounts of type "email" are deliverable; this '.
'account has a different type.'),
self::REASON_UNMAILABLE => pht(
'This PHID type does not correspond to a mailable object.'),
self::REASON_RESPONSE => pht(
'This message is a response to another email message, and this '.
'recipient received the original email message, so we are not '.
'sending them this substantially similar message (for example, '.
'the sender used "Reply All" instead of "Reply" in response to '.
- 'mail from Phabricator).'),
+ 'mail from this server).'),
self::REASON_SELF => pht(
'This recipient is the user whose actions caused delivery of '.
'this message, but they have set preferences so they do not '.
'receive mail about their own actions (Settings > Email '.
'Preferences > Self Actions).'),
self::REASON_MAIL_DISABLED => pht(
'This recipient has disabled all email notifications '.
'(Settings > Email Preferences > Email Notifications).'),
self::REASON_MAILTAGS => pht(
'This mail has tags which control which users receive it, and '.
'this recipient has not elected to receive mail with any of '.
'the tags on this message (Settings > Email Preferences).'),
self::REASON_UNLOADABLE => pht(
'Unable to load user record for this PHID.'),
self::REASON_FORCE => pht(
'Delivery of this mail is forced and ignores deliver preferences. '.
'Mail which uses forced delivery is usually related to account '.
'management or authentication. For example, password reset email '.
'ignores mail preferences.'),
self::REASON_FORCE_HERALD => pht(
'This recipient was added by a "Send me an Email" rule in Herald, '.
'which overrides some delivery settings.'),
self::REASON_ROUTE_AS_NOTIFICATION => pht(
'This message was downgraded to a notification by outbound mail '.
'rules in Herald.'),
self::REASON_ROUTE_AS_MAIL => pht(
'This message was upgraded to email by outbound mail rules '.
'in Herald.'),
self::REASON_UNVERIFIED => pht(
'This recipient does not have a verified primary email address.'),
self::REASON_MUTED => pht(
'This recipient has muted notifications for this object.'),
);
return idx($descriptions, $reason, pht('Unknown Reason ("%s")', $reason));
}
}
diff --git a/src/applications/metamta/query/PhabricatorMetaMTAMailPropertiesQuery.php b/src/applications/metamta/query/PhabricatorMetaMTAMailPropertiesQuery.php
index e6ec09f4ff..b7dd3e5ee4 100644
--- a/src/applications/metamta/query/PhabricatorMetaMTAMailPropertiesQuery.php
+++ b/src/applications/metamta/query/PhabricatorMetaMTAMailPropertiesQuery.php
@@ -1,51 +1,47 @@
<?php
final class PhabricatorMetaMTAMailPropertiesQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $objectPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withObjectPHIDs(array $object_phids) {
$this->objectPHIDs = $object_phids;
return $this;
}
public function newResultObject() {
return new PhabricatorMetaMTAMailProperties();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->objectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'objectPHID IN (%Ls)',
$this->objectPHIDs);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorMetaMTAApplication';
}
}
diff --git a/src/applications/metamta/query/PhabricatorMetaMTAMailQuery.php b/src/applications/metamta/query/PhabricatorMetaMTAMailQuery.php
index 903b385ceb..d1f0235c90 100644
--- a/src/applications/metamta/query/PhabricatorMetaMTAMailQuery.php
+++ b/src/applications/metamta/query/PhabricatorMetaMTAMailQuery.php
@@ -1,132 +1,128 @@
<?php
final class PhabricatorMetaMTAMailQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $actorPHIDs;
private $recipientPHIDs;
private $createdMin;
private $createdMax;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withActorPHIDs(array $phids) {
$this->actorPHIDs = $phids;
return $this;
}
public function withRecipientPHIDs(array $phids) {
$this->recipientPHIDs = $phids;
return $this;
}
public function withDateCreatedBetween($min, $max) {
$this->createdMin = $min;
$this->createdMax = $max;
return $this;
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'mail.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'mail.phid IN (%Ls)',
$this->phids);
}
if ($this->actorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'mail.actorPHID IN (%Ls)',
$this->actorPHIDs);
}
if ($this->createdMin !== null) {
$where[] = qsprintf(
$conn,
'mail.dateCreated >= %d',
$this->createdMin);
}
if ($this->createdMax !== null) {
$where[] = qsprintf(
$conn,
'mail.dateCreated <= %d',
$this->createdMax);
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->shouldJoinRecipients()) {
$joins[] = qsprintf(
$conn,
'JOIN %T recipient
ON mail.phid = recipient.src
AND recipient.type = %d
AND recipient.dst IN (%Ls)',
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST,
$this->recipientPHIDs);
}
return $joins;
}
private function shouldJoinRecipients() {
if ($this->recipientPHIDs === null) {
return false;
}
return true;
}
protected function getPrimaryTableAlias() {
return 'mail';
}
public function newResultObject() {
return new PhabricatorMetaMTAMail();
}
public function getQueryApplicationClass() {
return 'PhabricatorMetaMTAApplication';
}
protected function shouldGroupQueryResultRows() {
if ($this->shouldJoinRecipients()) {
if (count($this->recipientPHIDs) > 1) {
return true;
}
}
return parent::shouldGroupQueryResultRows();
}
}
diff --git a/src/applications/metamta/receiver/PhabricatorObjectMailReceiver.php b/src/applications/metamta/receiver/PhabricatorObjectMailReceiver.php
index 16950c1577..0342a94a60 100644
--- a/src/applications/metamta/receiver/PhabricatorObjectMailReceiver.php
+++ b/src/applications/metamta/receiver/PhabricatorObjectMailReceiver.php
@@ -1,190 +1,190 @@
<?php
abstract class PhabricatorObjectMailReceiver extends PhabricatorMailReceiver {
/**
* Return a regular expression fragment which matches the name of an
* object which can receive mail. For example, Differential uses:
*
* D[1-9]\d*
*
* ...to match `D123`, etc., identifying Differential Revisions.
*
* @return string Regular expression fragment.
*/
abstract protected function getObjectPattern();
/**
* Load the object receiving mail, based on an identifying pattern. Normally
* this pattern is some sort of object ID.
*
* @param string A string matched by @{method:getObjectPattern}
* fragment.
* @param PhabricatorUser The viewing user.
* @return void
*/
abstract protected function loadObject($pattern, PhabricatorUser $viewer);
final protected function processReceivedMail(
PhabricatorMetaMTAReceivedMail $mail,
PhutilEmailAddress $target) {
$parts = $this->matchObjectAddress($target);
if (!$parts) {
// We should only make it here if we matched already in "canAcceptMail()",
// so this is a surprise.
throw new Exception(
pht(
'Failed to parse object address ("%s") during processing.',
(string)$target));
}
$pattern = $parts['pattern'];
$sender = $this->getSender();
try {
$object = $this->loadObject($pattern, $sender);
} catch (PhabricatorPolicyException $policy_exception) {
throw new PhabricatorMetaMTAReceivedMailProcessingException(
MetaMTAReceivedMailStatus::STATUS_POLICY_PROBLEM,
pht(
'This mail is addressed to an object ("%s") you do not have '.
'permission to see: %s',
$pattern,
$policy_exception->getMessage()));
}
if (!$object) {
throw new PhabricatorMetaMTAReceivedMailProcessingException(
MetaMTAReceivedMailStatus::STATUS_NO_SUCH_OBJECT,
pht(
'This mail is addressed to an object ("%s"), but that object '.
'does not exist.',
$pattern));
}
$sender_identifier = $parts['sender'];
if ($sender_identifier === 'public') {
if (!PhabricatorEnv::getEnvConfig('metamta.public-replies')) {
throw new PhabricatorMetaMTAReceivedMailProcessingException(
MetaMTAReceivedMailStatus::STATUS_NO_PUBLIC_MAIL,
pht(
'This mail is addressed to the public email address of an object '.
- '("%s"), but public replies are not enabled on this Phabricator '.
- 'install. An administrator may have recently disabled this '.
- 'setting, or you may have replied to an old message. Try '.
- 'replying to a more recent message instead.',
+ '("%s"), but public replies are not enabled on this server. An '.
+ 'administrator may have recently disabled this setting, or you '.
+ 'may have replied to an old message. Try replying to a more '.
+ 'recent message instead.',
$pattern));
}
$check_phid = $object->getPHID();
} else {
if ($sender_identifier != $sender->getID()) {
throw new PhabricatorMetaMTAReceivedMailProcessingException(
MetaMTAReceivedMailStatus::STATUS_USER_MISMATCH,
pht(
'This mail is addressed to the private email address of an object '.
'("%s"), but you are not the user who is authorized to use the '.
'address you sent mail to. Each private address is unique to the '.
'user who received the original mail. Try replying to a message '.
'which was sent directly to you instead.',
$pattern));
}
$check_phid = $sender->getPHID();
}
$mail_key = PhabricatorMetaMTAMailProperties::loadMailKey($object);
$expect_hash = self::computeMailHash($mail_key, $check_phid);
if (!phutil_hashes_are_identical($expect_hash, $parts['hash'])) {
throw new PhabricatorMetaMTAReceivedMailProcessingException(
MetaMTAReceivedMailStatus::STATUS_HASH_MISMATCH,
pht(
'This mail is addressed to an object ("%s"), but the address is '.
'not correct (the security hash is wrong). Check that the address '.
'is correct.',
$pattern));
}
$mail->setRelatedPHID($object->getPHID());
$this->processReceivedObjectMail($mail, $object, $sender);
return $this;
}
protected function processReceivedObjectMail(
PhabricatorMetaMTAReceivedMail $mail,
PhabricatorLiskDAO $object,
PhabricatorUser $sender) {
$handler = $this->getTransactionReplyHandler();
if ($handler) {
return $handler
->setMailReceiver($object)
->setActor($sender)
->setExcludeMailRecipientPHIDs($mail->loadAllRecipientPHIDs())
->processEmail($mail);
}
throw new PhutilMethodNotImplementedException();
}
protected function getTransactionReplyHandler() {
return null;
}
public function loadMailReceiverObject($pattern, PhabricatorUser $viewer) {
return $this->loadObject($pattern, $viewer);
}
final public function canAcceptMail(
PhabricatorMetaMTAReceivedMail $mail,
PhutilEmailAddress $target) {
// If we don't have a valid sender user account, we can never accept
// mail to any object.
$sender = $this->getSender();
if (!$sender) {
return false;
}
return (bool)$this->matchObjectAddress($target);
}
private function matchObjectAddress(PhutilEmailAddress $address) {
$address = PhabricatorMailUtil::normalizeAddress($address);
$local = $address->getLocalPart();
$regexp = $this->getAddressRegexp();
$matches = null;
if (!preg_match($regexp, $local, $matches)) {
return false;
}
return $matches;
}
private function getAddressRegexp() {
$pattern = $this->getObjectPattern();
$regexp =
'(^'.
'(?P<pattern>'.$pattern.')'.
'\\+'.
'(?P<sender>\w+)'.
'\\+'.
'(?P<hash>[a-f0-9]{16})'.
'$)Ui';
return $regexp;
}
public static function computeMailHash($mail_key, $phid) {
$hash = PhabricatorHash::digestWithNamedKey(
$mail_key.$phid,
'mail.object-address-key');
return substr($hash, 0, 16);
}
}
diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php b/src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php
index c620bea30a..d3289bbc69 100644
--- a/src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php
+++ b/src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php
@@ -1,592 +1,593 @@
<?php
final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
protected $headers = array();
protected $bodies = array();
protected $attachments = array();
protected $status = '';
protected $relatedPHID;
protected $authorPHID;
protected $message;
protected $messageIDHash = '';
protected function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'headers' => self::SERIALIZATION_JSON,
'bodies' => self::SERIALIZATION_JSON,
'attachments' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'relatedPHID' => 'phid?',
'authorPHID' => 'phid?',
'message' => 'text?',
'messageIDHash' => 'bytes12',
'status' => 'text32',
),
self::CONFIG_KEY_SCHEMA => array(
'relatedPHID' => array(
'columns' => array('relatedPHID'),
),
'authorPHID' => array(
'columns' => array('authorPHID'),
),
'key_messageIDHash' => array(
'columns' => array('messageIDHash'),
),
'key_created' => array(
'columns' => array('dateCreated'),
),
),
) + parent::getConfiguration();
}
public function setHeaders(array $headers) {
// Normalize headers to lowercase.
$normalized = array();
foreach ($headers as $name => $value) {
$name = $this->normalizeMailHeaderName($name);
if ($name == 'message-id') {
$this->setMessageIDHash(PhabricatorHash::digestForIndex($value));
}
$normalized[$name] = $value;
}
$this->headers = $normalized;
return $this;
}
public function getHeader($key, $default = null) {
$key = $this->normalizeMailHeaderName($key);
return idx($this->headers, $key, $default);
}
private function normalizeMailHeaderName($name) {
return strtolower($name);
}
public function getMessageID() {
return $this->getHeader('Message-ID');
}
public function getSubject() {
return $this->getHeader('Subject');
}
public function getCCAddresses() {
return $this->getRawEmailAddresses(idx($this->headers, 'cc'));
}
public function getToAddresses() {
return $this->getRawEmailAddresses(idx($this->headers, 'to'));
}
public function newTargetAddresses() {
$raw_addresses = array();
foreach ($this->getToAddresses() as $raw_address) {
$raw_addresses[] = $raw_address;
}
foreach ($this->getCCAddresses() as $raw_address) {
$raw_addresses[] = $raw_address;
}
$raw_addresses = array_unique($raw_addresses);
$addresses = array();
foreach ($raw_addresses as $raw_address) {
$addresses[] = new PhutilEmailAddress($raw_address);
}
return $addresses;
}
public function loadAllRecipientPHIDs() {
$addresses = $this->newTargetAddresses();
// See T13317. Don't allow reserved addresses (like "noreply@...") to
// match user PHIDs.
foreach ($addresses as $key => $address) {
if (PhabricatorMailUtil::isReservedAddress($address)) {
unset($addresses[$key]);
}
}
if (!$addresses) {
return array();
}
$address_strings = array();
foreach ($addresses as $address) {
$address_strings[] = phutil_string_cast($address->getAddress());
}
// See T13317. If a verified email address is in the "To" or "Cc" line,
// we'll count the user who owns that address as a recipient.
// We require the address be verified because we'll trigger behavior (like
// adding subscribers) based on the recipient list, and don't want to add
// Alice as a subscriber if she adds an unverified "internal-bounces@"
// address to her account and this address gets caught in the crossfire.
// In the best case this is confusing; in the worst case it could
// some day give her access to objects she can't see.
$recipients = id(new PhabricatorUserEmail())
->loadAllWhere(
'address IN (%Ls) AND isVerified = 1',
$address_strings);
$recipient_phids = mpull($recipients, 'getUserPHID');
return $recipient_phids;
}
public function processReceivedMail() {
$viewer = $this->getViewer();
$sender = null;
try {
$this->dropMailFromPhabricator();
$this->dropMailAlreadyReceived();
$this->dropEmptyMail();
$sender = $this->loadSender();
if ($sender) {
$this->setAuthorPHID($sender->getPHID());
// If we've identified the sender, mark them as the author of any
// attached files. We do this before we validate them (below), since
// they still authored these files even if their account is not allowed
// to interact via email.
$attachments = $this->getAttachments();
if ($attachments) {
$files = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs($attachments)
->execute();
foreach ($files as $file) {
$file->setAuthorPHID($sender->getPHID())->save();
}
}
$this->validateSender($sender);
}
$receivers = id(new PhutilClassMapQuery())
->setAncestorClass('PhabricatorMailReceiver')
->setFilterMethod('isEnabled')
->execute();
$reserved_recipient = null;
$targets = $this->newTargetAddresses();
foreach ($targets as $key => $target) {
// Never accept any reserved address as a mail target. This prevents
// security issues around "hostmaster@" and bad behavior with
// "noreply@".
if (PhabricatorMailUtil::isReservedAddress($target)) {
if (!$reserved_recipient) {
$reserved_recipient = $target;
}
unset($targets[$key]);
continue;
}
// See T13234. Don't process mail if a user has attached this address
// to their account.
if (PhabricatorMailUtil::isUserAddress($target)) {
unset($targets[$key]);
continue;
}
}
$any_accepted = false;
$receiver_exception = null;
foreach ($receivers as $receiver) {
$receiver = id(clone $receiver)
->setViewer($viewer);
if ($sender) {
$receiver->setSender($sender);
}
foreach ($targets as $target) {
try {
if (!$receiver->canAcceptMail($this, $target)) {
continue;
}
$any_accepted = true;
$receiver->receiveMail($this, $target);
} catch (Exception $ex) {
// If receivers raise exceptions, we'll keep the first one in hope
// that it points at a root cause.
if (!$receiver_exception) {
$receiver_exception = $ex;
}
}
}
}
if ($receiver_exception) {
throw $receiver_exception;
}
if (!$any_accepted) {
if ($reserved_recipient) {
// If nothing accepted the mail, we normally raise an error to help
// users who mistakenly send mail to "barges@" instead of "bugs@".
// However, if the recipient list included a reserved recipient, we
// don't bounce the mail with an error.
// The intent here is that if a user does a "Reply All" and includes
// "From: noreply@phabricator" in the receipient list, we just want
// to drop the mail rather than send them an unhelpful bounce message.
throw new PhabricatorMetaMTAReceivedMailProcessingException(
MetaMTAReceivedMailStatus::STATUS_RESERVED,
pht(
'No application handled this mail. This mail was sent to a '.
'reserved recipient ("%s") so bounces are suppressed.',
(string)$reserved_recipient));
} else if (!$sender) {
// NOTE: Currently, we'll always drop this mail (since it's headed to
// an unverified recipient). See T12237. These details are still
// useful because they'll appear in the mail logs and Mail web UI.
throw new PhabricatorMetaMTAReceivedMailProcessingException(
MetaMTAReceivedMailStatus::STATUS_UNKNOWN_SENDER,
pht(
'This email was sent from an email address ("%s") that is not '.
- 'associated with a Phabricator account. To interact with '.
- 'Phabricator via email, add this address to your account.',
+ 'associated with a registered user account. To interact via '.
+ 'email, add this address to your account.',
(string)$this->newFromAddress()));
} else {
throw new PhabricatorMetaMTAReceivedMailProcessingException(
MetaMTAReceivedMailStatus::STATUS_NO_RECEIVERS,
pht(
- 'Phabricator can not process this mail because no application '.
+ 'This mail can not be processed because no application '.
'knows how to handle it. Check that the address you sent it to '.
- 'is correct.'.
- "\n\n".
- '(No concrete, enabled subclass of PhabricatorMailReceiver can '.
- 'accept this mail.)'));
+ 'is correct.'));
}
}
} catch (PhabricatorMetaMTAReceivedMailProcessingException $ex) {
switch ($ex->getStatusCode()) {
case MetaMTAReceivedMailStatus::STATUS_DUPLICATE:
case MetaMTAReceivedMailStatus::STATUS_FROM_PHABRICATOR:
// Don't send an error email back in these cases, since they're
// very unlikely to be the sender's fault.
break;
case MetaMTAReceivedMailStatus::STATUS_RESERVED:
// This probably is the sender's fault, but it's likely an accident
// that we received the mail at all.
break;
case MetaMTAReceivedMailStatus::STATUS_EMPTY_IGNORED:
// This error is explicitly ignored.
break;
default:
$this->sendExceptionMail($ex, $sender);
break;
}
$this
->setStatus($ex->getStatusCode())
->setMessage($ex->getMessage())
->save();
return $this;
} catch (Exception $ex) {
$this->sendExceptionMail($ex, $sender);
$this
->setStatus(MetaMTAReceivedMailStatus::STATUS_UNHANDLED_EXCEPTION)
->setMessage(pht('Unhandled Exception: %s', $ex->getMessage()))
->save();
throw $ex;
}
return $this->setMessage('OK')->save();
}
public function getCleanTextBody() {
$body = $this->getRawTextBody();
$parser = new PhabricatorMetaMTAEmailBodyParser();
return $parser->stripTextBody($body);
}
public function parseBody() {
$body = $this->getRawTextBody();
$parser = new PhabricatorMetaMTAEmailBodyParser();
return $parser->parseBody($body);
}
public function getRawTextBody() {
return idx($this->bodies, 'text');
}
/**
* Strip an email address down to the actual user@domain.tld part if
* necessary, since sometimes it will have formatting like
* '"Abraham Lincoln" <alincoln@logcab.in>'.
*/
private function getRawEmailAddress($address) {
$matches = null;
$ok = preg_match('/<(.*)>/', $address, $matches);
if ($ok) {
$address = $matches[1];
}
return $address;
}
private function getRawEmailAddresses($addresses) {
$raw_addresses = array();
- foreach (explode(',', $addresses) as $address) {
- $raw_addresses[] = $this->getRawEmailAddress($address);
+
+ if (phutil_nonempty_string($addresses)) {
+ foreach (explode(',', $addresses) as $address) {
+ $raw_addresses[] = $this->getRawEmailAddress($address);
+ }
}
+
return array_filter($raw_addresses);
}
/**
* If Phabricator sent the mail, always drop it immediately. This prevents
* loops where, e.g., the public bug address is also a user email address
* and creating a bug sends them an email, which loops.
*/
private function dropMailFromPhabricator() {
if (!$this->getHeader('x-phabricator-sent-this-message')) {
return;
}
throw new PhabricatorMetaMTAReceivedMailProcessingException(
MetaMTAReceivedMailStatus::STATUS_FROM_PHABRICATOR,
pht(
"Ignoring email with '%s' header to avoid loops.",
'X-Phabricator-Sent-This-Message'));
}
/**
* If this mail has the same message ID as some other mail, and isn't the
* first mail we we received with that message ID, we drop it as a duplicate.
*/
private function dropMailAlreadyReceived() {
$message_id_hash = $this->getMessageIDHash();
if (!$message_id_hash) {
// No message ID hash, so we can't detect duplicates. This should only
// happen with very old messages.
return;
}
$messages = $this->loadAllWhere(
'messageIDHash = %s ORDER BY id ASC LIMIT 2',
$message_id_hash);
$messages_count = count($messages);
if ($messages_count <= 1) {
// If we only have one copy of this message, we're good to process it.
return;
}
$first_message = reset($messages);
if ($first_message->getID() == $this->getID()) {
// If this is the first copy of the message, it is okay to process it.
// We may not have been able to to process it immediately when we received
// it, and could may have received several copies without processing any
// yet.
return;
}
$message = pht(
'Ignoring email with "Message-ID" hash "%s" that has been seen %d '.
'times, including this message.',
$message_id_hash,
$messages_count);
throw new PhabricatorMetaMTAReceivedMailProcessingException(
MetaMTAReceivedMailStatus::STATUS_DUPLICATE,
$message);
}
private function dropEmptyMail() {
$body = $this->getCleanTextBody();
$attachments = $this->getAttachments();
if (strlen($body) || $attachments) {
return;
}
// Only send an error email if the user is talking to just Phabricator.
// We can assume if there is only one "To" address it is a Phabricator
// address since this code is running and everything.
$is_direct_mail = (count($this->getToAddresses()) == 1) &&
(count($this->getCCAddresses()) == 0);
if ($is_direct_mail) {
$status_code = MetaMTAReceivedMailStatus::STATUS_EMPTY;
} else {
$status_code = MetaMTAReceivedMailStatus::STATUS_EMPTY_IGNORED;
}
throw new PhabricatorMetaMTAReceivedMailProcessingException(
$status_code,
pht(
'Your message does not contain any body text or attachments, so '.
- 'Phabricator can not do anything useful with it. Make sure comment '.
+ 'this server can not do anything useful with it. Make sure comment '.
'text appears at the top of your message: quoted replies, inline '.
'text, and signatures are discarded and ignored.'));
}
private function sendExceptionMail(
Exception $ex,
PhabricatorUser $viewer = null) {
// If we've failed to identify a legitimate sender, we don't send them
// an error message back. We want to avoid sending mail to unverified
// addresses. See T12491.
if (!$viewer) {
return;
}
if ($ex instanceof PhabricatorMetaMTAReceivedMailProcessingException) {
$status_code = $ex->getStatusCode();
$status_name = MetaMTAReceivedMailStatus::getHumanReadableName(
$status_code);
$title = pht('Error Processing Mail (%s)', $status_name);
$description = $ex->getMessage();
} else {
$title = pht('Error Processing Mail (%s)', get_class($ex));
$description = pht('%s: %s', get_class($ex), $ex->getMessage());
}
// TODO: Since headers don't necessarily have unique names, this may not
// really be all the headers. It would be nice to pass the raw headers
// through from the upper layers where possible.
// On the MimeMailParser pathway, we arrive here with a list value for
// headers that appeared multiple times in the original mail. Be
// accommodating until header handling gets straightened out.
$headers = array();
foreach ($this->headers as $key => $values) {
if (!is_array($values)) {
$values = array($values);
}
foreach ($values as $value) {
$headers[] = pht('%s: %s', $key, $value);
}
}
$headers = implode("\n", $headers);
$body = pht(<<<EOBODY
-Your email to Phabricator was not processed, because an error occurred while
+Your email to %s was not processed, because an error occurred while
trying to handle it:
%s
-- Original Message Body -----------------------------------------------------
%s
-- Original Message Headers --------------------------------------------------
%s
EOBODY
,
+ PlatformSymbols::getPlatformServerName(),
wordwrap($description, 78),
$this->getRawTextBody(),
$headers);
$mail = id(new PhabricatorMetaMTAMail())
->setIsErrorEmail(true)
->setSubject($title)
->addTos(array($viewer->getPHID()))
->setBody($body)
->saveAndSend();
}
public function newContentSource() {
return PhabricatorContentSource::newForSource(
PhabricatorEmailContentSource::SOURCECONST,
array(
'id' => $this->getID(),
));
}
public function newFromAddress() {
$raw_from = $this->getHeader('From');
if (strlen($raw_from)) {
return new PhutilEmailAddress($raw_from);
}
return null;
}
private function getViewer() {
return PhabricatorUser::getOmnipotentUser();
}
/**
* Identify the sender's user account for a piece of received mail.
*
* Note that this method does not validate that the sender is who they say
* they are, just that they've presented some credential which corresponds
* to a recognizable user.
*/
private function loadSender() {
$viewer = $this->getViewer();
// Try to identify the user based on their "From" address.
$from_address = $this->newFromAddress();
if ($from_address) {
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withEmails(array($from_address->getAddress()))
->executeOne();
if ($user) {
return $user;
}
}
return null;
}
private function validateSender(PhabricatorUser $sender) {
$failure_reason = null;
if ($sender->getIsDisabled()) {
$failure_reason = pht(
'Your account ("%s") is disabled, so you can not interact with '.
- 'Phabricator over email.',
+ 'over email.',
$sender->getUsername());
} else if ($sender->getIsStandardUser()) {
if (!$sender->getIsApproved()) {
$failure_reason = pht(
'Your account ("%s") has not been approved yet. You can not '.
- 'interact with Phabricator over email until your account is '.
- 'approved.',
+ 'interact over email until your account is approved.',
$sender->getUsername());
} else if (PhabricatorUserEmail::isEmailVerificationRequired() &&
!$sender->getIsEmailVerified()) {
$failure_reason = pht(
'You have not verified the email address for your account ("%s"). '.
- 'You must verify your email address before you can interact '.
- 'with Phabricator over email.',
+ 'You must verify your email address before you can interact over '.
+ 'email.',
$sender->getUsername());
}
}
if ($failure_reason) {
throw new PhabricatorMetaMTAReceivedMailProcessingException(
MetaMTAReceivedMailStatus::STATUS_DISABLED_SENDER,
$failure_reason);
}
}
}
diff --git a/src/applications/metamta/storage/__tests__/PhabricatorMetaMTAMailTestCase.php b/src/applications/metamta/storage/__tests__/PhabricatorMetaMTAMailTestCase.php
index 7462aaf558..e66dd8c61b 100644
--- a/src/applications/metamta/storage/__tests__/PhabricatorMetaMTAMailTestCase.php
+++ b/src/applications/metamta/storage/__tests__/PhabricatorMetaMTAMailTestCase.php
@@ -1,422 +1,422 @@
<?php
final class PhabricatorMetaMTAMailTestCase extends PhabricatorTestCase {
protected function getPhabricatorTestCaseConfiguration() {
return array(
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
);
}
public function testMailSendFailures() {
$user = $this->generateNewTestUser();
$phid = $user->getPHID();
// Normally, the send should succeed.
$mail = new PhabricatorMetaMTAMail();
$mail->addTos(array($phid));
$mailer = new PhabricatorMailTestAdapter();
$mail->sendWithMailers(array($mailer));
$this->assertEqual(
PhabricatorMailOutboundStatus::STATUS_SENT,
$mail->getStatus());
// When the mailer fails temporarily, the mail should remain queued.
$mail = new PhabricatorMetaMTAMail();
$mail->addTos(array($phid));
$mailer = new PhabricatorMailTestAdapter();
$mailer->setFailTemporarily(true);
try {
$mail->sendWithMailers(array($mailer));
} catch (Exception $ex) {
// Ignore.
}
$this->assertEqual(
PhabricatorMailOutboundStatus::STATUS_QUEUE,
$mail->getStatus());
// When the mailer fails permanently, the mail should be failed.
$mail = new PhabricatorMetaMTAMail();
$mail->addTos(array($phid));
$mailer = new PhabricatorMailTestAdapter();
$mailer->setFailPermanently(true);
try {
$mail->sendWithMailers(array($mailer));
} catch (Exception $ex) {
// Ignore.
}
$this->assertEqual(
PhabricatorMailOutboundStatus::STATUS_FAIL,
$mail->getStatus());
}
public function testRecipients() {
$user = $this->generateNewTestUser();
$phid = $user->getPHID();
$mailer = new PhabricatorMailTestAdapter();
$mail = new PhabricatorMetaMTAMail();
$mail->addTos(array($phid));
$this->assertTrue(
in_array($phid, $mail->buildRecipientList()),
pht('"To" is a recipient.'));
// Test that the "No Self Mail" and "No Mail" preferences work correctly.
$mail->setFrom($phid);
$this->assertTrue(
in_array($phid, $mail->buildRecipientList()),
pht('"From" does not exclude recipients by default.'));
$user = $this->writeSetting(
$user,
PhabricatorEmailSelfActionsSetting::SETTINGKEY,
true);
$this->assertFalse(
in_array($phid, $mail->buildRecipientList()),
pht('"From" excludes recipients with no-self-mail set.'));
$user = $this->writeSetting(
$user,
PhabricatorEmailSelfActionsSetting::SETTINGKEY,
null);
$this->assertTrue(
in_array($phid, $mail->buildRecipientList()),
pht('"From" does not exclude recipients by default.'));
$user = $this->writeSetting(
$user,
PhabricatorEmailNotificationsSetting::SETTINGKEY,
true);
$this->assertFalse(
in_array($phid, $mail->buildRecipientList()),
pht('"From" excludes recipients with no-mail set.'));
$mail->setForceDelivery(true);
$this->assertTrue(
in_array($phid, $mail->buildRecipientList()),
pht('"From" includes no-mail recipients when forced.'));
$mail->setForceDelivery(false);
$user = $this->writeSetting(
$user,
PhabricatorEmailNotificationsSetting::SETTINGKEY,
null);
$this->assertTrue(
in_array($phid, $mail->buildRecipientList()),
pht('"From" does not exclude recipients by default.'));
// Test that explicit exclusion works correctly.
$mail->setExcludeMailRecipientPHIDs(array($phid));
$this->assertFalse(
in_array($phid, $mail->buildRecipientList()),
pht('Explicit exclude excludes recipients.'));
$mail->setExcludeMailRecipientPHIDs(array());
// Test that mail tag preferences exclude recipients.
$user = $this->writeSetting(
$user,
PhabricatorEmailTagsSetting::SETTINGKEY,
array(
'test-tag' => false,
));
$mail->setMailTags(array('test-tag'));
$this->assertFalse(
in_array($phid, $mail->buildRecipientList()),
pht('Tag preference excludes recipients.'));
$user = $this->writeSetting(
$user,
PhabricatorEmailTagsSetting::SETTINGKEY,
null);
$this->assertTrue(
in_array($phid, $mail->buildRecipientList()),
'Recipients restored after tag preference removed.');
$email = id(new PhabricatorUserEmail())->loadOneWhere(
'userPHID = %s AND isPrimary = 1',
$phid);
$email->setIsVerified(0)->save();
$this->assertFalse(
in_array($phid, $mail->buildRecipientList()),
pht('Mail not sent to unverified address.'));
$email->setIsVerified(1)->save();
$this->assertTrue(
in_array($phid, $mail->buildRecipientList()),
pht('Mail sent to verified address.'));
}
public function testThreadIDHeaders() {
$this->runThreadIDHeadersWithConfiguration(true, true);
$this->runThreadIDHeadersWithConfiguration(true, false);
$this->runThreadIDHeadersWithConfiguration(false, true);
$this->runThreadIDHeadersWithConfiguration(false, false);
}
private function runThreadIDHeadersWithConfiguration(
$supports_message_id,
$is_first_mail) {
$user = $this->generateNewTestUser();
$phid = $user->getPHID();
$mailer = new PhabricatorMailTestAdapter();
$mailer->setSupportsMessageID($supports_message_id);
$thread_id = 'somethread-12345';
$mail = id(new PhabricatorMetaMTAMail())
->setThreadID($thread_id, $is_first_mail)
->addTos(array($phid))
->sendWithMailers(array($mailer));
$guts = $mailer->getGuts();
$headers = idx($guts, 'headers', array());
$dict = array();
foreach ($headers as $header) {
list($name, $value) = $header;
$dict[$name] = $value;
}
if ($is_first_mail && $supports_message_id) {
$expect_message_id = true;
$expect_in_reply_to = false;
$expect_references = false;
} else {
$expect_message_id = false;
$expect_in_reply_to = true;
$expect_references = true;
}
$case = '<message-id = '.($supports_message_id ? 'Y' : 'N').', '.
'first = '.($is_first_mail ? 'Y' : 'N').'>';
$this->assertTrue(
isset($dict['Thread-Index']),
pht('Expect Thread-Index header for case %s.', $case));
$this->assertEqual(
$expect_message_id,
isset($dict['Message-ID']),
pht(
'Expectation about existence of Message-ID header for case %s.',
$case));
$this->assertEqual(
$expect_in_reply_to,
isset($dict['In-Reply-To']),
pht(
'Expectation about existence of In-Reply-To header for case %s.',
$case));
$this->assertEqual(
$expect_references,
isset($dict['References']),
pht(
'Expectation about existence of References header for case %s.',
$case));
}
private function writeSetting(PhabricatorUser $user, $key, $value) {
$preferences = PhabricatorUserPreferences::loadUserPreferences($user);
$editor = id(new PhabricatorUserPreferencesEditor())
->setActor($user)
->setContentSource($this->newContentSource())
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$xactions = array();
$xactions[] = $preferences->newTransaction($key, $value);
$editor->applyTransactions($preferences, $xactions);
return id(new PhabricatorPeopleQuery())
->setViewer($user)
->withIDs(array($user->getID()))
->executeOne();
}
public function testMailerFailover() {
$user = $this->generateNewTestUser();
$phid = $user->getPHID();
$status_sent = PhabricatorMailOutboundStatus::STATUS_SENT;
$status_queue = PhabricatorMailOutboundStatus::STATUS_QUEUE;
$status_fail = PhabricatorMailOutboundStatus::STATUS_FAIL;
$mailer1 = id(new PhabricatorMailTestAdapter())
->setKey('mailer1');
$mailer2 = id(new PhabricatorMailTestAdapter())
->setKey('mailer2');
$mailers = array(
$mailer1,
$mailer2,
);
// Send mail with both mailers active. The first mailer should be used.
$mail = id(new PhabricatorMetaMTAMail())
->addTos(array($phid))
->sendWithMailers($mailers);
$this->assertEqual($status_sent, $mail->getStatus());
$this->assertEqual('mailer1', $mail->getMailerKey());
// If the first mailer fails, the mail should be sent with the second
// mailer. Since we transmitted the mail, this doesn't raise an exception.
$mailer1->setFailTemporarily(true);
$mail = id(new PhabricatorMetaMTAMail())
->addTos(array($phid))
->sendWithMailers($mailers);
$this->assertEqual($status_sent, $mail->getStatus());
$this->assertEqual('mailer2', $mail->getMailerKey());
// If both mailers fail, the mail should remain in queue.
$mailer2->setFailTemporarily(true);
$mail = id(new PhabricatorMetaMTAMail())
->addTos(array($phid));
$caught = null;
try {
$mail->sendWithMailers($mailers);
} catch (Exception $ex) {
$caught = $ex;
}
$this->assertTrue($caught instanceof Exception);
$this->assertEqual($status_queue, $mail->getStatus());
$this->assertEqual(null, $mail->getMailerKey());
$mailer1->setFailTemporarily(false);
$mailer2->setFailTemporarily(false);
// If the first mailer fails permanently, the mail should fail even though
// the second mailer isn't configured to fail.
$mailer1->setFailPermanently(true);
$mail = id(new PhabricatorMetaMTAMail())
->addTos(array($phid));
$caught = null;
try {
$mail->sendWithMailers($mailers);
} catch (Exception $ex) {
$caught = $ex;
}
$this->assertTrue($caught instanceof Exception);
$this->assertEqual($status_fail, $mail->getStatus());
$this->assertEqual(null, $mail->getMailerKey());
}
public function testMailSizeLimits() {
$env = PhabricatorEnv::beginScopedEnv();
$env->overrideEnvConfig('metamta.email-body-limit', 1024 * 512);
$user = $this->generateNewTestUser();
$phid = $user->getPHID();
$string_1kb = str_repeat('x', 1024);
$html_1kb = str_repeat('y', 1024);
$string_1mb = str_repeat('x', 1024 * 1024);
$html_1mb = str_repeat('y', 1024 * 1024);
// First, send a mail with a small text body and a small HTML body to make
// sure the basics work properly.
$mail = id(new PhabricatorMetaMTAMail())
->addTos(array($phid))
->setBody($string_1kb)
->setHTMLBody($html_1kb);
$mailer = new PhabricatorMailTestAdapter();
$mail->sendWithMailers(array($mailer));
$this->assertEqual(
PhabricatorMailOutboundStatus::STATUS_SENT,
$mail->getStatus());
$text_body = $mailer->getBody();
$html_body = $mailer->getHTMLBody();
$this->assertEqual($string_1kb, $text_body);
$this->assertEqual($html_1kb, $html_body);
// Now, send a mail with a large text body and a large HTML body. We expect
// the text body to be truncated and the HTML body to be dropped.
$mail = id(new PhabricatorMetaMTAMail())
->addTos(array($phid))
->setBody($string_1mb)
->setHTMLBody($html_1mb);
$mailer = new PhabricatorMailTestAdapter();
$mail->sendWithMailers(array($mailer));
$this->assertEqual(
PhabricatorMailOutboundStatus::STATUS_SENT,
$mail->getStatus());
$text_body = $mailer->getBody();
$html_body = $mailer->getHTMLBody();
// We expect the body was truncated, because it exceeded the body limit.
$this->assertTrue(
(strlen($text_body) < strlen($string_1mb)),
pht('Text Body Truncated'));
// We expect the HTML body was dropped completely after the text body was
// truncated.
$this->assertTrue(
- !strlen($html_body),
+ !phutil_nonempty_string($html_body),
pht('HTML Body Removed'));
// Next send a mail with a small text body and a large HTML body. We expect
// the text body to be intact and the HTML body to be dropped.
$mail = id(new PhabricatorMetaMTAMail())
->addTos(array($phid))
->setBody($string_1kb)
->setHTMLBody($html_1mb);
$mailer = new PhabricatorMailTestAdapter();
$mail->sendWithMailers(array($mailer));
$this->assertEqual(
PhabricatorMailOutboundStatus::STATUS_SENT,
$mail->getStatus());
$text_body = $mailer->getBody();
$html_body = $mailer->getHTMLBody();
$this->assertEqual($string_1kb, $text_body);
- $this->assertTrue(!strlen($html_body));
+ $this->assertTrue(!phutil_nonempty_string($html_body));
}
}
diff --git a/src/applications/metamta/util/PhabricatorMailUtil.php b/src/applications/metamta/util/PhabricatorMailUtil.php
index a5fbc7179e..270e9786f3 100644
--- a/src/applications/metamta/util/PhabricatorMailUtil.php
+++ b/src/applications/metamta/util/PhabricatorMailUtil.php
@@ -1,119 +1,118 @@
<?php
final class PhabricatorMailUtil
extends Phobject {
/**
* Normalize an email address for comparison or lookup.
*
* Phabricator can be configured to prepend a prefix to all reply addresses,
* which can make forwarding rules easier to write. This method strips the
* prefix if it is present, and normalizes casing and whitespace.
*
* @param PhutilEmailAddress Email address.
* @return PhutilEmailAddress Normalized address.
*/
public static function normalizeAddress(PhutilEmailAddress $address) {
$raw_address = $address->getAddress();
$raw_address = phutil_utf8_strtolower($raw_address);
$raw_address = trim($raw_address);
// If a mailbox prefix is configured and present, strip it off.
$prefix_key = 'metamta.single-reply-handler-prefix';
$prefix = PhabricatorEnv::getEnvConfig($prefix_key);
- $len = strlen($prefix);
- if ($len) {
+ if (phutil_nonempty_string($prefix)) {
$prefix = $prefix.'+';
- $len = $len + 1;
+ $len = strlen($prefix);
if (!strncasecmp($raw_address, $prefix, $len)) {
$raw_address = substr($raw_address, $len);
}
}
return id(clone $address)
->setAddress($raw_address);
}
/**
* Determine if two inbound email addresses are effectively identical.
*
* This method strips and normalizes addresses so that equivalent variations
* are correctly detected as identical. For example, these addresses are all
* considered to match one another:
*
* "Abraham Lincoln" <alincoln@example.com>
* alincoln@example.com
* <ALincoln@example.com>
* "Abraham" <phabricator+ALINCOLN@EXAMPLE.COM> # With configured prefix.
*
* @param PhutilEmailAddress Email address.
* @param PhutilEmailAddress Another email address.
* @return bool True if addresses are effectively the same address.
*/
public static function matchAddresses(
PhutilEmailAddress $u,
PhutilEmailAddress $v) {
$u = self::normalizeAddress($u);
$v = self::normalizeAddress($v);
return ($u->getAddress() === $v->getAddress());
}
public static function isReservedAddress(PhutilEmailAddress $address) {
$address = self::normalizeAddress($address);
$local = $address->getLocalPart();
$reserved = array(
'admin',
'administrator',
'hostmaster',
'list',
'list-request',
'majordomo',
'postmaster',
'root',
'ssl-admin',
'ssladmin',
'ssladministrator',
'sslwebmaster',
'sysadmin',
'uucp',
'webmaster',
'noreply',
'no-reply',
);
$reserved = array_fuse($reserved);
if (isset($reserved[$local])) {
return true;
}
$default_address = id(new PhabricatorMailEmailEngine())
->newDefaultEmailAddress();
if (self::matchAddresses($address, $default_address)) {
return true;
}
$void_address = id(new PhabricatorMailEmailEngine())
->newVoidEmailAddress();
if (self::matchAddresses($address, $void_address)) {
return true;
}
return false;
}
public static function isUserAddress(PhutilEmailAddress $address) {
$user_email = id(new PhabricatorUserEmail())->loadOneWhere(
'address = %s',
$address->getAddress());
return (bool)$user_email;
}
}
diff --git a/src/applications/notification/setup/PhabricatorAphlictSetupCheck.php b/src/applications/notification/setup/PhabricatorAphlictSetupCheck.php
index 7a00673ae1..20787c79bd 100644
--- a/src/applications/notification/setup/PhabricatorAphlictSetupCheck.php
+++ b/src/applications/notification/setup/PhabricatorAphlictSetupCheck.php
@@ -1,41 +1,41 @@
<?php
final class PhabricatorAphlictSetupCheck extends PhabricatorSetupCheck {
protected function executeChecks() {
try {
PhabricatorNotificationClient::tryAnyConnection();
} catch (Exception $ex) {
$message = pht(
- "Phabricator is configured to use a notification server, but is ".
+ "This server is configured to use a notification server, but is ".
"unable to connect to it. You should resolve this issue or disable ".
"the notification server. It may be helpful to double check your ".
"configuration or restart the server using the command below.\n\n%s",
phutil_tag(
'pre',
array(),
array(
get_class($ex),
"\n",
$ex->getMessage(),
)));
$this->newIssue('aphlict.connect')
->setShortName(pht('Notification Server Down'))
->setName(pht('Unable to Connect to Notification Server'))
->setSummary(
pht(
- 'Phabricator is configured to use a notification server, '.
+ 'This server is configured to use a notification server, '.
'but is not able to connect to it.'))
->setMessage($message)
->addRelatedPhabricatorConfig('notification.servers')
->addCommand(
pht(
"(To start the server, run this command.)\n%s",
- 'phabricator/ $ ./bin/aphlict start'));
+ '$ ./bin/aphlict start'));
return;
}
}
}
diff --git a/src/applications/nuance/github/NuanceGitHubRawEvent.php b/src/applications/nuance/github/NuanceGitHubRawEvent.php
index b28a9222dc..8652dca9ad 100644
--- a/src/applications/nuance/github/NuanceGitHubRawEvent.php
+++ b/src/applications/nuance/github/NuanceGitHubRawEvent.php
@@ -1,391 +1,391 @@
<?php
final class NuanceGitHubRawEvent extends Phobject {
private $raw;
private $type;
const TYPE_ISSUE = 'issue';
const TYPE_REPOSITORY = 'repository';
public static function newEvent($type, array $raw) {
$event = new self();
$event->type = $type;
$event->raw = $raw;
return $event;
}
public function getRepositoryFullName() {
return $this->getRepositoryFullRawName();
}
public function isIssueEvent() {
if ($this->isPullRequestEvent()) {
return false;
}
if ($this->type == self::TYPE_ISSUE) {
return true;
}
switch ($this->getIssueRawKind()) {
case 'IssuesEvent':
return true;
case 'IssueCommentEvent':
if (!$this->getRawPullRequestData()) {
return true;
}
break;
}
return false;
}
public function isPullRequestEvent() {
if ($this->type == self::TYPE_ISSUE) {
// TODO: This is wrong, some of these are pull events.
return false;
}
$raw = $this->raw;
switch ($this->getIssueRawKind()) {
case 'PullRequestEvent':
return true;
case 'IssueCommentEvent':
if ($this->getRawPullRequestData()) {
return true;
}
break;
}
return false;
}
public function getIssueNumber() {
if (!$this->isIssueEvent()) {
return null;
}
return $this->getRawIssueNumber();
}
public function getPullRequestNumber() {
if (!$this->isPullRequestEvent()) {
return null;
}
return $this->getRawIssueNumber();
}
public function getID() {
$raw = $this->raw;
$id = idx($raw, 'id');
if ($id) {
return (int)$id;
}
return null;
}
public function getComment() {
if (!$this->isIssueEvent() && !$this->isPullRequestEvent()) {
return null;
}
$raw = $this->raw;
return idxv($raw, array('payload', 'comment', 'body'));
}
public function getURI() {
$raw = $this->raw;
if ($this->isIssueEvent() || $this->isPullRequestEvent()) {
if ($this->type == self::TYPE_ISSUE) {
$uri = idxv($raw, array('issue', 'html_url'));
$uri = $uri.'#event-'.$this->getID();
} else {
// The format of pull request events varies so we need to fish around
// a bit to find the correct URI.
$uri = idxv($raw, array('payload', 'pull_request', 'html_url'));
$need_anchor = true;
// For comments, we get a different anchor to link to the comment. In
// this case, the URI comes with an anchor already.
if (!$uri) {
$uri = idxv($raw, array('payload', 'comment', 'html_url'));
$need_anchor = false;
}
if (!$uri) {
$uri = idxv($raw, array('payload', 'issue', 'html_url'));
$need_anchor = true;
}
if ($need_anchor) {
$uri = $uri.'#event-'.$this->getID();
}
}
} else {
switch ($this->getIssueRawKind()) {
case 'CreateEvent':
$ref = idxv($raw, array('payload', 'ref'));
$repo = $this->getRepositoryFullRawName();
return "https://github.com/{$repo}/commits/{$ref}";
case 'PushEvent':
// These don't really have a URI since there may be multiple commits
// involved and GitHub doesn't bundle the push as an object on its
// own. Just try to find the URI for the log. The API also does
// not return any HTML URI for these events.
$head = idxv($raw, array('payload', 'head'));
if ($head === null) {
return null;
}
$repo = $this->getRepositoryFullRawName();
return "https://github.com/{$repo}/commits/{$head}";
case 'WatchEvent':
// These have no reasonable URI.
return null;
default:
return null;
}
}
return $uri;
}
private function getRepositoryFullRawName() {
$raw = $this->raw;
$full = idxv($raw, array('repo', 'name'));
- if (strlen($full)) {
+ if (phutil_nonempty_string($full)) {
return $full;
}
// For issue events, the repository is not identified explicitly in the
// response body. Parse it out of the URI.
$matches = null;
$ok = preg_match(
'(/repos/((?:[^/]+)/(?:[^/]+))/issues/events/)',
idx($raw, 'url'),
$matches);
if ($ok) {
return $matches[1];
}
return null;
}
private function getIssueRawKind() {
$raw = $this->raw;
return idxv($raw, array('type'));
}
private function getRawIssueNumber() {
$raw = $this->raw;
if ($this->type == self::TYPE_ISSUE) {
return idxv($raw, array('issue', 'number'));
}
if ($this->type == self::TYPE_REPOSITORY) {
$issue_number = idxv($raw, array('payload', 'issue', 'number'));
if ($issue_number) {
return $issue_number;
}
$pull_number = idxv($raw, array('payload', 'number'));
if ($pull_number) {
return $pull_number;
}
}
return null;
}
private function getRawPullRequestData() {
$raw = $this->raw;
return idxv($raw, array('payload', 'issue', 'pull_request'));
}
public function getEventFullTitle() {
switch ($this->type) {
case self::TYPE_ISSUE:
$title = $this->getRawIssueEventTitle();
break;
case self::TYPE_REPOSITORY:
$title = $this->getRawRepositoryEventTitle();
break;
default:
$title = pht('Unknown Event Type ("%s")', $this->type);
break;
}
return pht(
'GitHub %s %s (%s)',
$this->getRepositoryFullRawName(),
$this->getTargetObjectName(),
$title);
}
public function getActorGitHubUserID() {
$raw = $this->raw;
return (int)idxv($raw, array('actor', 'id'));
}
private function getTargetObjectName() {
if ($this->isPullRequestEvent()) {
$number = $this->getRawIssueNumber();
return pht('Pull Request #%d', $number);
} else if ($this->isIssueEvent()) {
$number = $this->getRawIssueNumber();
return pht('Issue #%d', $number);
} else if ($this->type == self::TYPE_REPOSITORY) {
$raw = $this->raw;
$type = idx($raw, 'type');
switch ($type) {
case 'CreateEvent':
$ref = idxv($raw, array('payload', 'ref'));
$ref_type = idxv($raw, array('payload', 'ref_type'));
switch ($ref_type) {
case 'branch':
return pht('Branch %s', $ref);
case 'tag':
return pht('Tag %s', $ref);
default:
return pht('Ref %s', $ref);
}
break;
case 'PushEvent':
$ref = idxv($raw, array('payload', 'ref'));
if (preg_match('(^refs/heads/)', $ref)) {
return pht('Branch %s', substr($ref, strlen('refs/heads/')));
} else {
return pht('Ref %s', $ref);
}
break;
case 'WatchEvent':
$actor = idxv($raw, array('actor', 'login'));
return pht('User %s', $actor);
}
return pht('Unknown Object');
} else {
return pht('Unknown Object');
}
}
private function getRawIssueEventTitle() {
$raw = $this->raw;
$action = idxv($raw, array('event'));
switch ($action) {
case 'assigned':
$assignee = idxv($raw, array('assignee', 'login'));
$title = pht('Assigned: %s', $assignee);
break;
case 'closed':
$title = pht('Closed');
break;
case 'demilestoned':
$milestone = idxv($raw, array('milestone', 'title'));
$title = pht('Removed Milestone: %s', $milestone);
break;
case 'labeled':
$label = idxv($raw, array('label', 'name'));
$title = pht('Added Label: %s', $label);
break;
case 'locked':
$title = pht('Locked');
break;
case 'milestoned':
$milestone = idxv($raw, array('milestone', 'title'));
$title = pht('Added Milestone: %s', $milestone);
break;
case 'renamed':
$title = pht('Renamed');
break;
case 'reopened':
$title = pht('Reopened');
break;
case 'unassigned':
$assignee = idxv($raw, array('assignee', 'login'));
$title = pht('Unassigned: %s', $assignee);
break;
case 'unlabeled':
$label = idxv($raw, array('label', 'name'));
$title = pht('Removed Label: %s', $label);
break;
case 'unlocked':
$title = pht('Unlocked');
break;
default:
$title = pht('"%s"', $action);
break;
}
return $title;
}
private function getRawRepositoryEventTitle() {
$raw = $this->raw;
$type = idx($raw, 'type');
switch ($type) {
case 'CreateEvent':
return pht('Created');
case 'PushEvent':
$head = idxv($raw, array('payload', 'head'));
$head = substr($head, 0, 12);
return pht('Pushed: %s', $head);
case 'IssuesEvent':
$action = idxv($raw, array('payload', 'action'));
switch ($action) {
case 'closed':
return pht('Closed');
case 'opened':
return pht('Created');
case 'reopened':
return pht('Reopened');
default:
return pht('"%s"', $action);
}
break;
case 'IssueCommentEvent':
$action = idxv($raw, array('payload', 'action'));
switch ($action) {
case 'created':
return pht('Comment');
default:
return pht('"%s"', $action);
}
break;
case 'PullRequestEvent':
$action = idxv($raw, array('payload', 'action'));
switch ($action) {
case 'opened':
return pht('Created');
default:
return pht('"%s"', $action);
}
break;
case 'WatchEvent':
return pht('Watched');
}
return pht('"%s"', $type);
}
}
diff --git a/src/applications/nuance/query/NuanceImportCursorDataQuery.php b/src/applications/nuance/query/NuanceImportCursorDataQuery.php
index ae451abfb9..133c940b3f 100644
--- a/src/applications/nuance/query/NuanceImportCursorDataQuery.php
+++ b/src/applications/nuance/query/NuanceImportCursorDataQuery.php
@@ -1,60 +1,56 @@
<?php
final class NuanceImportCursorDataQuery
extends NuanceQuery {
private $ids;
private $phids;
private $sourcePHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withSourcePHIDs(array $source_phids) {
$this->sourcePHIDs = $source_phids;
return $this;
}
public function newResultObject() {
return new NuanceImportCursorData();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->sourcePHIDs !== null) {
$where[] = qsprintf(
$conn,
'sourcePHID IN (%Ls)',
$this->sourcePHIDs);
}
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
return $where;
}
}
diff --git a/src/applications/nuance/query/NuanceItemCommandQuery.php b/src/applications/nuance/query/NuanceItemCommandQuery.php
index 27137cf8f6..a694657120 100644
--- a/src/applications/nuance/query/NuanceItemCommandQuery.php
+++ b/src/applications/nuance/query/NuanceItemCommandQuery.php
@@ -1,60 +1,56 @@
<?php
final class NuanceItemCommandQuery
extends NuanceQuery {
private $ids;
private $itemPHIDs;
private $statuses;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withItemPHIDs(array $item_phids) {
$this->itemPHIDs = $item_phids;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function newResultObject() {
return new NuanceItemCommand();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->itemPHIDs !== null) {
$where[] = qsprintf(
$conn,
'itemPHID IN (%Ls)',
$this->itemPHIDs);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'status IN (%Ls)',
$this->statuses);
}
return $where;
}
}
diff --git a/src/applications/nuance/query/NuanceItemQuery.php b/src/applications/nuance/query/NuanceItemQuery.php
index 834e81ca72..e2e32c84b4 100644
--- a/src/applications/nuance/query/NuanceItemQuery.php
+++ b/src/applications/nuance/query/NuanceItemQuery.php
@@ -1,196 +1,192 @@
<?php
final class NuanceItemQuery
extends NuanceQuery {
private $ids;
private $phids;
private $sourcePHIDs;
private $queuePHIDs;
private $itemTypes;
private $itemKeys;
private $containerKeys;
private $statuses;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withSourcePHIDs(array $source_phids) {
$this->sourcePHIDs = $source_phids;
return $this;
}
public function withQueuePHIDs(array $queue_phids) {
$this->queuePHIDs = $queue_phids;
return $this;
}
public function withItemTypes(array $item_types) {
$this->itemTypes = $item_types;
return $this;
}
public function withItemKeys(array $item_keys) {
$this->itemKeys = $item_keys;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function withItemContainerKeys(array $container_keys) {
$this->containerKeys = $container_keys;
return $this;
}
public function newResultObject() {
return new NuanceItem();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $items) {
$viewer = $this->getViewer();
$source_phids = mpull($items, 'getSourcePHID');
$sources = id(new NuanceSourceQuery())
->setViewer($viewer)
->withPHIDs($source_phids)
->execute();
$sources = mpull($sources, null, 'getPHID');
foreach ($items as $key => $item) {
$source = idx($sources, $item->getSourcePHID());
if (!$source) {
$this->didRejectResult($items[$key]);
unset($items[$key]);
continue;
}
$item->attachSource($source);
}
$type_map = NuanceItemType::getAllItemTypes();
foreach ($items as $key => $item) {
$type = idx($type_map, $item->getItemType());
if (!$type) {
$this->didRejectResult($items[$key]);
unset($items[$key]);
continue;
}
$item->attachImplementation($type);
}
$queue_phids = array();
foreach ($items as $item) {
$queue_phid = $item->getQueuePHID();
if ($queue_phid) {
$queue_phids[$queue_phid] = $queue_phid;
}
}
if ($queue_phids) {
$queues = id(new NuanceQueueQuery())
->setViewer($viewer)
->withPHIDs($queue_phids)
->execute();
$queues = mpull($queues, null, 'getPHID');
} else {
$queues = array();
}
foreach ($items as $key => $item) {
$queue_phid = $item->getQueuePHID();
if (!$queue_phid) {
$item->attachQueue(null);
continue;
}
$queue = idx($queues, $queue_phid);
if (!$queue) {
unset($items[$key]);
$this->didRejectResult($item);
continue;
}
$item->attachQueue($queue);
}
return $items;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->sourcePHIDs !== null) {
$where[] = qsprintf(
$conn,
'sourcePHID IN (%Ls)',
$this->sourcePHIDs);
}
if ($this->queuePHIDs !== null) {
$where[] = qsprintf(
$conn,
'queuePHID IN (%Ls)',
$this->queuePHIDs);
}
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'status IN (%Ls)',
$this->statuses);
}
if ($this->itemTypes !== null) {
$where[] = qsprintf(
$conn,
'itemType IN (%Ls)',
$this->itemTypes);
}
if ($this->itemKeys !== null) {
$where[] = qsprintf(
$conn,
'itemKey IN (%Ls)',
$this->itemKeys);
}
if ($this->containerKeys !== null) {
$where[] = qsprintf(
$conn,
'itemContainerKey IN (%Ls)',
$this->containerKeys);
}
return $where;
}
}
diff --git a/src/applications/nuance/query/NuanceQueueQuery.php b/src/applications/nuance/query/NuanceQueueQuery.php
index 10f761d189..acb69665e0 100644
--- a/src/applications/nuance/query/NuanceQueueQuery.php
+++ b/src/applications/nuance/query/NuanceQueueQuery.php
@@ -1,47 +1,43 @@
<?php
final class NuanceQueueQuery
extends NuanceQuery {
private $ids;
private $phids;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function newResultObject() {
return new NuanceQueue();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
return $where;
}
}
diff --git a/src/applications/nuance/query/NuanceSourceQuery.php b/src/applications/nuance/query/NuanceSourceQuery.php
index 907d9c314f..9186d47cdd 100644
--- a/src/applications/nuance/query/NuanceSourceQuery.php
+++ b/src/applications/nuance/query/NuanceSourceQuery.php
@@ -1,137 +1,133 @@
<?php
final class NuanceSourceQuery
extends NuanceQuery {
private $ids;
private $phids;
private $types;
private $isDisabled;
private $hasCursors;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withTypes($types) {
$this->types = $types;
return $this;
}
public function withIsDisabled($disabled) {
$this->isDisabled = $disabled;
return $this;
}
public function withHasImportCursors($has_cursors) {
$this->hasCursors = $has_cursors;
return $this;
}
public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint(
new NuanceSourceNameNgrams(),
$ngrams);
}
public function newResultObject() {
return new NuanceSource();
}
protected function getPrimaryTableAlias() {
return 'source';
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $sources) {
$all_types = NuanceSourceDefinition::getAllDefinitions();
foreach ($sources as $key => $source) {
$definition = idx($all_types, $source->getType());
if (!$definition) {
$this->didRejectResult($source);
unset($sources[$key]);
continue;
}
$source->attachDefinition($definition);
}
return $sources;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->types !== null) {
$where[] = qsprintf(
$conn,
'source.type IN (%Ls)',
$this->types);
}
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'source.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'source.phid IN (%Ls)',
$this->phids);
}
if ($this->isDisabled !== null) {
$where[] = qsprintf(
$conn,
'source.isDisabled = %d',
(int)$this->isDisabled);
}
if ($this->hasCursors !== null) {
$cursor_types = array();
$definitions = NuanceSourceDefinition::getAllDefinitions();
foreach ($definitions as $key => $definition) {
if ($definition->hasImportCursors()) {
$cursor_types[] = $key;
}
}
if ($this->hasCursors) {
if (!$cursor_types) {
throw new PhabricatorEmptyQueryException();
} else {
$where[] = qsprintf(
$conn,
'source.type IN (%Ls)',
$cursor_types);
}
} else {
if (!$cursor_types) {
// Apply no constraint.
} else {
$where[] = qsprintf(
$conn,
'source.type NOT IN (%Ls)',
$cursor_types);
}
}
}
return $where;
}
}
diff --git a/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php b/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php
index 2eaf5781dc..3862cfed92 100644
--- a/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php
+++ b/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php
@@ -1,91 +1,91 @@
<?php
final class NuancePhabricatorFormSourceDefinition
extends NuanceSourceDefinition {
public function getName() {
- return pht('Phabricator Form');
+ return pht('Web Form');
}
public function getSourceDescription() {
return pht('Create a web form that submits into a Nuance queue.');
}
public function getSourceTypeConstant() {
return 'phabricator-form';
}
public function getSourceViewActions(AphrontRequest $request) {
$actions = array();
$actions[] = id(new PhabricatorActionView())
->setName(pht('View Form'))
->setIcon('fa-align-justify')
->setHref($this->getActionURI());
return $actions;
}
public function handleActionRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
// TODO: As above, this would eventually be driven by custom logic.
if ($request->isFormPost()) {
$properties = array(
'complaint' => (string)$request->getStr('complaint'),
);
$content_source = PhabricatorContentSource::newFromRequest($request);
$item = $this->newItemFromProperties(
NuanceFormItemType::ITEMTYPE,
$viewer->getPHID(),
$properties,
$content_source);
$uri = $item->getURI();
return id(new AphrontRedirectResponse())->setURI($uri);
}
$form = id(new AphrontFormView())
->setUser($viewer)
->appendRemarkupInstructions(
pht('IMPORTANT: This is a very rough prototype.'))
->appendRemarkupInstructions(
pht('Got a complaint? Complain here! We love complaints.'))
->appendChild(
id(new AphrontFormTextAreaControl())
->setName('complaint')
->setLabel(pht('Complaint')))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Submit Complaint')));
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Complaint Form'))
->appendChild($form);
return $box;
}
public function renderItemEditProperties(
PhabricatorUser $viewer,
NuanceItem $item,
PHUIPropertyListView $view) {
$this->renderItemCommonProperties($viewer, $item, $view);
}
private function renderItemCommonProperties(
PhabricatorUser $viewer,
NuanceItem $item,
PHUIPropertyListView $view) {
$complaint = $item->getItemProperty('complaint');
$complaint = new PHUIRemarkupView($viewer, $complaint);
$view->addSectionHeader(
pht('Complaint'), 'fa-exclamation-circle');
$view->addTextContent($complaint);
}
}
diff --git a/src/applications/oauthserver/application/PhabricatorOAuthServerApplication.php b/src/applications/oauthserver/application/PhabricatorOAuthServerApplication.php
index 90e2d5a7dd..70dfd5ea8a 100644
--- a/src/applications/oauthserver/application/PhabricatorOAuthServerApplication.php
+++ b/src/applications/oauthserver/application/PhabricatorOAuthServerApplication.php
@@ -1,74 +1,76 @@
<?php
final class PhabricatorOAuthServerApplication extends PhabricatorApplication {
public function getName() {
return pht('OAuth Server');
}
public function getBaseURI() {
return '/oauthserver/';
}
public function getShortDescription() {
return pht('OAuth Login Provider');
}
public function getIcon() {
return 'fa-hotel';
}
public function getTitleGlyph() {
return "\xE2\x99\x86";
}
public function getFlavorText() {
- return pht('Log In with Phabricator');
+ return pht(
+ 'Log In with %s',
+ PlatformSymbols::getPlatformServerName());
}
public function getApplicationGroup() {
return self::GROUP_ADMIN;
}
public function isPrototype() {
return true;
}
public function getHelpDocumentationArticles(PhabricatorUser $viewer) {
return array(
array(
'name' => pht('Using the Phabricator OAuth Server'),
'href' => PhabricatorEnv::getDoclink(
'Using the Phabricator OAuth Server'),
),
);
}
public function getRoutes() {
return array(
'/oauthserver/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhabricatorOAuthClientListController',
'auth/' => 'PhabricatorOAuthServerAuthController',
'token/' => 'PhabricatorOAuthServerTokenController',
$this->getEditRoutePattern('edit/') =>
'PhabricatorOAuthClientEditController',
'client/' => array(
'disable/(?P<id>\d+)/' => 'PhabricatorOAuthClientDisableController',
'view/(?P<id>\d+)/' => 'PhabricatorOAuthClientViewController',
'secret/(?P<id>\d+)/' => 'PhabricatorOAuthClientSecretController',
'test/(?P<id>\d+)/' => 'PhabricatorOAuthClientTestController',
),
),
);
}
protected function getCustomCapabilities() {
return array(
PhabricatorOAuthServerCreateClientsCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN,
),
);
}
}
diff --git a/src/applications/oauthserver/controller/PhabricatorOAuthServerAuthController.php b/src/applications/oauthserver/controller/PhabricatorOAuthServerAuthController.php
index 2b454e00ef..6b10dd60c5 100644
--- a/src/applications/oauthserver/controller/PhabricatorOAuthServerAuthController.php
+++ b/src/applications/oauthserver/controller/PhabricatorOAuthServerAuthController.php
@@ -1,316 +1,318 @@
<?php
final class PhabricatorOAuthServerAuthController
extends PhabricatorOAuthServerController {
protected function buildApplicationCrumbs() {
// We're specifically not putting an "OAuth Server" application crumb
// on the auth pages because it doesn't make sense to send users there.
return new PHUICrumbsView();
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$server = new PhabricatorOAuthServer();
$client_phid = $request->getStr('client_id');
$redirect_uri = $request->getStr('redirect_uri');
$response_type = $request->getStr('response_type');
// state is an opaque value the client sent us for their own purposes
// we just need to send it right back to them in the response!
$state = $request->getStr('state');
if (!$client_phid) {
return $this->buildErrorResponse(
'invalid_request',
pht('Malformed Request'),
pht(
'Required parameter %s was not present in the request.',
phutil_tag('strong', array(), 'client_id')));
}
// We require that users must be able to see an OAuth application
// in order to authorize it. This allows an application's visibility
// policy to be used to restrict authorized users.
try {
$client = id(new PhabricatorOAuthServerClientQuery())
->setViewer($viewer)
->withPHIDs(array($client_phid))
->executeOne();
} catch (PhabricatorPolicyException $ex) {
$ex->setContext(self::CONTEXT_AUTHORIZE);
throw $ex;
}
$server->setUser($viewer);
$is_authorized = false;
$authorization = null;
$uri = null;
$name = null;
// one giant try / catch around all the exciting database stuff so we
// can return a 'server_error' response if something goes wrong!
try {
if (!$client) {
return $this->buildErrorResponse(
'invalid_request',
pht('Invalid Client Application'),
pht(
'Request parameter %s does not specify a valid client application.',
phutil_tag('strong', array(), 'client_id')));
}
if ($client->getIsDisabled()) {
return $this->buildErrorResponse(
'invalid_request',
pht('Application Disabled'),
pht(
'The %s OAuth application has been disabled.',
phutil_tag('strong', array(), 'client_id')));
}
$name = $client->getName();
$server->setClient($client);
if ($redirect_uri) {
$client_uri = new PhutilURI($client->getRedirectURI());
$redirect_uri = new PhutilURI($redirect_uri);
if (!($server->validateSecondaryRedirectURI($redirect_uri,
$client_uri))) {
return $this->buildErrorResponse(
'invalid_request',
pht('Invalid Redirect URI'),
pht(
'Request parameter %s specifies an invalid redirect URI. '.
'The redirect URI must be a fully-qualified domain with no '.
'fragments, and must have the same domain and at least '.
'the same query parameters as the redirect URI the client '.
'registered.',
phutil_tag('strong', array(), 'redirect_uri')));
}
$uri = $redirect_uri;
} else {
$uri = new PhutilURI($client->getRedirectURI());
}
if (empty($response_type)) {
return $this->buildErrorResponse(
'invalid_request',
pht('Invalid Response Type'),
pht(
'Required request parameter %s is missing.',
phutil_tag('strong', array(), 'response_type')));
}
if ($response_type != 'code') {
return $this->buildErrorResponse(
'unsupported_response_type',
pht('Unsupported Response Type'),
pht(
'Request parameter %s specifies an unsupported response type. '.
'Valid response types are: %s.',
phutil_tag('strong', array(), 'response_type'),
implode(', ', array('code'))));
}
$requested_scope = $request->getStrList('scope');
$requested_scope = array_fuse($requested_scope);
$scope = PhabricatorOAuthServerScope::filterScope($requested_scope);
// NOTE: We're always requiring a confirmation dialog to redirect.
// Partly this is a general defense against redirect attacks, and
// partly this shakes off anchors in the URI (which are not shaken
// by 302'ing).
$auth_info = $server->userHasAuthorizedClient($scope);
list($is_authorized, $authorization) = $auth_info;
if ($request->isFormPost()) {
if ($authorization) {
$authorization->setScope($scope)->save();
} else {
$authorization = $server->authorizeClient($scope);
}
$is_authorized = true;
}
} catch (Exception $e) {
return $this->buildErrorResponse(
'server_error',
pht('Server Error'),
pht(
'The authorization server encountered an unexpected condition '.
'which prevented it from fulfilling the request.'));
}
// When we reach this part of the controller, we can be in two states:
//
// 1. The user has not authorized the application yet. We want to
// give them an "Authorize this application?" dialog.
// 2. The user has authorized the application. We want to give them
// a "Confirm Login" dialog.
if ($is_authorized) {
// The second case is simpler, so handle it first. The user either
// authorized the application previously, or has just authorized the
// application. Show them a confirm dialog with a normal link back to
// the application. This shakes anchors from the URI.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$auth_code = $server->generateAuthorizationCode($uri);
unset($unguarded);
$full_uri = $this->addQueryParams(
$uri,
array(
'code' => $auth_code->getCode(),
'scope' => $authorization->getScopeString(),
'state' => $state,
));
if ($client->getIsTrusted()) {
// NOTE: See T13099. We currently emit a "Content-Security-Policy"
// which includes a narrow "form-action". At the time of writing,
// Chrome applies "form-action" to redirects following form submission.
// This can lead to a situation where a user enters the OAuth workflow
// and is prompted for MFA. When they submit an MFA response, the form
// can redirect here, and Chrome will block the "Location" redirect.
// To avoid this, render an interstitial. We only actually need to do
// this in Chrome (but do it everywhere for consistency) and only need
// to do it if the request is a redirect after a form submission (but
// we can't tell if it is or not).
Javelin::initBehavior(
'redirect',
array(
'uri' => (string)$full_uri,
));
return $this->newDialog()
->setTitle(pht('Authenticate: %s', $name))
->appendParagraph(
pht(
'Authorization for "%s" confirmed, redirecting...',
phutil_tag('strong', array(), $name)))
->addCancelButton((string)$full_uri, pht('Continue'));
}
// TODO: It would be nice to give the user more options here, like
// reviewing permissions, canceling the authorization, or aborting
// the workflow.
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht('Authenticate: %s', $name))
->appendParagraph(
pht(
- 'This application ("%s") is authorized to use your Phabricator '.
+ 'This application ("%s") is authorized to use your %s '.
'credentials. Continue to complete the authentication workflow.',
- phutil_tag('strong', array(), $name)))
+ phutil_tag('strong', array(), $name),
+ PlatformSymbols::getPlatformServerName()))
->addCancelButton((string)$full_uri, pht('Continue to Application'));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
// Here, we're confirming authorization for the application.
if ($authorization) {
$missing_scope = array_diff_key($scope, $authorization->getScope());
} else {
$missing_scope = $scope;
}
$form = id(new AphrontFormView())
->addHiddenInput('client_id', $client_phid)
->addHiddenInput('redirect_uri', $redirect_uri)
->addHiddenInput('response_type', $response_type)
->addHiddenInput('state', $state)
->addHiddenInput('scope', $request->getStr('scope'))
->setUser($viewer);
$cancel_msg = pht('The user declined to authorize this application.');
$cancel_uri = $this->addQueryParams(
$uri,
array(
'error' => 'access_denied',
'error_description' => $cancel_msg,
));
$dialog = $this->newDialog()
->setShortTitle(pht('Authorize Access'))
->setTitle(pht('Authorize "%s"?', $name))
->setSubmitURI($request->getRequestURI()->getPath())
->setWidth(AphrontDialogView::WIDTH_FORM)
->appendParagraph(
pht(
'Do you want to authorize the external application "%s" to '.
- 'access your Phabricator account data, including your primary '.
+ 'access your %s account data, including your primary '.
'email address?',
- phutil_tag('strong', array(), $name)))
+ phutil_tag('strong', array(), $name),
+ PlatformSymbols::getPlatformServerName()))
->appendForm($form)
->addSubmitButton(pht('Authorize Access'))
->addCancelButton((string)$cancel_uri, pht('Do Not Authorize'));
if ($missing_scope) {
$dialog->appendParagraph(
pht(
'This application has requested these additional permissions. '.
'Authorizing it will grant it the permissions it requests:'));
foreach ($missing_scope as $scope_key => $ignored) {
// TODO: Once we introduce more scopes, explain them here.
}
}
$unknown_scope = array_diff_key($requested_scope, $scope);
if ($unknown_scope) {
$dialog->appendParagraph(
pht(
'This application also requested additional unrecognized '.
'permissions. These permissions may have existed in an older '.
- 'version of Phabricator, or may be from a future version of '.
- 'Phabricator. They will not be granted.'));
+ 'version of the software, or may be from a future version of '.
+ 'the software. They will not be granted.'));
$unknown_form = id(new AphrontFormView())
->setViewer($viewer)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Unknown Scope'))
->setValue(implode(', ', array_keys($unknown_scope)))
->setDisabled(true));
$dialog->appendForm($unknown_form);
}
return $dialog;
}
private function buildErrorResponse($code, $title, $message) {
$viewer = $this->getRequest()->getUser();
return $this->newDialog()
->setTitle(pht('OAuth: %s', $title))
->appendParagraph($message)
->appendParagraph(
pht('OAuth Error Code: %s', phutil_tag('tt', array(), $code)))
->addCancelButton('/', pht('Alas!'));
}
private function addQueryParams(PhutilURI $uri, array $params) {
$full_uri = clone $uri;
foreach ($params as $key => $value) {
if (strlen($value)) {
$full_uri->replaceQueryParam($key, $value);
}
}
return $full_uri;
}
}
diff --git a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientDisableController.php b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientDisableController.php
index 2ea9955365..334ef1b0aa 100644
--- a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientDisableController.php
+++ b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientDisableController.php
@@ -1,67 +1,67 @@
<?php
final class PhabricatorOAuthClientDisableController
extends PhabricatorOAuthClientController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$client = id(new PhabricatorOAuthServerClientQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$client) {
return new Aphront404Response();
}
$done_uri = $client->getViewURI();
$is_disable = !$client->getIsDisabled();
if ($request->isFormPost()) {
$xactions = array();
$xactions[] = id(new PhabricatorOAuthServerTransaction())
->setTransactionType(PhabricatorOAuthServerTransaction::TYPE_DISABLED)
->setNewValue((int)$is_disable);
$editor = id(new PhabricatorOAuthServerEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($client, $xactions);
return id(new AphrontRedirectResponse())->setURI($done_uri);
}
if ($is_disable) {
$title = pht('Disable OAuth Application');
$body = pht(
'Really disable the %s OAuth application? Users will no longer be '.
- 'able to authenticate against it, nor access Phabricator using '.
+ 'able to authenticate against it, nor access this server using '.
'tokens generated by this application.',
phutil_tag('strong', array(), $client->getName()));
$button = pht('Disable Application');
} else {
$title = pht('Enable OAuth Application');
$body = pht(
'Really enable the %s OAuth application? Users will be able to '.
'authenticate against it, and existing tokens will become usable '.
'again.',
phutil_tag('strong', array(), $client->getName()));
$button = pht('Enable Application');
}
return $this->newDialog()
->setTitle($title)
->appendParagraph($body)
->addCancelButton($done_uri)
->addSubmitButton($button);
}
}
diff --git a/src/applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php b/src/applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php
index 89a1cc0281..21fb891330 100644
--- a/src/applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php
+++ b/src/applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php
@@ -1,147 +1,147 @@
<?php
final class PhabricatorOAuthServerAuthorizationsSettingsPanel
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'oauthorizations';
}
public function getPanelName() {
return pht('OAuth Authorizations');
}
public function getPanelMenuIcon() {
return 'fa-exchange';
}
public function getPanelGroupKey() {
return PhabricatorSettingsLogsPanelGroup::PANELGROUPKEY;
}
public function isEnabled() {
return PhabricatorApplication::isClassInstalled(
'PhabricatorOAuthServerApplication');
}
public function processRequest(AphrontRequest $request) {
$viewer = $request->getUser();
// TODO: It would be nice to simply disable this panel, but we can't do
// viewer-based checks for enabled panels right now.
$app_class = 'PhabricatorOAuthServerApplication';
$installed = PhabricatorApplication::isClassInstalledForViewer(
$app_class,
$viewer);
if (!$installed) {
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht('OAuth Not Available'))
->appendParagraph(
pht('You do not have access to OAuth authorizations.'))
->addCancelButton('/settings/');
return id(new AphrontDialogResponse())->setDialog($dialog);
}
$authorizations = id(new PhabricatorOAuthClientAuthorizationQuery())
->setViewer($viewer)
->withUserPHIDs(array($viewer->getPHID()))
->execute();
$authorizations = mpull($authorizations, null, 'getID');
$panel_uri = $this->getPanelURI();
$revoke = $request->getInt('revoke');
if ($revoke) {
if (empty($authorizations[$revoke])) {
return new Aphront404Response();
}
if ($request->isFormPost()) {
$authorizations[$revoke]->delete();
return id(new AphrontRedirectResponse())->setURI($panel_uri);
}
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht('Revoke Authorization?'))
->appendParagraph(
pht(
- 'This application will no longer be able to access Phabricator '.
+ 'This application will no longer be able to access this server '.
'on your behalf.'))
->addSubmitButton(pht('Revoke Authorization'))
->addCancelButton($panel_uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
$highlight = $request->getInt('id');
$rows = array();
$rowc = array();
foreach ($authorizations as $authorization) {
if ($highlight == $authorization->getID()) {
$rowc[] = 'highlighted';
} else {
$rowc[] = null;
}
$button = javelin_tag(
'a',
array(
'href' => $this->getPanelURI('?revoke='.$authorization->getID()),
'class' => 'small button button-grey',
'sigil' => 'workflow',
),
pht('Revoke'));
$rows[] = array(
phutil_tag(
'a',
array(
'href' => $authorization->getClient()->getViewURI(),
),
$authorization->getClient()->getName()),
$authorization->getScopeString(),
phabricator_datetime($authorization->getDateCreated(), $viewer),
phabricator_datetime($authorization->getDateModified(), $viewer),
$button,
);
}
$table = new AphrontTableView($rows);
$table->setNoDataString(
pht("You haven't authorized any OAuth applications."));
$table->setRowClasses($rowc);
$table->setHeaders(
array(
pht('Application'),
pht('Scope'),
pht('Created'),
pht('Updated'),
null,
));
$table->setColumnClasses(
array(
'pri',
'wide',
'right',
'right',
'action',
));
$header = id(new PHUIHeaderView())
->setHeader(pht('OAuth Application Authorizations'));
$panel = id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->appendChild($table);
return $panel;
}
}
diff --git a/src/applications/oauthserver/query/PhabricatorOAuthClientAuthorizationQuery.php b/src/applications/oauthserver/query/PhabricatorOAuthClientAuthorizationQuery.php
index a746008f55..63a62e8cd9 100644
--- a/src/applications/oauthserver/query/PhabricatorOAuthClientAuthorizationQuery.php
+++ b/src/applications/oauthserver/query/PhabricatorOAuthClientAuthorizationQuery.php
@@ -1,89 +1,85 @@
<?php
final class PhabricatorOAuthClientAuthorizationQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $phids;
private $userPHIDs;
private $clientPHIDs;
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withUserPHIDs(array $phids) {
$this->userPHIDs = $phids;
return $this;
}
public function withClientPHIDs(array $phids) {
$this->clientPHIDs = $phids;
return $this;
}
public function newResultObject() {
return new PhabricatorOAuthClientAuthorization();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $authorizations) {
$client_phids = mpull($authorizations, 'getClientPHID');
$clients = id(new PhabricatorOAuthServerClientQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($client_phids)
->execute();
$clients = mpull($clients, null, 'getPHID');
foreach ($authorizations as $key => $authorization) {
$client = idx($clients, $authorization->getClientPHID());
if (!$client) {
$this->didRejectResult($authorization);
unset($authorizations[$key]);
continue;
}
$authorization->attachClient($client);
}
return $authorizations;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->userPHIDs !== null) {
$where[] = qsprintf(
$conn,
'userPHID IN (%Ls)',
$this->userPHIDs);
}
if ($this->clientPHIDs !== null) {
$where[] = qsprintf(
$conn,
'clientPHID IN (%Ls)',
$this->clientPHIDs);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorOAuthServerApplication';
}
}
diff --git a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php
index 9e5e4c3234..a80b972548 100644
--- a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php
+++ b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php
@@ -1,464 +1,460 @@
<?php
final class PhabricatorOwnersPackageQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $ownerPHIDs;
private $authorityPHIDs;
private $repositoryPHIDs;
private $paths;
private $statuses;
private $authorityModes;
private $controlMap = array();
private $controlResults;
private $needPaths;
/**
* Query owner PHIDs exactly. This does not expand authorities, so a user
* PHID will not match projects the user is a member of.
*/
public function withOwnerPHIDs(array $phids) {
$this->ownerPHIDs = $phids;
return $this;
}
/**
* Query owner authority. This will expand authorities, so a user PHID will
* match both packages they own directly and packages owned by a project they
* are a member of.
*/
public function withAuthorityPHIDs(array $phids) {
$this->authorityPHIDs = $phids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withRepositoryPHIDs(array $phids) {
$this->repositoryPHIDs = $phids;
return $this;
}
public function withPaths(array $paths) {
$this->paths = $paths;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function withControl($repository_phid, array $paths) {
if (empty($this->controlMap[$repository_phid])) {
$this->controlMap[$repository_phid] = array();
}
foreach ($paths as $path) {
$path = (string)$path;
$this->controlMap[$repository_phid][$path] = $path;
}
// We need to load paths to execute control queries.
$this->needPaths = true;
return $this;
}
public function withAuthorityModes(array $modes) {
$this->authorityModes = $modes;
return $this;
}
public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint(
new PhabricatorOwnersPackageNameNgrams(),
$ngrams);
}
public function needPaths($need_paths) {
$this->needPaths = $need_paths;
return $this;
}
public function newResultObject() {
return new PhabricatorOwnersPackage();
}
protected function willExecute() {
$this->controlResults = array();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $packages) {
$package_ids = mpull($packages, 'getID');
$owners = id(new PhabricatorOwnersOwner())->loadAllWhere(
'packageID IN (%Ld)',
$package_ids);
$owners = mgroup($owners, 'getPackageID');
foreach ($packages as $package) {
$package->attachOwners(idx($owners, $package->getID(), array()));
}
return $packages;
}
protected function didFilterPage(array $packages) {
$package_ids = mpull($packages, 'getID');
if ($this->needPaths) {
$paths = id(new PhabricatorOwnersPath())->loadAllWhere(
'packageID IN (%Ld)',
$package_ids);
$paths = mgroup($paths, 'getPackageID');
foreach ($packages as $package) {
$package->attachPaths(idx($paths, $package->getID(), array()));
}
}
if ($this->controlMap) {
foreach ($packages as $package) {
// If this package is archived, it's no longer a controlling package
// for any path. In particular, it can not force active packages with
// weak dominion to give up control.
if ($package->isArchived()) {
continue;
}
$this->controlResults[$package->getID()] = $package;
}
}
return $packages;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->shouldJoinOwnersTable()) {
$joins[] = qsprintf(
$conn,
'JOIN %T o ON o.packageID = p.id',
id(new PhabricatorOwnersOwner())->getTableName());
}
if ($this->shouldJoinPathTable()) {
$joins[] = qsprintf(
$conn,
'JOIN %T rpath ON rpath.packageID = p.id',
id(new PhabricatorOwnersPath())->getTableName());
}
return $joins;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'p.phid IN (%Ls)',
$this->phids);
}
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'p.id IN (%Ld)',
$this->ids);
}
if ($this->repositoryPHIDs !== null) {
$where[] = qsprintf(
$conn,
'rpath.repositoryPHID IN (%Ls)',
$this->repositoryPHIDs);
}
if ($this->authorityPHIDs !== null) {
$authority_phids = $this->expandAuthority($this->authorityPHIDs);
$where[] = qsprintf(
$conn,
'o.userPHID IN (%Ls)',
$authority_phids);
}
if ($this->ownerPHIDs !== null) {
$where[] = qsprintf(
$conn,
'o.userPHID IN (%Ls)',
$this->ownerPHIDs);
}
if ($this->paths !== null) {
$where[] = qsprintf(
$conn,
'rpath.pathIndex IN (%Ls)',
$this->getFragmentIndexesForPaths($this->paths));
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'p.status IN (%Ls)',
$this->statuses);
}
if ($this->controlMap) {
$clauses = array();
foreach ($this->controlMap as $repository_phid => $paths) {
$indexes = $this->getFragmentIndexesForPaths($paths);
$clauses[] = qsprintf(
$conn,
'(rpath.repositoryPHID = %s AND rpath.pathIndex IN (%Ls))',
$repository_phid,
$indexes);
}
$where[] = qsprintf($conn, '%LO', $clauses);
}
if ($this->authorityModes !== null) {
$where[] = qsprintf(
$conn,
'authorityMode IN (%Ls)',
$this->authorityModes);
}
return $where;
}
protected function shouldGroupQueryResultRows() {
if ($this->shouldJoinOwnersTable()) {
return true;
}
if ($this->shouldJoinPathTable()) {
return true;
}
return parent::shouldGroupQueryResultRows();
}
public function getBuiltinOrders() {
return array(
'name' => array(
'vector' => array('name'),
'name' => pht('Name'),
),
) + parent::getBuiltinOrders();
}
public function getOrderableColumns() {
return parent::getOrderableColumns() + array(
'name' => array(
'table' => $this->getPrimaryTableAlias(),
'column' => 'name',
'type' => 'string',
'unique' => true,
'reverse' => true,
),
);
}
protected function newPagingMapFromPartialObject($object) {
return array(
'id' => (int)$object->getID(),
'name' => $object->getName(),
);
}
public function getQueryApplicationClass() {
return 'PhabricatorOwnersApplication';
}
protected function getPrimaryTableAlias() {
return 'p';
}
private function shouldJoinOwnersTable() {
if ($this->ownerPHIDs !== null) {
return true;
}
if ($this->authorityPHIDs !== null) {
return true;
}
return false;
}
private function shouldJoinPathTable() {
if ($this->repositoryPHIDs !== null) {
return true;
}
if ($this->paths !== null) {
return true;
}
if ($this->controlMap) {
return true;
}
return false;
}
private function expandAuthority(array $phids) {
$projects = id(new PhabricatorProjectQuery())
->setViewer($this->getViewer())
->withMemberPHIDs($phids)
->execute();
$project_phids = mpull($projects, 'getPHID');
return array_fuse($phids) + array_fuse($project_phids);
}
private function getFragmentsForPaths(array $paths) {
$fragments = array();
foreach ($paths as $path) {
foreach (PhabricatorOwnersPackage::splitPath($path) as $fragment) {
$fragments[$fragment] = $fragment;
}
}
return $fragments;
}
private function getFragmentIndexesForPaths(array $paths) {
$indexes = array();
foreach ($this->getFragmentsForPaths($paths) as $fragment) {
$indexes[] = PhabricatorHash::digestForIndex($fragment);
}
return $indexes;
}
/* -( Path Control )------------------------------------------------------- */
/**
* Get a list of all packages which control a path or its parent directories,
* ordered from weakest to strongest.
*
* The first package has the most specific claim on the path; the last
* package has the most general claim. Multiple packages may have claims of
* equal strength, so this ordering is primarily one of usability and
* convenience.
*
* @return list<PhabricatorOwnersPackage> List of controlling packages.
*/
public function getControllingPackagesForPath(
$repository_phid,
$path,
$ignore_dominion = false) {
$path = (string)$path;
if (!isset($this->controlMap[$repository_phid][$path])) {
throw new PhutilInvalidStateException('withControl');
}
if ($this->controlResults === null) {
throw new PhutilInvalidStateException('execute');
}
$packages = $this->controlResults;
$weak_dominion = PhabricatorOwnersPackage::DOMINION_WEAK;
$path_fragments = PhabricatorOwnersPackage::splitPath($path);
$fragment_count = count($path_fragments);
$matches = array();
foreach ($packages as $package_id => $package) {
$best_match = null;
$include = false;
$repository_paths = $package->getPathsForRepository($repository_phid);
foreach ($repository_paths as $package_path) {
$strength = $package_path->getPathMatchStrength(
$path_fragments,
$fragment_count);
if ($strength > $best_match) {
$best_match = $strength;
$include = !$package_path->getExcluded();
}
}
if ($best_match && $include) {
if ($ignore_dominion) {
$is_weak = false;
} else {
$is_weak = ($package->getDominion() == $weak_dominion);
}
$matches[$package_id] = array(
'strength' => $best_match,
'weak' => $is_weak,
'package' => $package,
);
}
}
// At each strength level, drop weak packages if there are also strong
// packages of the same strength.
$strength_map = igroup($matches, 'strength');
foreach ($strength_map as $strength => $package_list) {
$any_strong = false;
foreach ($package_list as $package_id => $package) {
if (!$package['weak']) {
$any_strong = true;
break;
}
}
if ($any_strong) {
foreach ($package_list as $package_id => $package) {
if ($package['weak']) {
unset($matches[$package_id]);
}
}
}
}
$matches = isort($matches, 'strength');
$matches = array_reverse($matches);
$strongest = null;
foreach ($matches as $package_id => $match) {
if ($strongest === null) {
$strongest = $match['strength'];
}
if ($match['strength'] === $strongest) {
continue;
}
if ($match['weak']) {
unset($matches[$package_id]);
}
}
return array_values(ipull($matches, 'package'));
}
}
diff --git a/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php b/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php
index 728c3f42a8..26889c0e7a 100644
--- a/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php
+++ b/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php
@@ -1,178 +1,179 @@
<?php
final class PhabricatorOwnersPackageSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
return pht('Owners Packages');
}
public function getApplicationClassName() {
return 'PhabricatorOwnersApplication';
}
public function newQuery() {
return new PhabricatorOwnersPackageQuery();
}
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Authority'))
->setKey('authorityPHIDs')
->setAliases(array('authority', 'authorities'))
->setConduitKey('owners')
->setDescription(
pht('Search for packages with specific owners.'))
->setDatasource(new PhabricatorProjectOrUserDatasource()),
id(new PhabricatorSearchTextField())
->setLabel(pht('Name Contains'))
->setKey('name')
->setDescription(pht('Search for packages by name substrings.')),
id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Repositories'))
->setKey('repositoryPHIDs')
->setConduitKey('repositories')
->setAliases(array('repository', 'repositories'))
->setDescription(
pht('Search for packages by included repositories.'))
->setDatasource(new DiffusionRepositoryDatasource()),
id(new PhabricatorSearchStringListField())
->setLabel(pht('Paths'))
->setKey('paths')
->setAliases(array('path'))
->setDescription(
pht('Search for packages affecting specific paths.')),
id(new PhabricatorSearchCheckboxesField())
->setKey('statuses')
->setLabel(pht('Status'))
->setDescription(
pht('Search for active or archived packages.'))
->setOptions(
id(new PhabricatorOwnersPackage())
->getStatusNameMap()),
);
}
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
if ($map['authorityPHIDs']) {
$query->withAuthorityPHIDs($map['authorityPHIDs']);
}
if ($map['repositoryPHIDs']) {
$query->withRepositoryPHIDs($map['repositoryPHIDs']);
}
if ($map['paths']) {
$query->withPaths($map['paths']);
}
if ($map['statuses']) {
$query->withStatuses($map['statuses']);
}
if (strlen($map['name'])) {
$query->withNameNgrams($map['name']);
}
return $query;
}
protected function getURI($path) {
return '/owners/'.$path;
}
protected function getBuiltinQueryNames() {
$names = array();
if ($this->requireViewer()->isLoggedIn()) {
$names['authority'] = pht('Owned');
}
$names += array(
'active' => pht('Active Packages'),
'all' => pht('All Packages'),
);
return $names;
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
switch ($query_key) {
case 'all':
return $query;
case 'active':
return $query->setParameter(
'statuses',
array(
PhabricatorOwnersPackage::STATUS_ACTIVE,
));
case 'authority':
return $query->setParameter(
'authorityPHIDs',
array($this->requireViewer()->getPHID()));
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
protected function renderResultList(
array $packages,
PhabricatorSavedQuery $query,
array $handles) {
assert_instances_of($packages, 'PhabricatorOwnersPackage');
$viewer = $this->requireViewer();
$list = id(new PHUIObjectItemListView())
->setUser($viewer);
foreach ($packages as $package) {
$id = $package->getID();
$item = id(new PHUIObjectItemView())
->setObject($package)
->setObjectName($package->getMonogram())
->setHeader($package->getName())
->setHref($package->getURI());
if ($package->isArchived()) {
$item->setDisabled(true);
}
$list->addItem($item);
}
$result = new PhabricatorApplicationSearchResultView();
$result->setObjectList($list);
$result->setNoDataString(pht('No packages found.'));
return $result;
}
protected function getNewUserBody() {
$create_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('Create a Package'))
->setHref('/owners/edit/')
->setColor(PHUIButtonView::GREEN);
$icon = $this->getApplication()->getIcon();
$app_name = $this->getApplication()->getName();
$view = id(new PHUIBigInfoView())
->setIcon($icon)
->setTitle(pht('Welcome to %s', $app_name))
->setDescription(
- pht('Group sections of a codebase into packages for re-use in other '.
- 'areas of Phabricator, like Herald rules.'))
+ pht(
+ 'Group sections of a codebase into packages for re-use in other '.
+ 'applications, like Herald rules.'))
->addAction($create_button);
return $view;
}
}
diff --git a/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php b/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php
index d219abfc1e..8c581215e9 100644
--- a/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php
+++ b/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php
@@ -1,194 +1,194 @@
<?php
final class PhabricatorOwnersPackageTestCase extends PhabricatorTestCase {
public function testFindLongestPathsPerPackage() {
$rows = array(
array(
'id' => 1,
'excluded' => 0,
'dominion' => PhabricatorOwnersPackage::DOMINION_STRONG,
'path' => 'src/',
),
array(
'id' => 1,
'excluded' => 1,
'dominion' => PhabricatorOwnersPackage::DOMINION_STRONG,
- 'path' => 'src/releeph/',
+ 'path' => 'src/example/',
),
array(
'id' => 2,
'excluded' => 0,
'dominion' => PhabricatorOwnersPackage::DOMINION_STRONG,
- 'path' => 'src/releeph/',
+ 'path' => 'src/example/',
),
);
$paths = array(
- 'src/' => array('src/a.php' => true, 'src/releeph/b.php' => true),
- 'src/releeph/' => array('src/releeph/b.php' => true),
+ 'src/' => array('src/a.php' => true, 'src/example/b.php' => true),
+ 'src/example/' => array('src/example/b.php' => true),
);
$this->assertEqual(
array(
1 => strlen('src/'),
- 2 => strlen('src/releeph/'),
+ 2 => strlen('src/example/'),
),
PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths));
$paths = array(
- 'src/' => array('src/releeph/b.php' => true),
- 'src/releeph/' => array('src/releeph/b.php' => true),
+ 'src/' => array('src/example/b.php' => true),
+ 'src/example/' => array('src/example/b.php' => true),
);
$this->assertEqual(
array(
- 2 => strlen('src/releeph/'),
+ 2 => strlen('src/example/'),
),
PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths));
// Test packages with weak dominion. Here, only package #2 should own the
// path. Package #1's claim is ceded to Package #2 because it uses weak
// rules. Package #2 gets the claim even though it also has weak rules
// because there is no more-specific package.
$rows = array(
array(
'id' => 1,
'excluded' => 0,
'dominion' => PhabricatorOwnersPackage::DOMINION_WEAK,
'path' => 'src/',
),
array(
'id' => 2,
'excluded' => 0,
'dominion' => PhabricatorOwnersPackage::DOMINION_WEAK,
'path' => 'src/applications/',
),
);
$pvalue = array('src/applications/main/main.c' => true);
$paths = array(
'src/' => $pvalue,
'src/applications/' => $pvalue,
);
$this->assertEqual(
array(
2 => strlen('src/applications/'),
),
PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths));
// Now, add a more specific path to Package #1. This tests nested ownership
// in packages with weak dominion rules. This time, Package #1 should end
// up back on top, with Package #2 ceding control to its more specific
// path.
$rows[] = array(
'id' => 1,
'excluded' => 0,
'dominion' => PhabricatorOwnersPackage::DOMINION_WEAK,
'path' => 'src/applications/main/',
);
$paths['src/applications/main/'] = $pvalue;
$this->assertEqual(
array(
1 => strlen('src/applications/main/'),
),
PhabricatorOwnersPackage::findLongestPathsPerPackage($rows, $paths));
// Test cases where multiple packages own the same path, with various
// dominion rules.
$main_c = 'src/applications/main/main.c';
$rules = array(
// All claims strong.
array(
PhabricatorOwnersPackage::DOMINION_STRONG,
PhabricatorOwnersPackage::DOMINION_STRONG,
PhabricatorOwnersPackage::DOMINION_STRONG,
),
// All claims weak.
array(
PhabricatorOwnersPackage::DOMINION_WEAK,
PhabricatorOwnersPackage::DOMINION_WEAK,
PhabricatorOwnersPackage::DOMINION_WEAK,
),
// Mixture of strong and weak claims, strong first.
array(
PhabricatorOwnersPackage::DOMINION_STRONG,
PhabricatorOwnersPackage::DOMINION_STRONG,
PhabricatorOwnersPackage::DOMINION_WEAK,
),
// Mixture of strong and weak claims, weak first.
array(
PhabricatorOwnersPackage::DOMINION_WEAK,
PhabricatorOwnersPackage::DOMINION_STRONG,
PhabricatorOwnersPackage::DOMINION_STRONG,
),
);
foreach ($rules as $rule_idx => $rule) {
$rows = array(
array(
'id' => 1,
'excluded' => 0,
'dominion' => $rule[0],
'path' => $main_c,
),
array(
'id' => 2,
'excluded' => 0,
'dominion' => $rule[1],
'path' => $main_c,
),
array(
'id' => 3,
'excluded' => 0,
'dominion' => $rule[2],
'path' => $main_c,
),
);
$paths = array(
$main_c => $pvalue,
);
// If one or more packages have strong dominion, they should own the
// path. If not, all the packages with weak dominion should own the
// path.
$strong = array();
$weak = array();
foreach ($rule as $idx => $dominion) {
if ($dominion == PhabricatorOwnersPackage::DOMINION_STRONG) {
$strong[] = $idx + 1;
} else {
$weak[] = $idx + 1;
}
}
if ($strong) {
$expect = $strong;
} else {
$expect = $weak;
}
$expect = array_fill_keys($expect, strlen($main_c));
$actual = PhabricatorOwnersPackage::findLongestPathsPerPackage(
$rows,
$paths);
ksort($actual);
$this->assertEqual(
$expect,
$actual,
pht('Ruleset "%s" for Identical Ownership', $rule_idx));
}
}
}
diff --git a/src/applications/packages/query/PhabricatorPackagesPackageQuery.php b/src/applications/packages/query/PhabricatorPackagesPackageQuery.php
index 67cd3954c9..c97e3b244c 100644
--- a/src/applications/packages/query/PhabricatorPackagesPackageQuery.php
+++ b/src/applications/packages/query/PhabricatorPackagesPackageQuery.php
@@ -1,135 +1,131 @@
<?php
final class PhabricatorPackagesPackageQuery
extends PhabricatorPackagesQuery {
private $ids;
private $phids;
private $publisherPHIDs;
private $packageKeys;
private $fullKeys;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withPublisherPHIDs(array $phids) {
$this->publisherPHIDs = $phids;
return $this;
}
public function withPackageKeys(array $keys) {
$this->packageKeys = $keys;
return $this;
}
public function withFullKeys(array $keys) {
$this->fullKeys = $keys;
return $this;
}
public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint(
new PhabricatorPackagesPackageNameNgrams(),
$ngrams);
}
public function newResultObject() {
return new PhabricatorPackagesPackage();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'p.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'p.phid IN (%Ls)',
$this->phids);
}
if ($this->publisherPHIDs !== null) {
$where[] = qsprintf(
$conn,
'p.publisherPHID IN (%Ls)',
$this->publisherPHIDs);
}
if ($this->packageKeys !== null) {
$where[] = qsprintf(
$conn,
'p.packageKey IN (%Ls)',
$this->packageKeys);
}
if ($this->fullKeys !== null) {
$parts = $this->buildFullKeyClauseParts($conn, $this->fullKeys);
$where[] = qsprintf($conn, '%Q', $parts);
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
$join_publisher = ($this->fullKeys !== null);
if ($join_publisher) {
$publisher_table = new PhabricatorPackagesPublisher();
$joins[] = qsprintf(
$conn,
'JOIN %T u ON u.phid = p.publisherPHID',
$publisher_table->getTableName());
}
return $joins;
}
protected function willFilterPage(array $packages) {
$publisher_phids = mpull($packages, 'getPublisherPHID');
$publishers = id(new PhabricatorPackagesPublisherQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($publisher_phids)
->execute();
$publishers = mpull($publishers, null, 'getPHID');
foreach ($packages as $key => $package) {
$publisher = idx($publishers, $package->getPublisherPHID());
if (!$publisher) {
unset($packages[$key]);
$this->didRejectResult($package);
continue;
}
$package->attachPublisher($publisher);
}
return $packages;
}
protected function getPrimaryTableAlias() {
return 'p';
}
}
diff --git a/src/applications/packages/query/PhabricatorPackagesPublisherQuery.php b/src/applications/packages/query/PhabricatorPackagesPublisherQuery.php
index d46535c20d..7d31e5fec6 100644
--- a/src/applications/packages/query/PhabricatorPackagesPublisherQuery.php
+++ b/src/applications/packages/query/PhabricatorPackagesPublisherQuery.php
@@ -1,70 +1,66 @@
<?php
final class PhabricatorPackagesPublisherQuery
extends PhabricatorPackagesQuery {
private $ids;
private $phids;
private $publisherKeys;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withPublisherKeys(array $keys) {
$this->publisherKeys = $keys;
return $this;
}
public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint(
new PhabricatorPackagesPublisherNameNgrams(),
$ngrams);
}
public function newResultObject() {
return new PhabricatorPackagesPublisher();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'u.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'u.phid IN (%Ls)',
$this->phids);
}
if ($this->publisherKeys !== null) {
$where[] = qsprintf(
$conn,
'u.publisherKey IN (%Ls)',
$this->publisherKeys);
}
return $where;
}
protected function getPrimaryTableAlias() {
return 'u';
}
}
diff --git a/src/applications/packages/query/PhabricatorPackagesVersionQuery.php b/src/applications/packages/query/PhabricatorPackagesVersionQuery.php
index 6f417e2f77..447907c4a0 100644
--- a/src/applications/packages/query/PhabricatorPackagesVersionQuery.php
+++ b/src/applications/packages/query/PhabricatorPackagesVersionQuery.php
@@ -1,147 +1,143 @@
<?php
final class PhabricatorPackagesVersionQuery
extends PhabricatorPackagesQuery {
private $ids;
private $phids;
private $packagePHIDs;
private $fullKeys;
private $names;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withPackagePHIDs(array $phids) {
$this->packagePHIDs = $phids;
return $this;
}
public function withFullKeys(array $keys) {
$this->fullKeys = $keys;
return $this;
}
public function withNames(array $names) {
$this->names = $names;
return $this;
}
public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint(
new PhabricatorPackagesVersionNameNgrams(),
$ngrams);
}
public function newResultObject() {
return new PhabricatorPackagesVersion();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'v.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'v.phid IN (%Ls)',
$this->phids);
}
if ($this->packagePHIDs !== null) {
$where[] = qsprintf(
$conn,
'v.packagePHID IN (%Ls)',
$this->packagePHIDs);
}
if ($this->names !== null) {
$where[] = qsprintf(
$conn,
'v.name IN (%Ls)',
$this->names);
}
if ($this->fullKeys !== null) {
$parts = $this->buildFullKeyClauseParts($conn, $this->fullKeys);
$where[] = qsprintf($conn, '%Q', $parts);
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
$join_publisher = ($this->fullKeys !== null);
$join_package = ($this->fullKeys !== null) || $join_publisher;
if ($join_package) {
$package_table = new PhabricatorPackagesPackage();
$joins[] = qsprintf(
$conn,
'JOIN %T p ON v.packagePHID = p.phid',
$package_table->getTableName());
}
if ($join_publisher) {
$publisher_table = new PhabricatorPackagesPublisher();
$joins[] = qsprintf(
$conn,
'JOIN %T u ON u.phid = p.publisherPHID',
$publisher_table->getTableName());
}
return $joins;
}
protected function willFilterPage(array $versions) {
$package_phids = mpull($versions, 'getPackagePHID');
$packages = id(new PhabricatorPackagesPackageQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($package_phids)
->execute();
$packages = mpull($packages, null, 'getPHID');
foreach ($versions as $key => $version) {
$package = idx($packages, $version->getPackagePHID());
if (!$package) {
unset($versions[$key]);
$this->didRejectResult($version);
continue;
}
$version->attachPackage($package);
}
return $versions;
}
protected function getPrimaryTableAlias() {
return 'v';
}
}
diff --git a/src/applications/passphrase/query/PassphraseCredentialQuery.php b/src/applications/passphrase/query/PassphraseCredentialQuery.php
index 2518ad44e5..98ae9429a8 100644
--- a/src/applications/passphrase/query/PassphraseCredentialQuery.php
+++ b/src/applications/passphrase/query/PassphraseCredentialQuery.php
@@ -1,169 +1,165 @@
<?php
final class PassphraseCredentialQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $credentialTypes;
private $providesTypes;
private $isDestroyed;
private $allowConduit;
private $nameContains;
private $needSecrets;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withCredentialTypes(array $credential_types) {
$this->credentialTypes = $credential_types;
return $this;
}
public function withProvidesTypes(array $provides_types) {
$this->providesTypes = $provides_types;
return $this;
}
public function withIsDestroyed($destroyed) {
$this->isDestroyed = $destroyed;
return $this;
}
public function withAllowConduit($allow_conduit) {
$this->allowConduit = $allow_conduit;
return $this;
}
public function withNameContains($name_contains) {
$this->nameContains = $name_contains;
return $this;
}
public function needSecrets($need_secrets) {
$this->needSecrets = $need_secrets;
return $this;
}
public function newResultObject() {
return new PassphraseCredential();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $page) {
if ($this->needSecrets) {
$secret_ids = mpull($page, 'getSecretID');
$secret_ids = array_filter($secret_ids);
$secrets = array();
if ($secret_ids) {
$secret_objects = id(new PassphraseSecret())->loadAllWhere(
'id IN (%Ld)',
$secret_ids);
foreach ($secret_objects as $secret) {
$secret_data = $secret->getSecretData();
$secrets[$secret->getID()] = new PhutilOpaqueEnvelope($secret_data);
}
}
foreach ($page as $key => $credential) {
$secret_id = $credential->getSecretID();
if (!$secret_id) {
$credential->attachSecret(null);
} else if (isset($secrets[$secret_id])) {
$credential->attachSecret($secrets[$secret_id]);
} else {
unset($page[$key]);
}
}
}
foreach ($page as $key => $credential) {
$type = PassphraseCredentialType::getTypeByConstant(
$credential->getCredentialType());
if (!$type) {
unset($page[$key]);
continue;
}
$credential->attachImplementation(clone $type);
}
return $page;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'c.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'c.phid IN (%Ls)',
$this->phids);
}
if ($this->credentialTypes !== null) {
$where[] = qsprintf(
$conn,
'c.credentialType in (%Ls)',
$this->credentialTypes);
}
if ($this->providesTypes !== null) {
$where[] = qsprintf(
$conn,
'c.providesType IN (%Ls)',
$this->providesTypes);
}
if ($this->isDestroyed !== null) {
$where[] = qsprintf(
$conn,
'c.isDestroyed = %d',
(int)$this->isDestroyed);
}
if ($this->allowConduit !== null) {
$where[] = qsprintf(
$conn,
'c.allowConduit = %d',
(int)$this->allowConduit);
}
- if (strlen($this->nameContains)) {
+ if (phutil_nonempty_string($this->nameContains)) {
$where[] = qsprintf(
$conn,
'LOWER(c.name) LIKE %~',
phutil_utf8_strtolower($this->nameContains));
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorPassphraseApplication';
}
protected function getPrimaryTableAlias() {
return 'c';
}
}
diff --git a/src/applications/passphrase/query/PassphraseCredentialSearchEngine.php b/src/applications/passphrase/query/PassphraseCredentialSearchEngine.php
index ac4cbec9bf..971094afc4 100644
--- a/src/applications/passphrase/query/PassphraseCredentialSearchEngine.php
+++ b/src/applications/passphrase/query/PassphraseCredentialSearchEngine.php
@@ -1,134 +1,133 @@
<?php
final class PassphraseCredentialSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
return pht('Passphrase Credentials');
}
public function getApplicationClassName() {
return 'PhabricatorPassphraseApplication';
}
public function newQuery() {
return new PassphraseCredentialQuery();
}
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorSearchThreeStateField())
->setLabel(pht('Status'))
->setKey('isDestroyed')
->setOptions(
pht('Show All'),
pht('Show Only Destroyed Credentials'),
pht('Show Only Active Credentials')),
id(new PhabricatorSearchTextField())
->setLabel(pht('Name Contains'))
->setKey('name'),
);
}
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
if ($map['isDestroyed'] !== null) {
$query->withIsDestroyed($map['isDestroyed']);
}
if (strlen($map['name'])) {
$query->withNameContains($map['name']);
}
return $query;
}
protected function getURI($path) {
return '/passphrase/'.$path;
}
protected function getBuiltinQueryNames() {
return array(
'active' => pht('Active Credentials'),
'all' => pht('All Credentials'),
);
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
switch ($query_key) {
case 'all':
return $query;
case 'active':
return $query->setParameter('isDestroyed', false);
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
protected function renderResultList(
array $credentials,
PhabricatorSavedQuery $query,
array $handles) {
assert_instances_of($credentials, 'PassphraseCredential');
$viewer = $this->requireViewer();
$list = new PHUIObjectItemListView();
$list->setUser($viewer);
foreach ($credentials as $credential) {
$item = id(new PHUIObjectItemView())
->setObjectName('K'.$credential->getID())
->setHeader($credential->getName())
->setHref('/K'.$credential->getID())
->setObject($credential);
$item->addAttribute(
pht('Login: %s', $credential->getUsername()));
if ($credential->getIsDestroyed()) {
$item->addIcon('fa-ban', pht('Destroyed'));
$item->setDisabled(true);
}
$type = PassphraseCredentialType::getTypeByConstant(
$credential->getCredentialType());
if ($type) {
$item->addIcon('fa-wrench', $type->getCredentialTypeName());
}
$list->addItem($item);
}
$result = new PhabricatorApplicationSearchResultView();
$result->setObjectList($list);
$result->setNoDataString(pht('No credentials found.'));
return $result;
}
protected function getNewUserBody() {
$create_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('Create a Credential'))
->setHref('/passphrase/create/')
->setColor(PHUIButtonView::GREEN);
$icon = $this->getApplication()->getIcon();
$app_name = $this->getApplication()->getName();
$view = id(new PHUIBigInfoView())
->setIcon($icon)
->setTitle(pht('Welcome to %s', $app_name))
->setDescription(
- pht('Credential management for re-use in other areas of Phabricator '.
- 'or general storage of shared secrets.'))
+ pht('Credential management and general storage of shared secrets.'))
->addAction($create_button);
return $view;
}
}
diff --git a/src/applications/paste/query/PhabricatorPasteQuery.php b/src/applications/paste/query/PhabricatorPasteQuery.php
index d90ef3b1d2..e841bff300 100644
--- a/src/applications/paste/query/PhabricatorPasteQuery.php
+++ b/src/applications/paste/query/PhabricatorPasteQuery.php
@@ -1,400 +1,396 @@
<?php
final class PhabricatorPasteQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $authorPHIDs;
private $parentPHIDs;
private $needContent;
private $needRawContent;
private $needSnippets;
private $languages;
private $includeNoLanguage;
private $dateCreatedAfter;
private $dateCreatedBefore;
private $statuses;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withAuthorPHIDs(array $phids) {
$this->authorPHIDs = $phids;
return $this;
}
public function withParentPHIDs(array $phids) {
$this->parentPHIDs = $phids;
return $this;
}
public function needContent($need_content) {
$this->needContent = $need_content;
return $this;
}
public function needRawContent($need_raw_content) {
$this->needRawContent = $need_raw_content;
return $this;
}
public function needSnippets($need_snippets) {
$this->needSnippets = $need_snippets;
return $this;
}
public function withLanguages(array $languages) {
$this->includeNoLanguage = false;
foreach ($languages as $key => $language) {
if ($language === null) {
$languages[$key] = '';
continue;
}
}
$this->languages = $languages;
return $this;
}
public function withDateCreatedBefore($date_created_before) {
$this->dateCreatedBefore = $date_created_before;
return $this;
}
public function withDateCreatedAfter($date_created_after) {
$this->dateCreatedAfter = $date_created_after;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function newResultObject() {
return new PhabricatorPaste();
}
- protected function loadPage() {
- return $this->loadStandardPage(new PhabricatorPaste());
- }
-
protected function didFilterPage(array $pastes) {
if ($this->needRawContent) {
$pastes = $this->loadRawContent($pastes);
}
if ($this->needContent) {
$pastes = $this->loadContent($pastes);
}
if ($this->needSnippets) {
$pastes = $this->loadSnippets($pastes);
}
return $pastes;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'paste.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'paste.phid IN (%Ls)',
$this->phids);
}
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'paste.authorPHID IN (%Ls)',
$this->authorPHIDs);
}
if ($this->parentPHIDs !== null) {
$where[] = qsprintf(
$conn,
'paste.parentPHID IN (%Ls)',
$this->parentPHIDs);
}
if ($this->languages !== null) {
$where[] = qsprintf(
$conn,
'paste.language IN (%Ls)',
$this->languages);
}
if ($this->dateCreatedAfter !== null) {
$where[] = qsprintf(
$conn,
'paste.dateCreated >= %d',
$this->dateCreatedAfter);
}
if ($this->dateCreatedBefore !== null) {
$where[] = qsprintf(
$conn,
'paste.dateCreated <= %d',
$this->dateCreatedBefore);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'paste.status IN (%Ls)',
$this->statuses);
}
return $where;
}
protected function getPrimaryTableAlias() {
return 'paste';
}
private function getContentCacheKey(PhabricatorPaste $paste) {
return implode(
':',
array(
'P'.$paste->getID(),
$paste->getFilePHID(),
$paste->getLanguage(),
PhabricatorHash::digestForIndex($paste->getTitle()),
));
}
private function getSnippetCacheKey(PhabricatorPaste $paste) {
return implode(
':',
array(
'P'.$paste->getID(),
$paste->getFilePHID(),
$paste->getLanguage(),
'snippet',
'v2.1',
PhabricatorHash::digestForIndex($paste->getTitle()),
));
}
private function loadRawContent(array $pastes) {
$file_phids = mpull($pastes, 'getFilePHID');
$files = id(new PhabricatorFileQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($file_phids)
->execute();
$files = mpull($files, null, 'getPHID');
foreach ($pastes as $key => $paste) {
$file = idx($files, $paste->getFilePHID());
if (!$file) {
unset($pastes[$key]);
continue;
}
try {
$paste->attachRawContent($file->loadFileData());
} catch (Exception $ex) {
// We can hit various sorts of file storage issues here. Just drop the
// paste if the file is dead.
unset($pastes[$key]);
continue;
}
}
return $pastes;
}
private function loadContent(array $pastes) {
$cache = new PhabricatorKeyValueDatabaseCache();
$cache = new PhutilKeyValueCacheProfiler($cache);
$cache->setProfiler(PhutilServiceProfiler::getInstance());
$keys = array();
foreach ($pastes as $paste) {
$keys[] = $this->getContentCacheKey($paste);
}
$caches = $cache->getKeys($keys);
$need_raw = array();
$have_cache = array();
foreach ($pastes as $paste) {
$key = $this->getContentCacheKey($paste);
if (isset($caches[$key])) {
$paste->attachContent(phutil_safe_html($caches[$key]));
$have_cache[$paste->getPHID()] = true;
} else {
$need_raw[$key] = $paste;
}
}
if (!$need_raw) {
return $pastes;
}
$write_data = array();
$have_raw = $this->loadRawContent($need_raw);
$have_raw = mpull($have_raw, null, 'getPHID');
foreach ($pastes as $key => $paste) {
$paste_phid = $paste->getPHID();
if (isset($have_cache[$paste_phid])) {
continue;
}
if (empty($have_raw[$paste_phid])) {
unset($pastes[$key]);
continue;
}
$content = $this->buildContent($paste);
$paste->attachContent($content);
$write_data[$this->getContentCacheKey($paste)] = (string)$content;
}
if ($write_data) {
$cache->setKeys($write_data);
}
return $pastes;
}
private function loadSnippets(array $pastes) {
$cache = new PhabricatorKeyValueDatabaseCache();
$cache = new PhutilKeyValueCacheProfiler($cache);
$cache->setProfiler(PhutilServiceProfiler::getInstance());
$keys = array();
foreach ($pastes as $paste) {
$keys[] = $this->getSnippetCacheKey($paste);
}
$caches = $cache->getKeys($keys);
$need_raw = array();
$have_cache = array();
foreach ($pastes as $paste) {
$key = $this->getSnippetCacheKey($paste);
if (isset($caches[$key])) {
$snippet_data = phutil_json_decode($caches[$key]);
$snippet = new PhabricatorPasteSnippet(
phutil_safe_html($snippet_data['content']),
$snippet_data['type'],
$snippet_data['contentLineCount']);
$paste->attachSnippet($snippet);
$have_cache[$paste->getPHID()] = true;
} else {
$need_raw[$key] = $paste;
}
}
if (!$need_raw) {
return $pastes;
}
$write_data = array();
$have_raw = $this->loadRawContent($need_raw);
$have_raw = mpull($have_raw, null, 'getPHID');
foreach ($pastes as $key => $paste) {
$paste_phid = $paste->getPHID();
if (isset($have_cache[$paste_phid])) {
continue;
}
if (empty($have_raw[$paste_phid])) {
unset($pastes[$key]);
continue;
}
$snippet = $this->buildSnippet($paste);
$paste->attachSnippet($snippet);
$snippet_data = array(
'content' => (string)$snippet->getContent(),
'type' => (string)$snippet->getType(),
'contentLineCount' => $snippet->getContentLineCount(),
);
$write_data[$this->getSnippetCacheKey($paste)] = phutil_json_encode(
$snippet_data);
}
if ($write_data) {
$cache->setKeys($write_data);
}
return $pastes;
}
private function buildContent(PhabricatorPaste $paste) {
return $this->highlightSource(
$paste->getRawContent(),
$paste->getTitle(),
$paste->getLanguage());
}
private function buildSnippet(PhabricatorPaste $paste) {
$snippet_type = PhabricatorPasteSnippet::FULL;
$snippet = $paste->getRawContent();
$lines = phutil_split_lines($snippet);
$line_count = count($lines);
if (strlen($snippet) > 1024) {
$snippet_type = PhabricatorPasteSnippet::FIRST_BYTES;
$snippet = id(new PhutilUTF8StringTruncator())
->setMaximumBytes(1024)
->setTerminator('')
->truncateString($snippet);
}
if ($line_count > 5) {
$snippet_type = PhabricatorPasteSnippet::FIRST_LINES;
$snippet = implode('', array_slice($lines, 0, 5));
}
return new PhabricatorPasteSnippet(
$this->highlightSource(
$snippet,
$paste->getTitle(),
$paste->getLanguage()),
$snippet_type,
$line_count);
}
private function highlightSource($source, $title, $language) {
if (empty($language)) {
return PhabricatorSyntaxHighlighter::highlightWithFilename(
$title,
$source);
} else {
return PhabricatorSyntaxHighlighter::highlightWithLanguage(
$language,
$source);
}
}
public function getQueryApplicationClass() {
return 'PhabricatorPasteApplication';
}
}
diff --git a/src/applications/paste/xaction/PhabricatorPasteContentTransaction.php b/src/applications/paste/xaction/PhabricatorPasteContentTransaction.php
index 8df9ad8057..00c01b0fcb 100644
--- a/src/applications/paste/xaction/PhabricatorPasteContentTransaction.php
+++ b/src/applications/paste/xaction/PhabricatorPasteContentTransaction.php
@@ -1,123 +1,138 @@
<?php
final class PhabricatorPasteContentTransaction
extends PhabricatorPasteTransactionType {
const TRANSACTIONTYPE = 'paste.create';
+ private $filePHID;
+
public function generateOldValue($object) {
return $object->getFilePHID();
}
public function applyInternalEffects($object, $value) {
$object->setFilePHID($value);
}
public function extractFilePHIDs($object, $value) {
- return array($value);
+ $file_phid = $this->getFilePHID($object, $value);
+ return array($file_phid);
}
public function validateTransactions($object, array $xactions) {
if ($object->getFilePHID() || $xactions) {
return array();
}
$error = $this->newError(
pht('Required'),
pht('You must provide content to create a paste.'));
$error->setIsMissingFieldError(true);
return array($error);
}
public function generateNewValue($object, $value) {
+ return $this->getFilePHID($object, $value);
+ }
+
+ private function getFilePHID($object, $value) {
+ if ($this->filePHID === null) {
+ $this->filePHID = $this->newFilePHID($object, $value);
+ }
+
+ return $this->filePHID;
+ }
+
+ private function newFilePHID($object, $value) {
// If this transaction does not really change the paste content, return
// the current file PHID so this transaction no-ops.
$old_content = $object->getRawContent();
if ($value === $old_content) {
$file_phid = $object->getFilePHID();
if ($file_phid) {
return $file_phid;
}
}
$editor = $this->getEditor();
$actor = $editor->getActor();
$file = $this->newFileForPaste($actor, $value);
return $file->getPHID();
}
private function newFileForPaste(PhabricatorUser $actor, $data) {
$editor = $this->getEditor();
$file_name = $editor->getNewPasteTitle();
if (!strlen($file_name)) {
$file_name = 'raw-paste-data.txt';
}
return PhabricatorFile::newFromFileData(
$data,
array(
'name' => $file_name,
'mime-type' => 'text/plain; charset=utf-8',
'authorPHID' => $actor->getPHID(),
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
));
}
public function getIcon() {
return 'fa-plus';
}
public function getTitle() {
return pht(
'%s edited the content of this paste.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s edited %s.',
$this->renderAuthor(),
$this->renderObject());
}
public function hasChangeDetailView() {
return true;
}
public function getMailDiffSectionHeader() {
return pht('CHANGES TO PASTE CONTENT');
}
public function newChangeDetailView() {
$viewer = $this->getViewer();
$old = $this->getOldValue();
$new = $this->getNewValue();
$files = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($old, $new))
->execute();
$files = mpull($files, null, 'getPHID');
$old_text = '';
if (idx($files, $old)) {
$old_text = $files[$old]->loadFileData();
}
$new_text = '';
if (idx($files, $new)) {
$new_text = $files[$new]->loadFileData();
}
return id(new PhabricatorApplicationTransactionTextDiffDetailView())
->setViewer($viewer)
->setOldText($old_text)
->setNewText($new_text);
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleApproveController.php b/src/applications/people/controller/PhabricatorPeopleApproveController.php
index af08a6fbdc..a89dc85322 100644
--- a/src/applications/people/controller/PhabricatorPeopleApproveController.php
+++ b/src/applications/people/controller/PhabricatorPeopleApproveController.php
@@ -1,60 +1,60 @@
<?php
final class PhabricatorPeopleApproveController
extends PhabricatorPeopleController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->executeOne();
if (!$user) {
return new Aphront404Response();
}
$via = $request->getURIData('via');
switch ($via) {
case 'profile':
$done_uri = urisprintf('/people/manage/%d/', $user->getID());
break;
default:
$done_uri = $this->getApplicationURI('query/approval/');
break;
}
if ($user->getIsApproved()) {
return $this->newDialog()
->setTitle(pht('Already Approved'))
->appendChild(pht('This user has already been approved.'))
->addCancelButton($done_uri);
}
if ($request->isFormPost()) {
$xactions = array();
$xactions[] = id(new PhabricatorUserTransaction())
->setTransactionType(PhabricatorUserApproveTransaction::TRANSACTIONTYPE)
->setNewValue(true);
id(new PhabricatorUserTransactionEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->applyTransactions($user, $xactions);
return id(new AphrontRedirectResponse())->setURI($done_uri);
}
return $this->newDialog()
->setTitle(pht('Confirm Approval'))
->appendChild(
pht(
- 'Allow %s to access this Phabricator install?',
+ 'Allow %s to access this server?',
phutil_tag('strong', array(), $user->getUsername())))
->addCancelButton($done_uri)
->addSubmitButton(pht('Approve Account'));
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleCreateController.php b/src/applications/people/controller/PhabricatorPeopleCreateController.php
index c0b232645b..320ed57d3c 100644
--- a/src/applications/people/controller/PhabricatorPeopleCreateController.php
+++ b/src/applications/people/controller/PhabricatorPeopleCreateController.php
@@ -1,120 +1,120 @@
<?php
final class PhabricatorPeopleCreateController
extends PhabricatorPeopleController {
public function handleRequest(AphrontRequest $request) {
$admin = $request->getUser();
id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$admin,
$request,
$this->getApplicationURI());
$v_type = 'standard';
if ($request->isFormPost()) {
$v_type = $request->getStr('type');
if ($v_type == 'standard' || $v_type == 'bot' || $v_type == 'list') {
return id(new AphrontRedirectResponse())->setURI(
$this->getApplicationURI('new/'.$v_type.'/'));
}
}
$title = pht('Create New User');
$standard_caption = pht(
- 'Create a standard user account. These users can log in to Phabricator, '.
+ 'Create a standard user account. These users can log in, '.
'use the web interface and API, and receive email.');
$standard_admin = pht(
'Administrators are limited in their ability to access or edit these '.
'accounts after account creation.');
$bot_caption = pht(
'Create a bot/script user account, to automate interactions with other '.
'systems. These users can not use the web interface, but can use the '.
'API.');
$bot_admin = pht(
'Administrators have greater access to edit these accounts.');
$types = array();
$can_create = $this->hasApplicationCapability(
PeopleCreateUsersCapability::CAPABILITY);
if ($can_create) {
$types[] = array(
'type' => 'standard',
'name' => pht('Create Standard User'),
'help' => pht('Create a standard user account.'),
);
}
$types[] = array(
'type' => 'bot',
'name' => pht('Create Bot User'),
'help' => pht('Create a new user for use with automated scripts.'),
);
$types[] = array(
'type' => 'list',
'name' => pht('Create Mailing List User'),
'help' => pht(
'Create a mailing list user to represent an existing, external '.
'mailing list like a Google Group or a Mailman list.'),
);
$buttons = id(new AphrontFormRadioButtonControl())
->setLabel(pht('Account Type'))
->setName('type')
->setValue($v_type);
foreach ($types as $type) {
$buttons->addButton($type['type'], $type['name'], $type['help']);
}
$form = id(new AphrontFormView())
->setUser($admin)
->appendRemarkupInstructions(
pht(
'Choose the type of user account to create. For a detailed '.
'explanation of user account types, see [[ %s | User Guide: '.
'Account Roles ]].',
PhabricatorEnv::getDoclink('User Guide: Account Roles')))
->appendChild($buttons)
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($this->getApplicationURI())
->setValue(pht('Continue')));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($title);
$crumbs->setBorder(true);
$box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->setForm($form);
$guidance_context = new PhabricatorPeopleCreateGuidanceContext();
$guidance = id(new PhabricatorGuidanceEngine())
->setViewer($admin)
->setGuidanceContext($guidance_context)
->newInfoView();
$view = id(new PHUITwoColumnView())
->setFooter(
array(
$guidance,
$box,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleDisableController.php b/src/applications/people/controller/PhabricatorPeopleDisableController.php
index 9f2718086b..6e3f436d6f 100644
--- a/src/applications/people/controller/PhabricatorPeopleDisableController.php
+++ b/src/applications/people/controller/PhabricatorPeopleDisableController.php
@@ -1,118 +1,118 @@
<?php
final class PhabricatorPeopleDisableController
extends PhabricatorPeopleController {
public function shouldRequireAdmin() {
return false;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$via = $request->getURIData('via');
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$user) {
return new Aphront404Response();
}
// NOTE: We reach this controller via the administrative "Disable User"
// on profiles and also via the "X" action on the approval queue. We do
// things slightly differently depending on the context the actor is in.
// In particular, disabling via "Disapprove" requires you be an
// administrator (and bypasses the "Can Disable Users" permission).
// Disabling via "Disable" requires the permission only.
$is_disapprove = ($via == 'disapprove');
if ($is_disapprove) {
$done_uri = $this->getApplicationURI('query/approval/');
if (!$viewer->getIsAdmin()) {
return $this->newDialog()
->setTitle(pht('No Permission'))
->appendParagraph(pht('Only administrators can disapprove users.'))
->addCancelButton($done_uri);
}
if ($user->getIsApproved()) {
return $this->newDialog()
->setTitle(pht('Already Approved'))
->appendParagraph(pht('This user has already been approved.'))
->addCancelButton($done_uri);
}
// On the "Disapprove" flow, bypass the "Can Disable Users" permission.
$actor = PhabricatorUser::getOmnipotentUser();
$should_disable = true;
} else {
$this->requireApplicationCapability(
PeopleDisableUsersCapability::CAPABILITY);
$actor = $viewer;
$done_uri = $this->getApplicationURI("manage/{$id}/");
$should_disable = !$user->getIsDisabled();
}
if ($viewer->getPHID() == $user->getPHID()) {
return $this->newDialog()
->setTitle(pht('Something Stays Your Hand'))
->appendParagraph(
pht(
'Try as you might, you find you can not disable your own account.'))
->addCancelButton($done_uri, pht('Curses!'));
}
if ($request->isFormPost()) {
$xactions = array();
$xactions[] = id(new PhabricatorUserTransaction())
->setTransactionType(PhabricatorUserDisableTransaction::TRANSACTIONTYPE)
->setNewValue($should_disable);
id(new PhabricatorUserTransactionEditor())
->setActor($actor)
->setActingAsPHID($viewer->getPHID())
->setContentSourceFromRequest($request)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->applyTransactions($user, $xactions);
return id(new AphrontRedirectResponse())->setURI($done_uri);
}
if ($should_disable) {
$title = pht('Disable User?');
$short_title = pht('Disable User');
$body = pht(
- 'Disable %s? They will no longer be able to access Phabricator or '.
+ 'Disable %s? They will no longer be able to access this server or '.
'receive email.',
phutil_tag('strong', array(), $user->getUsername()));
$submit = pht('Disable User');
} else {
$title = pht('Enable User?');
$short_title = pht('Enable User');
$body = pht(
- 'Enable %s? They will be able to access Phabricator and receive '.
+ 'Enable %s? They will be able to access this server and receive '.
'email again.',
phutil_tag('strong', array(), $user->getUsername()));
$submit = pht('Enable User');
}
return $this->newDialog()
->setTitle($title)
->setShortTitle($short_title)
->appendParagraph($body)
->addCancelButton($done_uri)
->addSubmitButton($submit);
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleEmpowerController.php b/src/applications/people/controller/PhabricatorPeopleEmpowerController.php
index 22e7c22b68..b7af73ac02 100644
--- a/src/applications/people/controller/PhabricatorPeopleEmpowerController.php
+++ b/src/applications/people/controller/PhabricatorPeopleEmpowerController.php
@@ -1,70 +1,70 @@
<?php
final class PhabricatorPeopleEmpowerController
extends PhabricatorPeopleController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$user) {
return new Aphront404Response();
}
$done_uri = $this->getApplicationURI("manage/{$id}/");
$validation_exception = null;
if ($request->isFormOrHisecPost()) {
$xactions = array();
$xactions[] = id(new PhabricatorUserTransaction())
->setTransactionType(
PhabricatorUserEmpowerTransaction::TRANSACTIONTYPE)
->setNewValue(!$user->getIsAdmin());
$editor = id(new PhabricatorUserTransactionEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnMissingFields(true)
->setCancelURI($done_uri);
try {
$editor->applyTransactions($user, $xactions);
return id(new AphrontRedirectResponse())->setURI($done_uri);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
}
}
if ($user->getIsAdmin()) {
$title = pht('Remove as Administrator?');
$short = pht('Remove Administrator');
$body = pht(
'Remove %s as an administrator? They will no longer be able to '.
- 'perform administrative functions on this Phabricator install.',
+ 'perform administrative functions on this server.',
phutil_tag('strong', array(), $user->getUsername()));
$submit = pht('Remove Administrator');
} else {
$title = pht('Make Administrator?');
$short = pht('Make Administrator');
$body = pht(
'Empower %s as an administrator? They will be able to create users, '.
'approve users, make and remove administrators, delete accounts, and '.
- 'perform other administrative functions on this Phabricator install.',
+ 'perform other administrative functions on this server.',
phutil_tag('strong', array(), $user->getUsername()));
$submit = pht('Make Administrator');
}
return $this->newDialog()
->setValidationException($validation_exception)
->setTitle($title)
->setShortTitle($short)
->appendParagraph($body)
->addCancelButton($done_uri)
->addSubmitButton($submit);
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleInviteSendController.php b/src/applications/people/controller/PhabricatorPeopleInviteSendController.php
index e611606a79..78ab7573ed 100644
--- a/src/applications/people/controller/PhabricatorPeopleInviteSendController.php
+++ b/src/applications/people/controller/PhabricatorPeopleInviteSendController.php
@@ -1,231 +1,232 @@
<?php
final class PhabricatorPeopleInviteSendController
extends PhabricatorPeopleInviteController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$this->requireApplicationCapability(
PeopleCreateUsersCapability::CAPABILITY);
$is_confirm = false;
$errors = array();
$confirm_errors = array();
$e_emails = true;
$message = $request->getStr('message');
$emails = $request->getStr('emails');
$severity = PHUIInfoView::SEVERITY_ERROR;
if ($request->isFormPost()) {
// NOTE: We aren't using spaces as a delimiter here because email
// addresses with names often include spaces.
$email_list = preg_split('/[,;\n]+/', $emails);
foreach ($email_list as $key => $email) {
if (!strlen(trim($email))) {
unset($email_list[$key]);
}
}
if ($email_list) {
$e_emails = null;
} else {
$e_emails = pht('Required');
$errors[] = pht(
'To send invites, you must enter at least one email address.');
}
if (!$errors) {
$is_confirm = true;
$actions = PhabricatorAuthInviteAction::newActionListFromAddresses(
$viewer,
$email_list);
$any_valid = false;
$all_valid = true;
foreach ($actions as $action) {
if ($action->willSend()) {
$any_valid = true;
} else {
$all_valid = false;
}
}
if (!$any_valid) {
$confirm_errors[] = pht(
'None of the provided addresses are valid invite recipients. '.
'Review the table below for details. Revise the address list '.
'to continue.');
} else if ($all_valid) {
$confirm_errors[] = pht(
'All of the addresses appear to be valid invite recipients. '.
'Confirm the actions below to continue.');
$severity = PHUIInfoView::SEVERITY_NOTICE;
} else {
$confirm_errors[] = pht(
'Some of the addresses you entered do not appear to be '.
'valid recipients. Review the table below. You can revise '.
'the address list, or ignore these errors and continue.');
$severity = PHUIInfoView::SEVERITY_WARNING;
}
if ($any_valid && $request->getBool('confirm')) {
// TODO: The copywriting on this mail could probably be more
// engaging and we could have a fancy HTML version.
$template = array();
$template[] = pht(
- '%s has invited you to join Phabricator.',
- $viewer->getFullName());
+ '%s has invited you to join %s.',
+ $viewer->getFullName(),
+ PlatformSymbols::getPlatformServerName());
if (strlen(trim($message))) {
$template[] = $message;
}
$template[] = pht(
'To register an account and get started, follow this link:');
// This isn't a variable; it will be replaced later on in the
// daemons once they generate the URI.
$template[] = '{$INVITE_URI}';
$template[] = pht(
'If you already have an account, you can follow the link to '.
'quickly verify this email address.');
$template = implode("\n\n", $template);
foreach ($actions as $action) {
if ($action->willSend()) {
$action->sendInvite($viewer, $template);
}
}
// TODO: This is a bit anticlimactic. We don't really have anything
// to show the user because the action is happening in the background
// and the invites won't exist yet. After T5166 we can show a
// better progress bar.
return id(new AphrontRedirectResponse())
->setURI($this->getApplicationURI());
}
}
}
if ($is_confirm) {
$title = pht('Confirm Invites');
} else {
$title = pht('Invite Users');
}
$crumbs = $this->buildApplicationCrumbs();
if ($is_confirm) {
$crumbs->addTextCrumb(pht('Confirm'));
} else {
$crumbs->addTextCrumb(pht('Invite Users'));
}
$crumbs->setBorder(true);
$confirm_box = null;
$info_view = null;
if ($is_confirm) {
$handles = array();
if ($actions) {
$handles = $this->loadViewerHandles(mpull($actions, 'getUserPHID'));
}
$invite_table = id(new PhabricatorAuthInviteActionTableView())
->setUser($viewer)
->setInviteActions($actions)
->setHandles($handles);
$confirm_form = null;
if ($any_valid) {
$confirm_form = id(new AphrontFormView())
->setUser($viewer)
->addHiddenInput('message', $message)
->addHiddenInput('emails', $emails)
->addHiddenInput('confirm', true)
->appendRemarkupInstructions(
pht(
'If everything looks good, click **Send Invitations** to '.
'deliver email invitations these users. Otherwise, edit the '.
'email list or personal message at the bottom of the page to '.
'revise the invitations.'))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Send Invitations')));
}
$info_view = id(new PHUIInfoView())
->setErrors($confirm_errors)
->setSeverity($severity);
$confirm_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Confirm Invites'))
->setTable($invite_table)
->appendChild($confirm_form)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
}
$form = id(new AphrontFormView())
->setUser($viewer)
->appendRemarkupInstructions(
pht(
- 'To invite users to Phabricator, enter their email addresses below. '.
+ 'To invite users, enter their email addresses below. '.
'Separate addresses with commas or newlines.'))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Email Addresses'))
->setName(pht('emails'))
->setValue($emails)
->setError($e_emails)
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL))
->appendRemarkupInstructions(
pht(
'You can optionally include a heartfelt personal message in '.
'the email.'))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Message'))
->setName(pht('message'))
->setValue($message))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(
$is_confirm
? pht('Update Preview')
: pht('Continue'))
->addCancelButton($this->getApplicationURI('invite/')));
$header = id(new PHUIHeaderView())
->setHeader($title)
->setHeaderIcon('fa-group');
$box = id(new PHUIObjectBoxView())
->setHeaderText(
$is_confirm
? pht('Revise Invites')
: pht('Invite Users'))
->setFormErrors($errors)
->setForm($form)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$info_view,
$confirm_box,
$box,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleNewController.php b/src/applications/people/controller/PhabricatorPeopleNewController.php
index 858ebef234..121e464b30 100644
--- a/src/applications/people/controller/PhabricatorPeopleNewController.php
+++ b/src/applications/people/controller/PhabricatorPeopleNewController.php
@@ -1,243 +1,245 @@
<?php
final class PhabricatorPeopleNewController
extends PhabricatorPeopleController {
public function handleRequest(AphrontRequest $request) {
$type = $request->getURIData('type');
$admin = $request->getUser();
id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$admin,
$request,
$this->getApplicationURI());
$is_bot = false;
$is_list = false;
switch ($type) {
case 'standard':
$this->requireApplicationCapability(
PeopleCreateUsersCapability::CAPABILITY);
break;
case 'bot':
$is_bot = true;
break;
case 'list':
$is_list = true;
break;
default:
return new Aphront404Response();
}
$user = new PhabricatorUser();
$require_real_name = PhabricatorEnv::getEnvConfig('user.require-real-name');
$e_username = true;
$e_realname = $require_real_name ? true : null;
$e_email = true;
$errors = array();
$welcome_checked = true;
$new_email = null;
if ($request->isFormPost()) {
$welcome_checked = $request->getInt('welcome');
$user->setUsername($request->getStr('username'));
$new_email = $request->getStr('email');
if (!strlen($new_email)) {
$errors[] = pht('Email is required.');
$e_email = pht('Required');
} else if (!PhabricatorUserEmail::isValidAddress($new_email)) {
$errors[] = PhabricatorUserEmail::describeValidAddresses();
$e_email = pht('Invalid');
} else if (!PhabricatorUserEmail::isAllowedAddress($new_email)) {
$errors[] = PhabricatorUserEmail::describeAllowedAddresses();
$e_email = pht('Not Allowed');
} else {
$e_email = null;
}
$user->setRealName($request->getStr('realname'));
if (!strlen($user->getUsername())) {
$errors[] = pht('Username is required.');
$e_username = pht('Required');
} else if (!PhabricatorUser::validateUsername($user->getUsername())) {
$errors[] = PhabricatorUser::describeValidUsername();
$e_username = pht('Invalid');
} else {
$e_username = null;
}
if (!strlen($user->getRealName()) && $require_real_name) {
$errors[] = pht('Real name is required.');
$e_realname = pht('Required');
} else {
$e_realname = null;
}
if (!$errors) {
try {
$email = id(new PhabricatorUserEmail())
->setAddress($new_email)
->setIsVerified(0);
// Automatically approve the user, since an admin is creating them.
$user->setIsApproved(1);
// If the user is a bot or list, approve their email too.
if ($is_bot || $is_list) {
$email->setIsVerified(1);
}
id(new PhabricatorUserEditor())
->setActor($admin)
->createNewUser($user, $email);
if ($is_bot) {
id(new PhabricatorUserEditor())
->setActor($admin)
->makeSystemAgentUser($user, true);
}
if ($is_list) {
id(new PhabricatorUserEditor())
->setActor($admin)
->makeMailingListUser($user, true);
}
if ($welcome_checked) {
$welcome_engine = id(new PhabricatorPeopleWelcomeMailEngine())
->setSender($admin)
->setRecipient($user);
if ($welcome_engine->canSendMail()) {
$welcome_engine->sendMail();
}
}
$response = id(new AphrontRedirectResponse())
->setURI('/p/'.$user->getUsername().'/');
return $response;
} catch (AphrontDuplicateKeyQueryException $ex) {
$errors[] = pht('Username and email must be unique.');
$same_username = id(new PhabricatorUser())
->loadOneWhere('username = %s', $user->getUsername());
$same_email = id(new PhabricatorUserEmail())
->loadOneWhere('address = %s', $new_email);
if ($same_username) {
$e_username = pht('Duplicate');
}
if ($same_email) {
$e_email = pht('Duplicate');
}
}
}
}
$form = id(new AphrontFormView())
->setUser($admin);
if ($is_bot) {
$title = pht('Create New Bot');
$form->appendRemarkupInstructions(
pht('You are creating a new **bot** user account.'));
} else if ($is_list) {
$title = pht('Create New Mailing List');
$form->appendRemarkupInstructions(
pht('You are creating a new **mailing list** user account.'));
} else {
$title = pht('Create New User');
$form->appendRemarkupInstructions(
pht('You are creating a new **standard** user account.'));
}
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Username'))
->setName('username')
->setValue($user->getUsername())
->setError($e_username))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Real Name'))
->setName('realname')
->setValue($user->getRealName())
->setError($e_realname))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Email'))
->setName('email')
->setValue($new_email)
->setCaption(PhabricatorUserEmail::describeAllowedAddresses())
->setError($e_email));
if (!$is_bot && !$is_list) {
$form->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'welcome',
1,
- pht('Send "Welcome to Phabricator" email with login instructions.'),
+ pht(
+ 'Send "Welcome to %s" email with login instructions.',
+ PlatformSymbols::getPlatformServerName()),
$welcome_checked));
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($this->getApplicationURI())
->setValue(pht('Create User')));
if ($is_bot) {
$form
->appendChild(id(new AphrontFormDividerControl()))
->appendRemarkupInstructions(
pht(
'**Why do bot accounts need an email address?**'.
"\n\n".
- 'Although bots do not normally receive email from Phabricator, '.
- 'they can interact with other systems which require an email '.
- 'address. Examples include:'.
+ 'Although bots do not normally receive email, they can interact '.
+ 'with other systems which require an email address. Examples '.
+ 'include:'.
"\n\n".
" - If the account takes actions which //send// email, we need ".
" an address to use in the //From// header.\n".
" - If the account creates commits, Git and Mercurial require ".
" an email address for authorship.\n".
- " - If you send email //to// Phabricator on behalf of the ".
+ " - If you send email //to// this server on behalf of the ".
" account, the address can identify the sender.\n".
" - Some internal authentication functions depend on accounts ".
" having an email address.\n".
"\n\n".
"The address will automatically be verified, so you do not need ".
"to be able to receive mail at this address, and can enter some ".
"invalid or nonexistent (but correctly formatted) address like ".
"`bot@yourcompany.com` if you prefer."));
}
$box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setFormErrors($errors)
->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->setForm($form);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($title);
$crumbs->setBorder(true);
$view = id(new PHUITwoColumnView())
->setFooter($box);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleWelcomeController.php b/src/applications/people/controller/PhabricatorPeopleWelcomeController.php
index 94d5e0bb03..924eb97b5d 100644
--- a/src/applications/people/controller/PhabricatorPeopleWelcomeController.php
+++ b/src/applications/people/controller/PhabricatorPeopleWelcomeController.php
@@ -1,95 +1,96 @@
<?php
final class PhabricatorPeopleWelcomeController
extends PhabricatorPeopleController {
public function shouldRequireAdmin() {
// You need to be an administrator to actually send welcome email, but
// we let anyone hit this page so they can get a nice error dialog
// explaining the issue.
return false;
}
public function handleRequest(AphrontRequest $request) {
$admin = $this->getViewer();
$user = id(new PhabricatorPeopleQuery())
->setViewer($admin)
->withIDs(array($request->getURIData('id')))
->executeOne();
if (!$user) {
return new Aphront404Response();
}
$id = $user->getID();
$profile_uri = "/people/manage/{$id}/";
$welcome_engine = id(new PhabricatorPeopleWelcomeMailEngine())
->setSender($admin)
->setRecipient($user);
try {
$welcome_engine->validateMail();
} catch (PhabricatorPeopleMailEngineException $ex) {
return $this->newDialog()
->setTitle($ex->getTitle())
->appendParagraph($ex->getBody())
->addCancelButton($profile_uri, pht('Done'));
}
$v_message = $request->getStr('message');
if ($request->isFormPost()) {
if (strlen($v_message)) {
$welcome_engine->setWelcomeMessage($v_message);
}
$welcome_engine->sendMail();
return id(new AphrontRedirectResponse())->setURI($profile_uri);
}
$default_message = PhabricatorAuthMessage::loadMessage(
$admin,
PhabricatorAuthWelcomeMailMessageType::MESSAGEKEY);
if ($default_message && strlen($default_message->getMessageText())) {
$message_instructions = pht(
'The email will identify you as the sender. You may optionally '.
'replace the [[ %s | default custom mail body ]] with different text '.
'by providing a message below.',
$default_message->getURI());
} else {
$message_instructions = pht(
'The email will identify you as the sender. You may optionally '.
'include additional text in the mail body by specifying it below.');
}
$form = id(new AphrontFormView())
->setViewer($admin)
->appendRemarkupInstructions(
pht(
'This workflow will send this user ("%s") a copy of the "Welcome to '.
- 'Phabricator" email that users normally receive when their '.
+ '%s" email that users normally receive when their '.
'accounts are created by an administrator.',
- $user->getUsername()))
+ $user->getUsername(),
+ PlatformSymbols::getPlatformServerName()))
->appendRemarkupInstructions(
pht(
'The email will contain a link that the user may use to log in '.
'to their account. This link bypasses authentication requirements '.
'and allows them to log in without credentials. Sending a copy of '.
'this email can be useful if the original was lost or never sent.'))
->appendRemarkupInstructions($message_instructions)
->appendControl(
id(new PhabricatorRemarkupControl())
->setName('message')
->setLabel(pht('Custom Message'))
->setValue($v_message));
return $this->newDialog()
->setTitle(pht('Send Welcome Email'))
->setWidth(AphrontDialogView::WIDTH_FORM)
->appendForm($form)
->addSubmitButton(pht('Send Email'))
->addCancelButton($profile_uri);
}
}
diff --git a/src/applications/people/mail/PhabricatorPeopleEmailLoginMailEngine.php b/src/applications/people/mail/PhabricatorPeopleEmailLoginMailEngine.php
index c25b19c3c1..4904e269a6 100644
--- a/src/applications/people/mail/PhabricatorPeopleEmailLoginMailEngine.php
+++ b/src/applications/people/mail/PhabricatorPeopleEmailLoginMailEngine.php
@@ -1,130 +1,133 @@
<?php
final class PhabricatorPeopleEmailLoginMailEngine
extends PhabricatorPeopleMailEngine {
public function validateMail() {
$recipient = $this->getRecipient();
if ($recipient->getIsDisabled()) {
$this->throwValidationException(
pht('User is Disabled'),
pht(
'You can not send an email login link to this email address '.
'because the associated user account is disabled.'));
}
if (!$recipient->canEstablishWebSessions()) {
$this->throwValidationException(
pht('Not a Normal User'),
pht(
'You can not send an email login link to this email address '.
'because the associated user account is not a normal user account '.
'and can not log in to the web interface.'));
}
}
protected function newMail() {
$is_set_password = $this->isSetPasswordWorkflow();
if ($is_set_password) {
- $subject = pht('[Phabricator] Account Password Link');
+ $subject = pht(
+ '[%s] Account Password Link',
+ PlatformSymbols::getPlatformServerName());
} else {
- $subject = pht('[Phabricator] Account Login Link');
+ $subject = pht(
+ '[%s] Account Login Link',
+ PlatformSymbols::getPlatformServerName());
}
$recipient = $this->getRecipient();
PhabricatorSystemActionEngine::willTakeAction(
array($recipient->getPHID()),
new PhabricatorAuthEmailLoginAction(),
1);
$engine = new PhabricatorAuthSessionEngine();
$login_uri = $engine->getOneTimeLoginURI(
$recipient,
null,
PhabricatorAuthSessionEngine::ONETIME_RESET);
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$have_passwords = $this->isPasswordAuthEnabled();
$body = array();
if ($is_set_password) {
$message_key = PhabricatorAuthEmailSetPasswordMessageType::MESSAGEKEY;
} else {
$message_key = PhabricatorAuthEmailLoginMessageType::MESSAGEKEY;
}
$message_body = PhabricatorAuthMessage::loadMessageText(
$recipient,
$message_key);
if (strlen($message_body)) {
$body[] = $this->newRemarkupText($message_body);
}
if ($have_passwords) {
if ($is_set_password) {
$body[] = pht(
'You can use this link to set a password on your account:'.
"\n\n %s\n",
$login_uri);
} else if ($is_serious) {
$body[] = pht(
- "You can use this link to reset your Phabricator password:".
+ "You can use this link to reset your password:".
"\n\n %s\n",
$login_uri);
} else {
$body[] = pht(
"Condolences on forgetting your password. You can use this ".
"link to reset it:\n\n".
" %s\n\n".
"After you set a new password, consider writing it down on a ".
"sticky note and attaching it to your monitor so you don't ".
"forget again! Choosing a very short, easy-to-remember password ".
"like \"cat\" or \"1234\" might also help.\n\n".
"Best Wishes,\nPhabricator\n",
$login_uri);
}
} else {
$body[] = pht(
- "You can use this login link to regain access to your Phabricator ".
- "account:".
+ "You can use this login link to regain access to your account:".
"\n\n".
" %s\n",
$login_uri);
}
$body = implode("\n\n", $body);
return id(new PhabricatorMetaMTAMail())
->setSubject($subject)
->setBody($body);
}
private function isPasswordAuthEnabled() {
return (bool)PhabricatorPasswordAuthProvider::getPasswordProvider();
}
private function isSetPasswordWorkflow() {
$sender = $this->getSender();
$recipient = $this->getRecipient();
// Users can hit the "login with an email link" workflow while trying to
// set a password on an account which does not yet have a password. We
// require they verify that they own the email address and send them
// through the email login flow. In this case, the messaging is slightly
// different.
if ($sender->getPHID()) {
if ($sender->getPHID() === $recipient->getPHID()) {
return true;
}
}
return false;
}
}
diff --git a/src/applications/people/mail/PhabricatorPeopleUsernameMailEngine.php b/src/applications/people/mail/PhabricatorPeopleUsernameMailEngine.php
index e62a6a4859..8ba59afafc 100644
--- a/src/applications/people/mail/PhabricatorPeopleUsernameMailEngine.php
+++ b/src/applications/people/mail/PhabricatorPeopleUsernameMailEngine.php
@@ -1,58 +1,62 @@
<?php
final class PhabricatorPeopleUsernameMailEngine
extends PhabricatorPeopleMailEngine {
private $oldUsername;
private $newUsername;
public function setNewUsername($new_username) {
$this->newUsername = $new_username;
return $this;
}
public function getNewUsername() {
return $this->newUsername;
}
public function setOldUsername($old_username) {
$this->oldUsername = $old_username;
return $this;
}
public function getOldUsername() {
return $this->oldUsername;
}
public function validateMail() {
return;
}
protected function newMail() {
$sender = $this->getSender();
$sender_username = $sender->getUsername();
$sender_realname = $sender->getRealName();
$old_username = $this->getOldUsername();
$new_username = $this->getNewUsername();
$body = sprintf(
"%s\n\n %s\n %s\n",
pht(
- '%s (%s) has changed your Phabricator username.',
+ '%s (%s) has changed your %s username.',
$sender_username,
- $sender_realname),
+ $sender_realname,
+ PlatformSymbols::getPlatformServerName()),
pht(
'Old Username: %s',
$old_username),
pht(
'New Username: %s',
$new_username));
return id(new PhabricatorMetaMTAMail())
- ->setSubject(pht('[Phabricator] Username Changed'))
+ ->setSubject(
+ pht(
+ '[%s] Username Changed',
+ PlatformSymbols::getPlatformServerName()))
->setBody($body);
}
}
diff --git a/src/applications/people/mail/PhabricatorPeopleWelcomeMailEngine.php b/src/applications/people/mail/PhabricatorPeopleWelcomeMailEngine.php
index ec99a5a484..868cc13da1 100644
--- a/src/applications/people/mail/PhabricatorPeopleWelcomeMailEngine.php
+++ b/src/applications/people/mail/PhabricatorPeopleWelcomeMailEngine.php
@@ -1,134 +1,142 @@
<?php
final class PhabricatorPeopleWelcomeMailEngine
extends PhabricatorPeopleMailEngine {
private $welcomeMessage;
public function setWelcomeMessage($welcome_message) {
$this->welcomeMessage = $welcome_message;
return $this;
}
public function getWelcomeMessage() {
return $this->welcomeMessage;
}
public function validateMail() {
$sender = $this->getSender();
$recipient = $this->getRecipient();
if (!$sender->getIsAdmin()) {
$this->throwValidationException(
pht('Not an Administrator'),
pht(
'You can not send welcome mail because you are not an '.
'administrator. Only administrators may send welcome mail.'));
}
if ($recipient->getIsDisabled()) {
$this->throwValidationException(
pht('User is Disabled'),
pht(
'You can not send welcome mail to this user because their account '.
'is disabled.'));
}
if (!$recipient->canEstablishWebSessions()) {
$this->throwValidationException(
pht('Not a Normal User'),
pht(
'You can not send this user welcome mail because they are not '.
'a normal user and can not log in to the web interface. Special '.
'users (like bots and mailing lists) are unable to establish '.
'web sessions.'));
}
}
protected function newMail() {
$sender = $this->getSender();
$recipient = $this->getRecipient();
$base_uri = PhabricatorEnv::getProductionURI('/');
$engine = new PhabricatorAuthSessionEngine();
$uri = $engine->getOneTimeLoginURI(
$recipient,
$recipient->loadPrimaryEmail(),
PhabricatorAuthSessionEngine::ONETIME_WELCOME);
$message = array();
- $message[] = pht('Welcome to Phabricator!');
+ $message[] = pht(
+ 'Welcome to %s!',
+ PlatformSymbols::getPlatformServerName());
$message[] = pht(
'%s (%s) has created an account for you.',
$sender->getUsername(),
$sender->getRealName());
$message[] = pht(
' Username: %s',
$recipient->getUsername());
// If password auth is enabled, give the user specific instructions about
// how to add a credential to their account.
// If we aren't sure what they're supposed to be doing and passwords are
// not enabled, just give them generic instructions.
$use_passwords = PhabricatorPasswordAuthProvider::getPasswordProvider();
if ($use_passwords) {
$message[] = pht(
- 'To log in to Phabricator, follow this link and set a password:');
+ 'To log in, follow this link and set a password:');
$message[] = pht(' %s', $uri);
$message[] = pht(
- 'After you have set a password, you can log in to Phabricator in '.
+ 'After you have set a password, you can log in again in '.
'the future by going here:');
$message[] = pht(' %s', $base_uri);
} else {
$message[] = pht(
'To log in to your account for the first time, follow this link:');
$message[] = pht(' %s', $uri);
$message[] = pht(
- 'After you set up your account, you can log in to Phabricator in '.
+ 'After you set up your account, you can log in again in '.
'the future by going here:');
$message[] = pht(' %s', $base_uri);
}
$message_body = $this->newBody();
if ($message_body !== null) {
$message[] = $message_body;
}
$message = implode("\n\n", $message);
return id(new PhabricatorMetaMTAMail())
- ->setSubject(pht('[Phabricator] Welcome to Phabricator'))
+ ->setSubject(
+ pht(
+ '[%s] Welcome to %s',
+ PlatformSymbols::getPlatformServerName(),
+ PlatformSymbols::getPlatformServerName()))
->setBody($message);
}
private function newBody() {
$recipient = $this->getRecipient();
$custom_body = $this->getWelcomeMessage();
if (strlen($custom_body)) {
return $this->newRemarkupText($custom_body);
}
$default_body = PhabricatorAuthMessage::loadMessageText(
$recipient,
PhabricatorAuthWelcomeMailMessageType::MESSAGEKEY);
if (strlen($default_body)) {
return $this->newRemarkupText($default_body);
}
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
if (!$is_serious) {
- return pht("Love,\nPhabricator");
+ return pht(
+ "Love,\n%s",
+ PlatformSymbols::getPlatformServerName());
}
return null;
}
}
diff --git a/src/applications/people/query/PhabricatorPeopleLogQuery.php b/src/applications/people/query/PhabricatorPeopleLogQuery.php
index 203f79579a..e87fb33660 100644
--- a/src/applications/people/query/PhabricatorPeopleLogQuery.php
+++ b/src/applications/people/query/PhabricatorPeopleLogQuery.php
@@ -1,139 +1,135 @@
<?php
final class PhabricatorPeopleLogQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $actorPHIDs;
private $userPHIDs;
private $relatedPHIDs;
private $sessionKeys;
private $actions;
private $remoteAddressPrefix;
private $dateCreatedMin;
private $dateCreatedMax;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withActorPHIDs(array $actor_phids) {
$this->actorPHIDs = $actor_phids;
return $this;
}
public function withUserPHIDs(array $user_phids) {
$this->userPHIDs = $user_phids;
return $this;
}
public function withRelatedPHIDs(array $related_phids) {
$this->relatedPHIDs = $related_phids;
return $this;
}
public function withSessionKeys(array $session_keys) {
$this->sessionKeys = $session_keys;
return $this;
}
public function withActions(array $actions) {
$this->actions = $actions;
return $this;
}
public function withRemoteAddressPrefix($remote_address_prefix) {
$this->remoteAddressPrefix = $remote_address_prefix;
return $this;
}
public function withDateCreatedBetween($min, $max) {
$this->dateCreatedMin = $min;
$this->dateCreatedMax = $max;
return $this;
}
public function newResultObject() {
return new PhabricatorUserLog();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->actorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'actorPHID IN (%Ls)',
$this->actorPHIDs);
}
if ($this->userPHIDs !== null) {
$where[] = qsprintf(
$conn,
'userPHID IN (%Ls)',
$this->userPHIDs);
}
if ($this->relatedPHIDs !== null) {
$where[] = qsprintf(
$conn,
'(actorPHID IN (%Ls) OR userPHID IN (%Ls))',
$this->relatedPHIDs,
$this->relatedPHIDs);
}
if ($this->sessionKeys !== null) {
$where[] = qsprintf(
$conn,
'session IN (%Ls)',
$this->sessionKeys);
}
if ($this->actions !== null) {
$where[] = qsprintf(
$conn,
'action IN (%Ls)',
$this->actions);
}
if ($this->remoteAddressPrefix !== null) {
$where[] = qsprintf(
$conn,
'remoteAddr LIKE %>',
$this->remoteAddressPrefix);
}
if ($this->dateCreatedMin !== null) {
$where[] = qsprintf(
$conn,
'dateCreated >= %d',
$this->dateCreatedMin);
}
if ($this->dateCreatedMax !== null) {
$where[] = qsprintf(
$conn,
'dateCreated <= %d',
$this->dateCreatedMax);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorPeopleApplication';
}
}
diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php
index e122be0b2e..b74b936ba8 100644
--- a/src/applications/people/query/PhabricatorPeopleQuery.php
+++ b/src/applications/people/query/PhabricatorPeopleQuery.php
@@ -1,634 +1,630 @@
<?php
final class PhabricatorPeopleQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $usernames;
private $realnames;
private $emails;
private $phids;
private $ids;
private $dateCreatedAfter;
private $dateCreatedBefore;
private $isAdmin;
private $isSystemAgent;
private $isMailingList;
private $isDisabled;
private $isApproved;
private $nameLike;
private $nameTokens;
private $namePrefixes;
private $isEnrolledInMultiFactor;
private $needPrimaryEmail;
private $needProfile;
private $needProfileImage;
private $needAvailability;
private $needBadgeAwards;
private $cacheKeys = array();
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withEmails(array $emails) {
$this->emails = $emails;
return $this;
}
public function withRealnames(array $realnames) {
$this->realnames = $realnames;
return $this;
}
public function withUsernames(array $usernames) {
$this->usernames = $usernames;
return $this;
}
public function withDateCreatedBefore($date_created_before) {
$this->dateCreatedBefore = $date_created_before;
return $this;
}
public function withDateCreatedAfter($date_created_after) {
$this->dateCreatedAfter = $date_created_after;
return $this;
}
public function withIsAdmin($admin) {
$this->isAdmin = $admin;
return $this;
}
public function withIsSystemAgent($system_agent) {
$this->isSystemAgent = $system_agent;
return $this;
}
public function withIsMailingList($mailing_list) {
$this->isMailingList = $mailing_list;
return $this;
}
public function withIsDisabled($disabled) {
$this->isDisabled = $disabled;
return $this;
}
public function withIsApproved($approved) {
$this->isApproved = $approved;
return $this;
}
public function withNameLike($like) {
$this->nameLike = $like;
return $this;
}
public function withNameTokens(array $tokens) {
$this->nameTokens = array_values($tokens);
return $this;
}
public function withNamePrefixes(array $prefixes) {
$this->namePrefixes = $prefixes;
return $this;
}
public function withIsEnrolledInMultiFactor($enrolled) {
$this->isEnrolledInMultiFactor = $enrolled;
return $this;
}
public function needPrimaryEmail($need) {
$this->needPrimaryEmail = $need;
return $this;
}
public function needProfile($need) {
$this->needProfile = $need;
return $this;
}
public function needProfileImage($need) {
$cache_key = PhabricatorUserProfileImageCacheType::KEY_URI;
if ($need) {
$this->cacheKeys[$cache_key] = true;
} else {
unset($this->cacheKeys[$cache_key]);
}
return $this;
}
public function needAvailability($need) {
$this->needAvailability = $need;
return $this;
}
public function needUserSettings($need) {
$cache_key = PhabricatorUserPreferencesCacheType::KEY_PREFERENCES;
if ($need) {
$this->cacheKeys[$cache_key] = true;
} else {
unset($this->cacheKeys[$cache_key]);
}
return $this;
}
public function needBadgeAwards($need) {
$cache_key = PhabricatorUserBadgesCacheType::KEY_BADGES;
if ($need) {
$this->cacheKeys[$cache_key] = true;
} else {
unset($this->cacheKeys[$cache_key]);
}
return $this;
}
public function newResultObject() {
return new PhabricatorUser();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function didFilterPage(array $users) {
if ($this->needProfile) {
$user_list = mpull($users, null, 'getPHID');
$profiles = new PhabricatorUserProfile();
$profiles = $profiles->loadAllWhere(
'userPHID IN (%Ls)',
array_keys($user_list));
$profiles = mpull($profiles, null, 'getUserPHID');
foreach ($user_list as $user_phid => $user) {
$profile = idx($profiles, $user_phid);
if (!$profile) {
$profile = PhabricatorUserProfile::initializeNewProfile($user);
}
$user->attachUserProfile($profile);
}
}
if ($this->needAvailability) {
$rebuild = array();
foreach ($users as $user) {
$cache = $user->getAvailabilityCache();
if ($cache !== null) {
$user->attachAvailability($cache);
} else {
$rebuild[] = $user;
}
}
if ($rebuild) {
$this->rebuildAvailabilityCache($rebuild);
}
}
$this->fillUserCaches($users);
return $users;
}
protected function shouldGroupQueryResultRows() {
if ($this->nameTokens) {
return true;
}
return parent::shouldGroupQueryResultRows();
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->emails) {
$email_table = new PhabricatorUserEmail();
$joins[] = qsprintf(
$conn,
'JOIN %T email ON email.userPHID = user.PHID',
$email_table->getTableName());
}
if ($this->nameTokens) {
foreach ($this->nameTokens as $key => $token) {
$token_table = 'token_'.$key;
$joins[] = qsprintf(
$conn,
'JOIN %T %T ON %T.userID = user.id AND %T.token LIKE %>',
PhabricatorUser::NAMETOKEN_TABLE,
$token_table,
$token_table,
$token_table,
$token);
}
}
return $joins;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->usernames !== null) {
$where[] = qsprintf(
$conn,
'user.userName IN (%Ls)',
$this->usernames);
}
if ($this->namePrefixes) {
$parts = array();
foreach ($this->namePrefixes as $name_prefix) {
$parts[] = qsprintf(
$conn,
'user.username LIKE %>',
$name_prefix);
}
$where[] = qsprintf($conn, '%LO', $parts);
}
if ($this->emails !== null) {
$where[] = qsprintf(
$conn,
'email.address IN (%Ls)',
$this->emails);
}
if ($this->realnames !== null) {
$where[] = qsprintf(
$conn,
'user.realName IN (%Ls)',
$this->realnames);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'user.phid IN (%Ls)',
$this->phids);
}
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'user.id IN (%Ld)',
$this->ids);
}
if ($this->dateCreatedAfter) {
$where[] = qsprintf(
$conn,
'user.dateCreated >= %d',
$this->dateCreatedAfter);
}
if ($this->dateCreatedBefore) {
$where[] = qsprintf(
$conn,
'user.dateCreated <= %d',
$this->dateCreatedBefore);
}
if ($this->isAdmin !== null) {
$where[] = qsprintf(
$conn,
'user.isAdmin = %d',
(int)$this->isAdmin);
}
if ($this->isDisabled !== null) {
$where[] = qsprintf(
$conn,
'user.isDisabled = %d',
(int)$this->isDisabled);
}
if ($this->isApproved !== null) {
$where[] = qsprintf(
$conn,
'user.isApproved = %d',
(int)$this->isApproved);
}
if ($this->isSystemAgent !== null) {
$where[] = qsprintf(
$conn,
'user.isSystemAgent = %d',
(int)$this->isSystemAgent);
}
if ($this->isMailingList !== null) {
$where[] = qsprintf(
$conn,
'user.isMailingList = %d',
(int)$this->isMailingList);
}
if ($this->nameLike !== null) {
$where[] = qsprintf(
$conn,
'user.username LIKE %~ OR user.realname LIKE %~',
$this->nameLike,
$this->nameLike);
}
if ($this->isEnrolledInMultiFactor !== null) {
$where[] = qsprintf(
$conn,
'user.isEnrolledInMultiFactor = %d',
(int)$this->isEnrolledInMultiFactor);
}
return $where;
}
protected function getPrimaryTableAlias() {
return 'user';
}
public function getQueryApplicationClass() {
return 'PhabricatorPeopleApplication';
}
public function getOrderableColumns() {
return parent::getOrderableColumns() + array(
'username' => array(
'table' => 'user',
'column' => 'username',
'type' => 'string',
'reverse' => true,
'unique' => true,
),
);
}
protected function newPagingMapFromPartialObject($object) {
return array(
'id' => (int)$object->getID(),
'username' => $object->getUsername(),
);
}
private function rebuildAvailabilityCache(array $rebuild) {
$rebuild = mpull($rebuild, null, 'getPHID');
// Limit the window we look at because far-future events are largely
// irrelevant and this makes the cache cheaper to build and allows it to
// self-heal over time.
$min_range = PhabricatorTime::getNow();
$max_range = $min_range + phutil_units('72 hours in seconds');
// NOTE: We don't need to generate ghosts here, because we only care if
// the user is attending, and you can't attend a ghost event: RSVP'ing
// to it creates a real event.
$events = id(new PhabricatorCalendarEventQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withInvitedPHIDs(array_keys($rebuild))
->withIsCancelled(false)
->withDateRange($min_range, $max_range)
->execute();
// Group all the events by invited user. Only examine events that users
// are actually attending.
$map = array();
$invitee_map = array();
foreach ($events as $event) {
foreach ($event->getInvitees() as $invitee) {
if (!$invitee->isAttending()) {
continue;
}
// If the user is set to "Available" for this event, don't consider it
// when computing their away status.
if (!$invitee->getDisplayAvailability($event)) {
continue;
}
$invitee_phid = $invitee->getInviteePHID();
if (!isset($rebuild[$invitee_phid])) {
continue;
}
$map[$invitee_phid][] = $event;
$event_phid = $event->getPHID();
$invitee_map[$invitee_phid][$event_phid] = $invitee;
}
}
// We need to load these users' timezone settings to figure out their
// availability if they're attending all-day events.
$this->needUserSettings(true);
$this->fillUserCaches($rebuild);
foreach ($rebuild as $phid => $user) {
$events = idx($map, $phid, array());
// We loaded events with the omnipotent user, but want to shift them
// into the user's timezone before building the cache because they will
// be unavailable during their own local day.
foreach ($events as $event) {
$event->applyViewerTimezone($user);
}
$cursor = $min_range;
$next_event = null;
if ($events) {
// Find the next time when the user has no meetings. If we move forward
// because of an event, we check again for events after that one ends.
while (true) {
foreach ($events as $event) {
$from = $event->getStartDateTimeEpochForCache();
$to = $event->getEndDateTimeEpochForCache();
if (($from <= $cursor) && ($to > $cursor)) {
$cursor = $to;
if (!$next_event) {
$next_event = $event;
}
continue 2;
}
}
break;
}
}
if ($cursor > $min_range) {
$invitee = $invitee_map[$phid][$next_event->getPHID()];
$availability_type = $invitee->getDisplayAvailability($next_event);
$availability = array(
'until' => $cursor,
'eventPHID' => $next_event->getPHID(),
'availability' => $availability_type,
);
// We only cache this availability until the end of the current event,
// since the event PHID (and possibly the availability type) are only
// valid for that long.
// NOTE: This doesn't handle overlapping events with the greatest
// possible care. In theory, if you're attending multiple events
// simultaneously we should accommodate that. However, it's complex
// to compute, rare, and probably not confusing most of the time.
$availability_ttl = $next_event->getEndDateTimeEpochForCache();
} else {
$availability = array(
'until' => null,
'eventPHID' => null,
'availability' => null,
);
// Cache that the user is available until the next event they are
// invited to starts.
$availability_ttl = $max_range;
foreach ($events as $event) {
$from = $event->getStartDateTimeEpochForCache();
if ($from > $cursor) {
$availability_ttl = min($from, $availability_ttl);
}
}
}
// Never TTL the cache to longer than the maximum range we examined.
$availability_ttl = min($availability_ttl, $max_range);
$user->writeAvailabilityCache($availability, $availability_ttl);
$user->attachAvailability($availability);
}
}
private function fillUserCaches(array $users) {
if (!$this->cacheKeys) {
return;
}
$user_map = mpull($users, null, 'getPHID');
$keys = array_keys($this->cacheKeys);
$hashes = array();
foreach ($keys as $key) {
$hashes[] = PhabricatorHash::digestForIndex($key);
}
$types = PhabricatorUserCacheType::getAllCacheTypes();
// First, pull any available caches. If we wanted to be particularly clever
// we could do this with JOINs in the main query.
$cache_table = new PhabricatorUserCache();
$cache_conn = $cache_table->establishConnection('r');
$cache_data = queryfx_all(
$cache_conn,
'SELECT cacheKey, userPHID, cacheData, cacheType FROM %T
WHERE cacheIndex IN (%Ls) AND userPHID IN (%Ls)',
$cache_table->getTableName(),
$hashes,
array_keys($user_map));
$skip_validation = array();
// After we read caches from the database, discard any which have data that
// invalid or out of date. This allows cache types to implement TTLs or
// versions instead of or in addition to explicit cache clears.
foreach ($cache_data as $row_key => $row) {
$cache_type = $row['cacheType'];
if (isset($skip_validation[$cache_type])) {
continue;
}
if (empty($types[$cache_type])) {
unset($cache_data[$row_key]);
continue;
}
$type = $types[$cache_type];
if (!$type->shouldValidateRawCacheData()) {
$skip_validation[$cache_type] = true;
continue;
}
$user = $user_map[$row['userPHID']];
$raw_data = $row['cacheData'];
if (!$type->isRawCacheDataValid($user, $row['cacheKey'], $raw_data)) {
unset($cache_data[$row_key]);
continue;
}
}
$need = array();
$cache_data = igroup($cache_data, 'userPHID');
foreach ($user_map as $user_phid => $user) {
$raw_rows = idx($cache_data, $user_phid, array());
$raw_data = ipull($raw_rows, 'cacheData', 'cacheKey');
foreach ($keys as $key) {
if (isset($raw_data[$key]) || array_key_exists($key, $raw_data)) {
continue;
}
$need[$key][$user_phid] = $user;
}
$user->attachRawCacheData($raw_data);
}
// If we missed any cache values, bulk-construct them now. This is
// usually much cheaper than generating them on-demand for each user
// record.
if (!$need) {
return;
}
$writes = array();
foreach ($need as $cache_key => $need_users) {
$type = PhabricatorUserCacheType::getCacheTypeForKey($cache_key);
if (!$type) {
continue;
}
$data = $type->newValueForUsers($cache_key, $need_users);
foreach ($data as $user_phid => $raw_value) {
$data[$user_phid] = $raw_value;
$writes[] = array(
'userPHID' => $user_phid,
'key' => $cache_key,
'type' => $type,
'value' => $raw_value,
);
}
foreach ($need_users as $user_phid => $user) {
if (isset($data[$user_phid]) || array_key_exists($user_phid, $data)) {
$user->attachRawCacheData(
array(
$cache_key => $data[$user_phid],
));
}
}
}
PhabricatorUserCache::writeCaches($writes);
}
}
diff --git a/src/applications/people/query/PhabricatorPeopleUserEmailQuery.php b/src/applications/people/query/PhabricatorPeopleUserEmailQuery.php
index 6e2627a96d..ead44a56dc 100644
--- a/src/applications/people/query/PhabricatorPeopleUserEmailQuery.php
+++ b/src/applications/people/query/PhabricatorPeopleUserEmailQuery.php
@@ -1,81 +1,77 @@
<?php
final class PhabricatorPeopleUserEmailQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function newResultObject() {
return new PhabricatorUserEmail();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function getPrimaryTableAlias() {
return 'email';
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'email.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'email.phid IN (%Ls)',
$this->phids);
}
return $where;
}
protected function willLoadPage(array $page) {
$user_phids = mpull($page, 'getUserPHID');
$users = id(new PhabricatorPeopleQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($user_phids)
->execute();
$users = mpull($users, null, 'getPHID');
foreach ($page as $key => $address) {
$user = idx($users, $address->getUserPHID());
if (!$user) {
unset($page[$key]);
$this->didRejectResult($address);
continue;
}
$address->attachUser($user);
}
return $page;
}
public function getQueryApplicationClass() {
return 'PhabricatorPeopleApplication';
}
}
diff --git a/src/applications/people/storage/PhabricatorUserEmail.php b/src/applications/people/storage/PhabricatorUserEmail.php
index 4e43b2fb41..d9866f2c43 100644
--- a/src/applications/people/storage/PhabricatorUserEmail.php
+++ b/src/applications/people/storage/PhabricatorUserEmail.php
@@ -1,327 +1,338 @@
<?php
/**
* @task restrictions Domain Restrictions
* @task email Email About Email
*/
final class PhabricatorUserEmail
extends PhabricatorUserDAO
implements
PhabricatorDestructibleInterface,
PhabricatorPolicyInterface {
protected $userPHID;
protected $address;
protected $isVerified;
protected $isPrimary;
protected $verificationCode;
private $user = self::ATTACHABLE;
const MAX_ADDRESS_LENGTH = 128;
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'address' => 'sort128',
'isVerified' => 'bool',
'isPrimary' => 'bool',
'verificationCode' => 'text64?',
),
self::CONFIG_KEY_SCHEMA => array(
'address' => array(
'columns' => array('address'),
'unique' => true,
),
'userPHID' => array(
'columns' => array('userPHID', 'isPrimary'),
),
),
) + parent::getConfiguration();
}
public function getPHIDType() {
return PhabricatorPeopleUserEmailPHIDType::TYPECONST;
}
public function getVerificationURI() {
return '/emailverify/'.$this->getVerificationCode().'/';
}
public function save() {
if (!$this->verificationCode) {
$this->setVerificationCode(Filesystem::readRandomCharacters(24));
}
return parent::save();
}
public function attachUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function getUser() {
return $this->assertAttached($this->user);
}
/* -( Domain Restrictions )------------------------------------------------ */
/**
* @task restrictions
*/
public static function isValidAddress($address) {
if (strlen($address) > self::MAX_ADDRESS_LENGTH) {
return false;
}
// Very roughly validate that this address isn't so mangled that a
// reasonable piece of code might completely misparse it. In particular,
// the major risks are:
//
// - `PhutilEmailAddress` needs to be able to extract the domain portion
// from it.
// - Reasonable mail adapters should be hard-pressed to interpret one
// address as several addresses.
//
// To this end, we're roughly verifying that there's some normal text, an
// "@" symbol, and then some more normal text.
$email_regex = '(^[a-z0-9_+.!-]+@[a-z0-9_+:.-]+\z)i';
if (!preg_match($email_regex, $address)) {
return false;
}
return true;
}
/**
* @task restrictions
*/
public static function describeValidAddresses() {
return pht(
'Email addresses should be in the form "user@domain.com". The maximum '.
'length of an email address is %s characters.',
new PhutilNumber(self::MAX_ADDRESS_LENGTH));
}
/**
* @task restrictions
*/
public static function isAllowedAddress($address) {
if (!self::isValidAddress($address)) {
return false;
}
$allowed_domains = PhabricatorEnv::getEnvConfig('auth.email-domains');
if (!$allowed_domains) {
return true;
}
$addr_obj = new PhutilEmailAddress($address);
$domain = $addr_obj->getDomainName();
if (!$domain) {
return false;
}
$lower_domain = phutil_utf8_strtolower($domain);
foreach ($allowed_domains as $allowed_domain) {
$lower_allowed = phutil_utf8_strtolower($allowed_domain);
if ($lower_allowed === $lower_domain) {
return true;
}
}
return false;
}
/**
* @task restrictions
*/
public static function describeAllowedAddresses() {
$domains = PhabricatorEnv::getEnvConfig('auth.email-domains');
if (!$domains) {
return null;
}
if (count($domains) == 1) {
return pht('Email address must be @%s', head($domains));
} else {
return pht(
'Email address must be at one of: %s',
implode(', ', $domains));
}
}
/**
* Check if this install requires email verification.
*
* @return bool True if email addresses must be verified.
*
* @task restrictions
*/
public static function isEmailVerificationRequired() {
// NOTE: Configuring required email domains implies required verification.
return PhabricatorEnv::getEnvConfig('auth.require-email-verification') ||
PhabricatorEnv::getEnvConfig('auth.email-domains');
}
/* -( Email About Email )-------------------------------------------------- */
/**
* Send a verification email from $user to this address.
*
* @param PhabricatorUser The user sending the verification.
* @return this
* @task email
*/
public function sendVerificationEmail(PhabricatorUser $user) {
$username = $user->getUsername();
$address = $this->getAddress();
$link = PhabricatorEnv::getProductionURI($this->getVerificationURI());
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$signature = null;
if (!$is_serious) {
- $signature = pht("Get Well Soon,\nPhabricator");
+ $signature = pht(
+ "Get Well Soon,\n%s",
+ PlatformSymbols::getPlatformServerName());
}
$body = sprintf(
"%s\n\n%s\n\n %s\n\n%s",
pht('Hi %s', $username),
pht(
'Please verify that you own this email address (%s) by '.
'clicking this link:',
$address),
$link,
$signature);
id(new PhabricatorMetaMTAMail())
->addRawTos(array($address))
->setForceDelivery(true)
- ->setSubject(pht('[Phabricator] Email Verification'))
+ ->setSubject(
+ pht(
+ '[%s] Email Verification',
+ PlatformSymbols::getPlatformServerName()))
->setBody($body)
->setRelatedPHID($user->getPHID())
->saveAndSend();
return $this;
}
/**
* Send a notification email from $user to this address, informing the
* recipient that this is no longer their account's primary address.
*
* @param PhabricatorUser The user sending the notification.
* @param PhabricatorUserEmail New primary email address.
* @return this
* @task email
*/
public function sendOldPrimaryEmail(
PhabricatorUser $user,
PhabricatorUserEmail $new) {
$username = $user->getUsername();
$old_address = $this->getAddress();
$new_address = $new->getAddress();
$body = sprintf(
"%s\n\n%s\n",
pht('Hi %s', $username),
pht(
'This email address (%s) is no longer your primary email address. '.
- 'Going forward, Phabricator will send all email to your new primary '.
- 'email address (%s).',
+ 'Going forward, all email will be sent to your new primary email '.
+ 'address (%s).',
$old_address,
$new_address));
id(new PhabricatorMetaMTAMail())
->addRawTos(array($old_address))
->setForceDelivery(true)
- ->setSubject(pht('[Phabricator] Primary Address Changed'))
+ ->setSubject(
+ pht(
+ '[%s] Primary Address Changed',
+ PlatformSymbols::getPlatformServerName()))
->setBody($body)
->setFrom($user->getPHID())
->setRelatedPHID($user->getPHID())
->saveAndSend();
}
/**
* Send a notification email from $user to this address, informing the
* recipient that this is now their account's new primary email address.
*
* @param PhabricatorUser The user sending the verification.
* @return this
* @task email
*/
public function sendNewPrimaryEmail(PhabricatorUser $user) {
$username = $user->getUsername();
$new_address = $this->getAddress();
$body = sprintf(
"%s\n\n%s\n",
pht('Hi %s', $username),
pht(
'This is now your primary email address (%s). Going forward, '.
- 'Phabricator will send all email here.',
+ 'all email will be sent here.',
$new_address));
id(new PhabricatorMetaMTAMail())
->addRawTos(array($new_address))
->setForceDelivery(true)
- ->setSubject(pht('[Phabricator] Primary Address Changed'))
+ ->setSubject(
+ pht(
+ '[%s] Primary Address Changed',
+ PlatformSymbols::getPlatformServerName()))
->setBody($body)
->setFrom($user->getPHID())
->setRelatedPHID($user->getPHID())
->saveAndSend();
return $this;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->delete();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
$user = $this->getUser();
if ($this->getIsSystemAgent() || $this->getIsMailingList()) {
return PhabricatorPolicies::POLICY_ADMIN;
}
return $user->getPHID();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
}
diff --git a/src/applications/people/xaction/PhabricatorUserApproveTransaction.php b/src/applications/people/xaction/PhabricatorUserApproveTransaction.php
index 1084f0c8fa..213b381d1a 100644
--- a/src/applications/people/xaction/PhabricatorUserApproveTransaction.php
+++ b/src/applications/people/xaction/PhabricatorUserApproveTransaction.php
@@ -1,95 +1,101 @@
<?php
final class PhabricatorUserApproveTransaction
extends PhabricatorUserTransactionType {
const TRANSACTIONTYPE = 'user.approve';
public function generateOldValue($object) {
return (bool)$object->getIsApproved();
}
public function generateNewValue($object, $value) {
return (bool)$value;
}
public function applyInternalEffects($object, $value) {
$object->setIsApproved((int)$value);
}
public function applyExternalEffects($object, $value) {
$user = $object;
$actor = $this->getActor();
$title = pht(
- 'Phabricator Account "%s" Approved',
+ '%s Account "%s" Approved',
+ PlatformSymbols::getPlatformServerName(),
$user->getUsername());
$body = sprintf(
"%s\n\n %s\n\n",
pht(
- 'Your Phabricator account (%s) has been approved by %s. You can '.
+ 'Your %s account (%s) has been approved by %s. You can '.
'login here:',
+ PlatformSymbols::getPlatformServerName(),
$user->getUsername(),
$actor->getUsername()),
PhabricatorEnv::getProductionURI('/'));
$mail = id(new PhabricatorMetaMTAMail())
->addTos(array($user->getPHID()))
->addCCs(array($actor->getPHID()))
- ->setSubject('[Phabricator] '.$title)
+ ->setSubject(
+ pht(
+ '[%s] %s',
+ PlatformSymbols::getPlatformServerName(),
+ $title))
->setForceDelivery(true)
->setBody($body)
->saveAndSend();
}
public function getTitle() {
$new = $this->getNewValue();
if ($new) {
return pht(
'%s approved this user.',
$this->renderAuthor());
} else {
return pht(
'%s rejected this user.',
$this->renderAuthor());
}
}
public function shouldHideForFeed() {
return true;
}
public function validateTransactions($object, array $xactions) {
$actor = $this->getActor();
$errors = array();
foreach ($xactions as $xaction) {
$is_approved = (bool)$object->getIsApproved();
if ((bool)$xaction->getNewValue() === $is_approved) {
continue;
}
$is_admin = $actor->getIsAdmin();
$is_omnipotent = $actor->isOmnipotent();
if (!$is_admin && !$is_omnipotent) {
$errors[] = $this->newInvalidError(
pht('You must be an administrator to approve users.'));
}
}
return $errors;
}
public function getRequiredCapabilities(
$object,
PhabricatorApplicationTransaction $xaction) {
// Unlike normal user edits, approvals require admin permissions, which
// is enforced by validateTransactions().
return null;
}
}
diff --git a/src/applications/phame/query/PhameBlogQuery.php b/src/applications/phame/query/PhameBlogQuery.php
index b4018c78eb..b730f3c4aa 100644
--- a/src/applications/phame/query/PhameBlogQuery.php
+++ b/src/applications/phame/query/PhameBlogQuery.php
@@ -1,150 +1,146 @@
<?php
final class PhameBlogQuery extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $domain;
private $statuses;
private $needBloggers;
private $needProfileImage;
private $needHeaderImage;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withDomain($domain) {
$this->domain = $domain;
return $this;
}
public function withStatuses(array $status) {
$this->statuses = $status;
return $this;
}
public function needProfileImage($need) {
$this->needProfileImage = $need;
return $this;
}
public function needHeaderImage($need) {
$this->needHeaderImage = $need;
return $this;
}
public function newResultObject() {
return new PhameBlog();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'b.status IN (%Ls)',
$this->statuses);
}
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'b.id IN (%Ls)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'b.phid IN (%Ls)',
$this->phids);
}
if ($this->domain !== null) {
$where[] = qsprintf(
$conn,
'b.domain = %s',
$this->domain);
}
return $where;
}
protected function didFilterPage(array $blogs) {
if ($this->needProfileImage) {
$default = null;
$file_phids = mpull($blogs, 'getProfileImagePHID');
$file_phids = array_filter($file_phids);
if ($file_phids) {
$files = id(new PhabricatorFileQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($file_phids)
->execute();
$files = mpull($files, null, 'getPHID');
} else {
$files = array();
}
foreach ($blogs as $blog) {
$file = idx($files, $blog->getProfileImagePHID());
if (!$file) {
if (!$default) {
$default = PhabricatorFile::loadBuiltin(
$this->getViewer(),
'blog.png');
}
$file = $default;
}
$blog->attachProfileImageFile($file);
}
}
if ($this->needHeaderImage) {
$file_phids = mpull($blogs, 'getHeaderImagePHID');
$file_phids = array_filter($file_phids);
if ($file_phids) {
$files = id(new PhabricatorFileQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($file_phids)
->execute();
$files = mpull($files, null, 'getPHID');
} else {
$files = array();
}
foreach ($blogs as $blog) {
$file = idx($files, $blog->getHeaderImagePHID());
if ($file) {
$blog->attachHeaderImageFile($file);
}
}
}
return $blogs;
}
public function getQueryApplicationClass() {
// TODO: Can we set this without breaking public blogs?
return null;
}
protected function getPrimaryTableAlias() {
return 'b';
}
}
diff --git a/src/applications/phame/query/PhamePostQuery.php b/src/applications/phame/query/PhamePostQuery.php
index d7396e553f..61ee4f2a25 100644
--- a/src/applications/phame/query/PhamePostQuery.php
+++ b/src/applications/phame/query/PhamePostQuery.php
@@ -1,190 +1,186 @@
<?php
final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $blogPHIDs;
private $bloggerPHIDs;
private $visibility;
private $publishedAfter;
private $phids;
private $needHeaderImage;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withBloggerPHIDs(array $blogger_phids) {
$this->bloggerPHIDs = $blogger_phids;
return $this;
}
public function withBlogPHIDs(array $blog_phids) {
$this->blogPHIDs = $blog_phids;
return $this;
}
public function withVisibility(array $visibility) {
$this->visibility = $visibility;
return $this;
}
public function withPublishedAfter($time) {
$this->publishedAfter = $time;
return $this;
}
public function needHeaderImage($need) {
$this->needHeaderImage = $need;
return $this;
}
public function newResultObject() {
return new PhamePost();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $posts) {
// We require blogs to do visibility checks, so load them unconditionally.
$blog_phids = mpull($posts, 'getBlogPHID');
$blogs = id(new PhameBlogQuery())
->setViewer($this->getViewer())
->needProfileImage(true)
->withPHIDs($blog_phids)
->execute();
$blogs = mpull($blogs, null, 'getPHID');
foreach ($posts as $key => $post) {
$blog_phid = $post->getBlogPHID();
$blog = idx($blogs, $blog_phid);
if (!$blog) {
$this->didRejectResult($post);
unset($posts[$key]);
continue;
}
$post->attachBlog($blog);
}
if ($this->needHeaderImage) {
$file_phids = mpull($posts, 'getHeaderImagePHID');
$file_phids = array_filter($file_phids);
if ($file_phids) {
$files = id(new PhabricatorFileQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($file_phids)
->execute();
$files = mpull($files, null, 'getPHID');
} else {
$files = array();
}
foreach ($posts as $post) {
$file = idx($files, $post->getHeaderImagePHID());
if ($file) {
$post->attachHeaderImageFile($file);
}
}
}
return $posts;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'p.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'p.phid IN (%Ls)',
$this->phids);
}
if ($this->bloggerPHIDs !== null) {
$where[] = qsprintf(
$conn,
'p.bloggerPHID IN (%Ls)',
$this->bloggerPHIDs);
}
if ($this->visibility !== null) {
$where[] = qsprintf(
$conn,
'p.visibility IN (%Ld)',
$this->visibility);
}
if ($this->publishedAfter !== null) {
$where[] = qsprintf(
$conn,
'p.datePublished > %d',
$this->publishedAfter);
}
if ($this->blogPHIDs !== null) {
$where[] = qsprintf(
$conn,
'p.blogPHID in (%Ls)',
$this->blogPHIDs);
}
return $where;
}
public function getBuiltinOrders() {
return array(
'datePublished' => array(
'vector' => array('datePublished', 'id'),
'name' => pht('Publish Date'),
),
) + parent::getBuiltinOrders();
}
public function getOrderableColumns() {
return parent::getOrderableColumns() + array(
'datePublished' => array(
'table' => $this->getPrimaryTableAlias(),
'column' => 'datePublished',
'type' => 'int',
'reverse' => false,
),
);
}
protected function newPagingMapFromPartialObject($object) {
return array(
'id' => (int)$object->getID(),
'datePublished' => (int)$object->getDatePublished(),
);
}
public function getQueryApplicationClass() {
// TODO: Does setting this break public blogs?
return null;
}
protected function getPrimaryTableAlias() {
return 'p';
}
}
diff --git a/src/applications/phame/storage/PhameBlog.php b/src/applications/phame/storage/PhameBlog.php
index 1722e63bfe..95507d61dc 100644
--- a/src/applications/phame/storage/PhameBlog.php
+++ b/src/applications/phame/storage/PhameBlog.php
@@ -1,397 +1,397 @@
<?php
final class PhameBlog extends PhameDAO
implements
PhabricatorPolicyInterface,
PhabricatorMarkupInterface,
PhabricatorSubscribableInterface,
PhabricatorFlaggableInterface,
PhabricatorProjectInterface,
PhabricatorDestructibleInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorConduitResultInterface,
PhabricatorFulltextInterface,
PhabricatorFerretInterface {
protected $name;
protected $subtitle;
protected $description;
protected $domain;
protected $domainFullURI;
protected $parentSite;
protected $parentDomain;
protected $configData;
protected $creatorPHID;
protected $viewPolicy;
protected $editPolicy;
protected $interactPolicy;
protected $status;
protected $mailKey;
protected $profileImagePHID;
protected $headerImagePHID;
private $profileImageFile = self::ATTACHABLE;
private $headerImageFile = self::ATTACHABLE;
const STATUS_ACTIVE = 'active';
const STATUS_ARCHIVED = 'archived';
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'configData' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text64',
'subtitle' => 'text64',
'description' => 'text',
'domain' => 'text128?',
'domainFullURI' => 'text128?',
'parentSite' => 'text128?',
'parentDomain' => 'text128?',
'status' => 'text32',
'mailKey' => 'bytes20',
'profileImagePHID' => 'phid?',
'headerImagePHID' => 'phid?',
'editPolicy' => 'policy',
'viewPolicy' => 'policy',
'interactPolicy' => 'policy',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
),
'domain' => array(
'columns' => array('domain'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public function save() {
if (!$this->getMailKey()) {
$this->setMailKey(Filesystem::readRandomCharacters(20));
}
return parent::save();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPhameBlogPHIDType::TYPECONST);
}
public static function initializeNewBlog(PhabricatorUser $actor) {
$blog = id(new PhameBlog())
->setCreatorPHID($actor->getPHID())
->setStatus(self::STATUS_ACTIVE)
->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy())
->setEditPolicy(PhabricatorPolicies::POLICY_USER)
->setInteractPolicy(PhabricatorPolicies::POLICY_USER);
return $blog;
}
public function isArchived() {
return ($this->getStatus() == self::STATUS_ARCHIVED);
}
public static function getStatusNameMap() {
return array(
self::STATUS_ACTIVE => pht('Active'),
self::STATUS_ARCHIVED => pht('Archived'),
);
}
/**
* Makes sure a given custom blog uri is properly configured in DNS
* to point at this Phabricator instance. If there is an error in
* the configuration, return a string describing the error and how
* to fix it. If there is no error, return an empty string.
*
* @return string
*/
public function validateCustomDomain($domain_full_uri) {
$example_domain = 'http://blog.example.com/';
$label = pht('Invalid');
// note this "uri" should be pretty busted given the desired input
// so just use it to test if there's a protocol specified
$uri = new PhutilURI($domain_full_uri);
$domain = $uri->getDomain();
$protocol = $uri->getProtocol();
$path = $uri->getPath();
$supported_protocols = array('http', 'https');
if (!in_array($protocol, $supported_protocols)) {
return pht(
'The custom domain should include a valid protocol in the URI '.
'(for example, "%s"). Valid protocols are "http" or "https".',
$example_domain);
}
if (strlen($path) && $path != '/') {
return pht(
'The custom domain should not specify a path (hosting a Phame '.
'blog at a path is currently not supported). Instead, just provide '.
'the bare domain name (for example, "%s").',
$example_domain);
}
if (strpos($domain, '.') === false) {
return pht(
'The custom domain should contain at least one dot (.) because '.
'some browsers fail to set cookies on domains without a dot. '.
'Instead, use a normal looking domain name like "%s".',
$example_domain);
}
if (!PhabricatorEnv::getEnvConfig('policy.allow-public')) {
$href = PhabricatorEnv::getProductionURI(
'/config/edit/policy.allow-public/');
return pht(
- 'For custom domains to work, this Phabricator instance must be '.
+ 'For custom domains to work, this this server must be '.
'configured to allow the public access policy. Configure this '.
'setting %s, or ask an administrator to configure this setting. '.
'The domain can be specified later once this setting has been '.
'changed.',
phutil_tag(
'a',
array('href' => $href),
pht('here')));
}
return null;
}
public function getLiveURI() {
if (strlen($this->getDomain())) {
return $this->getExternalLiveURI();
} else {
return $this->getInternalLiveURI();
}
}
public function getExternalLiveURI() {
$uri = new PhutilURI($this->getDomainFullURI());
PhabricatorEnv::requireValidRemoteURIForLink($uri);
return (string)$uri;
}
public function getExternalParentURI() {
$uri = $this->getParentDomain();
PhabricatorEnv::requireValidRemoteURIForLink($uri);
return (string)$uri;
}
public function getInternalLiveURI() {
return '/phame/live/'.$this->getID().'/';
}
public function getViewURI() {
return '/phame/blog/view/'.$this->getID().'/';
}
public function getManageURI() {
return '/phame/blog/manage/'.$this->getID().'/';
}
public function getProfileImageURI() {
return $this->getProfileImageFile()->getBestURI();
}
public function attachProfileImageFile(PhabricatorFile $file) {
$this->profileImageFile = $file;
return $this;
}
public function getProfileImageFile() {
return $this->assertAttached($this->profileImageFile);
}
public function getHeaderImageURI() {
return $this->getHeaderImageFile()->getBestURI();
}
public function attachHeaderImageFile(PhabricatorFile $file) {
$this->headerImageFile = $file;
return $this;
}
public function getHeaderImageFile() {
return $this->assertAttached($this->headerImageFile);
}
/* -( PhabricatorPolicyInterface Implementation )-------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_INTERACT,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_INTERACT:
return $this->getInteractPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
$can_edit = PhabricatorPolicyCapability::CAN_EDIT;
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
// Users who can edit or post to a blog can always view it.
if (PhabricatorPolicyFilter::hasCapability($user, $this, $can_edit)) {
return true;
}
break;
}
return false;
}
public function describeAutomaticCapability($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return pht(
'Users who can edit a blog can always view it.');
}
return null;
}
/* -( PhabricatorMarkupInterface Implementation )-------------------------- */
public function getMarkupFieldKey($field) {
$content = $this->getMarkupText($field);
return PhabricatorMarkupEngine::digestRemarkupContent($this, $content);
}
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newPhameMarkupEngine();
}
public function getMarkupText($field) {
return $this->getDescription();
}
public function didMarkupText(
$field,
$output,
PhutilMarkupEngine $engine) {
return $output;
}
public function shouldUseMarkupCache($field) {
return (bool)$this->getPHID();
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$posts = id(new PhamePostQuery())
->setViewer($engine->getViewer())
->withBlogPHIDs(array($this->getPHID()))
->execute();
foreach ($posts as $post) {
$engine->destroyObject($post);
}
$this->delete();
$this->saveTransaction();
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhameBlogEditor();
}
public function getApplicationTransactionTemplate() {
return new PhameBlogTransaction();
}
/* -( PhabricatorSubscribableInterface Implementation )-------------------- */
public function isAutomaticallySubscribed($phid) {
return false;
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('name')
->setType('string')
->setDescription(pht('The name of the blog.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('description')
->setType('string')
->setDescription(pht('Blog description.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('status')
->setType('string')
->setDescription(pht('Archived or active status.')),
);
}
public function getFieldValuesForConduit() {
return array(
'name' => $this->getName(),
'description' => $this->getDescription(),
'status' => $this->getStatus(),
);
}
public function getConduitSearchAttachments() {
return array();
}
/* -( PhabricatorFulltextInterface )--------------------------------------- */
public function newFulltextEngine() {
return new PhameBlogFulltextEngine();
}
/* -( PhabricatorFerretInterface )----------------------------------------- */
public function newFerretEngine() {
return new PhameBlogFerretEngine();
}
}
diff --git a/src/applications/pholio/query/PholioImageQuery.php b/src/applications/pholio/query/PholioImageQuery.php
index 0d64540f91..69d890ab98 100644
--- a/src/applications/pholio/query/PholioImageQuery.php
+++ b/src/applications/pholio/query/PholioImageQuery.php
@@ -1,162 +1,158 @@
<?php
final class PholioImageQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $mockPHIDs;
private $mocks;
private $needInlineComments;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withMocks(array $mocks) {
assert_instances_of($mocks, 'PholioMock');
$mocks = mpull($mocks, null, 'getPHID');
$this->mocks = $mocks;
$this->mockPHIDs = array_keys($mocks);
return $this;
}
public function withMockPHIDs(array $mock_phids) {
$this->mockPHIDs = $mock_phids;
return $this;
}
public function needInlineComments($need_inline_comments) {
$this->needInlineComments = $need_inline_comments;
return $this;
}
public function newResultObject() {
return new PholioImage();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->mockPHIDs !== null) {
$where[] = qsprintf(
$conn,
'mockPHID IN (%Ls)',
$this->mockPHIDs);
}
return $where;
}
protected function willFilterPage(array $images) {
assert_instances_of($images, 'PholioImage');
$mock_phids = array();
foreach ($images as $image) {
if (!$image->hasMock()) {
continue;
}
$mock_phids[] = $image->getMockPHID();
}
if ($mock_phids) {
if ($this->mocks) {
$mocks = $this->mocks;
} else {
$mocks = id(new PholioMockQuery())
->setViewer($this->getViewer())
->withPHIDs($mock_phids)
->execute();
}
$mocks = mpull($mocks, null, 'getPHID');
foreach ($images as $key => $image) {
if (!$image->hasMock()) {
continue;
}
$mock = idx($mocks, $image->getMockPHID());
if (!$mock) {
unset($images[$key]);
$this->didRejectResult($image);
continue;
}
$image->attachMock($mock);
}
}
return $images;
}
protected function didFilterPage(array $images) {
assert_instances_of($images, 'PholioImage');
$file_phids = mpull($images, 'getFilePHID');
$all_files = id(new PhabricatorFileQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($file_phids)
->execute();
$all_files = mpull($all_files, null, 'getPHID');
if ($this->needInlineComments) {
// Only load inline comments the viewer has permission to see.
$all_inline_comments = id(new PholioTransactionComment())->loadAllWhere(
'imageID IN (%Ld)
AND (transactionPHID IS NOT NULL OR authorPHID = %s)',
mpull($images, 'getID'),
$this->getViewer()->getPHID());
$all_inline_comments = mgroup($all_inline_comments, 'getImageID');
}
foreach ($images as $image) {
$file = idx($all_files, $image->getFilePHID());
if (!$file) {
$file = PhabricatorFile::loadBuiltin($this->getViewer(), 'missing.png');
}
$image->attachFile($file);
if ($this->needInlineComments) {
$inlines = idx($all_inline_comments, $image->getID(), array());
$image->attachInlineComments($inlines);
}
}
return $images;
}
public function getQueryApplicationClass() {
return 'PhabricatorPholioApplication';
}
}
diff --git a/src/applications/phortune/query/PhortuneAccountEmailQuery.php b/src/applications/phortune/query/PhortuneAccountEmailQuery.php
index 0bdfdb78dc..0e0a668b8c 100644
--- a/src/applications/phortune/query/PhortuneAccountEmailQuery.php
+++ b/src/applications/phortune/query/PhortuneAccountEmailQuery.php
@@ -1,117 +1,113 @@
<?php
final class PhortuneAccountEmailQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $accountPHIDs;
private $addressKeys;
private $statuses;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withAccountPHIDs(array $phids) {
$this->accountPHIDs = $phids;
return $this;
}
public function withAddressKeys(array $keys) {
$this->addressKeys = $keys;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function newResultObject() {
return new PhortuneAccountEmail();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $addresses) {
$accounts = id(new PhortuneAccountQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs(mpull($addresses, 'getAccountPHID'))
->execute();
$accounts = mpull($accounts, null, 'getPHID');
foreach ($addresses as $key => $address) {
$account = idx($accounts, $address->getAccountPHID());
if (!$account) {
$this->didRejectResult($addresses[$key]);
unset($addresses[$key]);
continue;
}
$address->attachAccount($account);
}
return $addresses;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'address.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'address.phid IN (%Ls)',
$this->phids);
}
if ($this->accountPHIDs !== null) {
$where[] = qsprintf(
$conn,
'address.accountPHID IN (%Ls)',
$this->accountPHIDs);
}
if ($this->addressKeys !== null) {
$where[] = qsprintf(
$conn,
'address.addressKey IN (%Ls)',
$this->addressKeys);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'address.status IN (%Ls)',
$this->statuses);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorPhortuneApplication';
}
protected function getPrimaryTableAlias() {
return 'address';
}
}
diff --git a/src/applications/phortune/query/PhortuneAccountQuery.php b/src/applications/phortune/query/PhortuneAccountQuery.php
index 70c12d9722..43c7c5f976 100644
--- a/src/applications/phortune/query/PhortuneAccountQuery.php
+++ b/src/applications/phortune/query/PhortuneAccountQuery.php
@@ -1,138 +1,134 @@
<?php
final class PhortuneAccountQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $memberPHIDs;
public static function loadAccountsForUser(
PhabricatorUser $user,
PhabricatorContentSource $content_source) {
$accounts = id(new PhortuneAccountQuery())
->setViewer($user)
->withMemberPHIDs(array($user->getPHID()))
->execute();
if (!$accounts) {
$accounts = array(
PhortuneAccount::createNewAccount($user, $content_source),
);
}
$accounts = mpull($accounts, null, 'getPHID');
return $accounts;
}
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withMemberPHIDs(array $phids) {
$this->memberPHIDs = $phids;
return $this;
}
public function newResultObject() {
return new PhortuneAccount();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $accounts) {
$query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(mpull($accounts, 'getPHID'))
->withEdgeTypes(
array(
PhortuneAccountHasMemberEdgeType::EDGECONST,
PhortuneAccountHasMerchantEdgeType::EDGECONST,
));
$query->execute();
foreach ($accounts as $account) {
$member_phids = $query->getDestinationPHIDs(
array(
$account->getPHID(),
),
array(
PhortuneAccountHasMemberEdgeType::EDGECONST,
));
$member_phids = array_reverse($member_phids);
$account->attachMemberPHIDs($member_phids);
$merchant_phids = $query->getDestinationPHIDs(
array(
$account->getPHID(),
),
array(
PhortuneAccountHasMerchantEdgeType::EDGECONST,
));
$merchant_phids = array_reverse($merchant_phids);
$account->attachMerchantPHIDs($merchant_phids);
}
return $accounts;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'a.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'a.phid IN (%Ls)',
$this->phids);
}
if ($this->memberPHIDs !== null) {
$where[] = qsprintf(
$conn,
'm.dst IN (%Ls)',
$this->memberPHIDs);
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->memberPHIDs !== null) {
$joins[] = qsprintf(
$conn,
'LEFT JOIN %T m ON a.phid = m.src AND m.type = %d',
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
PhortuneAccountHasMemberEdgeType::EDGECONST);
}
return $joins;
}
public function getQueryApplicationClass() {
return 'PhabricatorPhortuneApplication';
}
protected function getPrimaryTableAlias() {
return 'a';
}
}
diff --git a/src/applications/phortune/query/PhortuneMerchantQuery.php b/src/applications/phortune/query/PhortuneMerchantQuery.php
index aef7d8aaf1..2c9aefc74d 100644
--- a/src/applications/phortune/query/PhortuneMerchantQuery.php
+++ b/src/applications/phortune/query/PhortuneMerchantQuery.php
@@ -1,193 +1,189 @@
<?php
final class PhortuneMerchantQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $memberPHIDs;
private $needProfileImage;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withMemberPHIDs(array $member_phids) {
$this->memberPHIDs = $member_phids;
return $this;
}
public function needProfileImage($need) {
$this->needProfileImage = $need;
return $this;
}
public function newResultObject() {
return new PhortuneMerchant();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $merchants) {
$query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(mpull($merchants, 'getPHID'))
->withEdgeTypes(array(PhortuneMerchantHasMemberEdgeType::EDGECONST));
$query->execute();
foreach ($merchants as $merchant) {
$member_phids = $query->getDestinationPHIDs(array($merchant->getPHID()));
$member_phids = array_reverse($member_phids);
$merchant->attachMemberPHIDs($member_phids);
}
if ($this->needProfileImage) {
$default = null;
$file_phids = mpull($merchants, 'getProfileImagePHID');
$file_phids = array_filter($file_phids);
if ($file_phids) {
$files = id(new PhabricatorFileQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($file_phids)
->execute();
$files = mpull($files, null, 'getPHID');
} else {
$files = array();
}
foreach ($merchants as $merchant) {
$file = idx($files, $merchant->getProfileImagePHID());
if (!$file) {
if (!$default) {
$default = PhabricatorFile::loadBuiltin(
$this->getViewer(),
'merchant.png');
}
$file = $default;
}
$merchant->attachProfileImageFile($file);
}
}
return $merchants;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'merchant.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'merchant.phid IN (%Ls)',
$this->phids);
}
if ($this->memberPHIDs !== null) {
$where[] = qsprintf(
$conn,
'e.dst IN (%Ls)',
$this->memberPHIDs);
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->memberPHIDs !== null) {
$joins[] = qsprintf(
$conn,
'LEFT JOIN %T e ON merchant.phid = e.src AND e.type = %d',
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
PhortuneMerchantHasMemberEdgeType::EDGECONST);
}
return $joins;
}
public function getQueryApplicationClass() {
return 'PhabricatorPhortuneApplication';
}
protected function getPrimaryTableAlias() {
return 'merchant';
}
public static function canViewersEditMerchants(
array $viewer_phids,
array $merchant_phids) {
// See T13366 for some discussion. This is an unusual caching construct to
// make policy filtering of Accounts easier.
foreach ($viewer_phids as $key => $viewer_phid) {
if (!$viewer_phid) {
unset($viewer_phids[$key]);
}
}
if (!$viewer_phids) {
return array();
}
$cache_key = 'phortune.merchant.can-edit';
$cache = PhabricatorCaches::getRequestCache();
$cache_data = $cache->getKey($cache_key);
if (!$cache_data) {
$cache_data = array();
}
$load_phids = array();
foreach ($viewer_phids as $viewer_phid) {
if (!isset($cache_data[$viewer_phid])) {
$load_phids[] = $viewer_phid;
}
}
$did_write = false;
foreach ($load_phids as $load_phid) {
$merchants = id(new self())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withMemberPHIDs(array($load_phid))
->execute();
foreach ($merchants as $merchant) {
$cache_data[$load_phid][$merchant->getPHID()] = true;
$did_write = true;
}
}
if ($did_write) {
$cache->setKey($cache_key, $cache_data);
}
$results = array();
foreach ($viewer_phids as $viewer_phid) {
foreach ($merchant_phids as $merchant_phid) {
if (!isset($cache_data[$viewer_phid][$merchant_phid])) {
continue;
}
$results[$viewer_phid][$merchant_phid] = true;
}
}
return $results;
}
}
diff --git a/src/applications/phortune/query/PhortunePaymentMethodQuery.php b/src/applications/phortune/query/PhortunePaymentMethodQuery.php
index 013fa147ec..b95881d3a7 100644
--- a/src/applications/phortune/query/PhortunePaymentMethodQuery.php
+++ b/src/applications/phortune/query/PhortunePaymentMethodQuery.php
@@ -1,150 +1,146 @@
<?php
final class PhortunePaymentMethodQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $accountPHIDs;
private $merchantPHIDs;
private $statuses;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withAccountPHIDs(array $phids) {
$this->accountPHIDs = $phids;
return $this;
}
public function withMerchantPHIDs(array $phids) {
$this->merchantPHIDs = $phids;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function newResultObject() {
return new PhortunePaymentMethod();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $methods) {
$accounts = id(new PhortuneAccountQuery())
->setViewer($this->getViewer())
->withPHIDs(mpull($methods, 'getAccountPHID'))
->execute();
$accounts = mpull($accounts, null, 'getPHID');
foreach ($methods as $key => $method) {
$account = idx($accounts, $method->getAccountPHID());
if (!$account) {
unset($methods[$key]);
$this->didRejectResult($method);
continue;
}
$method->attachAccount($account);
}
if (!$methods) {
return $methods;
}
$merchants = id(new PhortuneMerchantQuery())
->setViewer($this->getViewer())
->withPHIDs(mpull($methods, 'getMerchantPHID'))
->execute();
$merchants = mpull($merchants, null, 'getPHID');
foreach ($methods as $key => $method) {
$merchant = idx($merchants, $method->getMerchantPHID());
if (!$merchant) {
unset($methods[$key]);
$this->didRejectResult($method);
continue;
}
$method->attachMerchant($merchant);
}
if (!$methods) {
return $methods;
}
$provider_configs = id(new PhortunePaymentProviderConfigQuery())
->setViewer($this->getViewer())
->withPHIDs(mpull($methods, 'getProviderPHID'))
->execute();
$provider_configs = mpull($provider_configs, null, 'getPHID');
foreach ($methods as $key => $method) {
$provider_config = idx($provider_configs, $method->getProviderPHID());
if (!$provider_config) {
unset($methods[$key]);
$this->didRejectResult($method);
continue;
}
$method->attachProviderConfig($provider_config);
}
return $methods;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->accountPHIDs !== null) {
$where[] = qsprintf(
$conn,
'accountPHID IN (%Ls)',
$this->accountPHIDs);
}
if ($this->merchantPHIDs !== null) {
$where[] = qsprintf(
$conn,
'merchantPHID IN (%Ls)',
$this->merchantPHIDs);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'status IN (%Ls)',
$this->statuses);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorPhortuneApplication';
}
}
diff --git a/src/applications/phortune/query/PhortuneSubscriptionQuery.php b/src/applications/phortune/query/PhortuneSubscriptionQuery.php
index de0fee1980..5622578738 100644
--- a/src/applications/phortune/query/PhortuneSubscriptionQuery.php
+++ b/src/applications/phortune/query/PhortuneSubscriptionQuery.php
@@ -1,204 +1,200 @@
<?php
final class PhortuneSubscriptionQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $accountPHIDs;
private $merchantPHIDs;
private $statuses;
private $paymentMethodPHIDs;
private $needTriggers;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withAccountPHIDs(array $account_phids) {
$this->accountPHIDs = $account_phids;
return $this;
}
public function withMerchantPHIDs(array $merchant_phids) {
$this->merchantPHIDs = $merchant_phids;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function withPaymentMethodPHIDs(array $method_phids) {
$this->paymentMethodPHIDs = $method_phids;
return $this;
}
public function needTriggers($need_triggers) {
$this->needTriggers = $need_triggers;
return $this;
}
public function newResultObject() {
return new PhortuneSubscription();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $subscriptions) {
$accounts = id(new PhortuneAccountQuery())
->setViewer($this->getViewer())
->withPHIDs(mpull($subscriptions, 'getAccountPHID'))
->execute();
$accounts = mpull($accounts, null, 'getPHID');
foreach ($subscriptions as $key => $subscription) {
$account = idx($accounts, $subscription->getAccountPHID());
if (!$account) {
unset($subscriptions[$key]);
$this->didRejectResult($subscription);
continue;
}
$subscription->attachAccount($account);
}
if (!$subscriptions) {
return $subscriptions;
}
$merchants = id(new PhortuneMerchantQuery())
->setViewer($this->getViewer())
->withPHIDs(mpull($subscriptions, 'getMerchantPHID'))
->execute();
$merchants = mpull($merchants, null, 'getPHID');
foreach ($subscriptions as $key => $subscription) {
$merchant = idx($merchants, $subscription->getMerchantPHID());
if (!$merchant) {
unset($subscriptions[$key]);
$this->didRejectResult($subscription);
continue;
}
$subscription->attachMerchant($merchant);
}
if (!$subscriptions) {
return $subscriptions;
}
$implementations = array();
$subscription_map = mgroup($subscriptions, 'getSubscriptionClass');
foreach ($subscription_map as $class => $class_subscriptions) {
$sub = newv($class, array());
$impl_objects = $sub->loadImplementationsForRefs(
$this->getViewer(),
mpull($class_subscriptions, 'getSubscriptionRef'));
$implementations += mpull($impl_objects, null, 'getRef');
}
foreach ($subscriptions as $key => $subscription) {
$ref = $subscription->getSubscriptionRef();
$implementation = idx($implementations, $ref);
if (!$implementation) {
unset($subscriptions[$key]);
$this->didRejectResult($subscription);
continue;
}
$subscription->attachImplementation($implementation);
}
if (!$subscriptions) {
return $subscriptions;
}
if ($this->needTriggers) {
$trigger_phids = mpull($subscriptions, 'getTriggerPHID');
$triggers = id(new PhabricatorWorkerTriggerQuery())
->setViewer($this->getViewer())
->withPHIDs($trigger_phids)
->needEvents(true)
->execute();
$triggers = mpull($triggers, null, 'getPHID');
foreach ($subscriptions as $key => $subscription) {
$trigger = idx($triggers, $subscription->getTriggerPHID());
if (!$trigger) {
unset($subscriptions[$key]);
$this->didRejectResult($subscription);
continue;
}
$subscription->attachTrigger($trigger);
}
}
return $subscriptions;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'subscription.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'subscription.phid IN (%Ls)',
$this->phids);
}
if ($this->accountPHIDs !== null) {
$where[] = qsprintf(
$conn,
'subscription.accountPHID IN (%Ls)',
$this->accountPHIDs);
}
if ($this->merchantPHIDs !== null) {
$where[] = qsprintf(
$conn,
'subscription.merchantPHID IN (%Ls)',
$this->merchantPHIDs);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'subscription.status IN (%Ls)',
$this->statuses);
}
if ($this->paymentMethodPHIDs !== null) {
$where[] = qsprintf(
$conn,
'subscription.defaultPaymentMethodPHID IN (%Ls)',
$this->paymentMethodPHIDs);
}
return $where;
}
protected function getPrimaryTableAlias() {
return 'subscription';
}
public function getQueryApplicationClass() {
return 'PhabricatorPhortuneApplication';
}
}
diff --git a/src/applications/phragment/application/PhabricatorPhragmentApplication.php b/src/applications/phragment/application/PhabricatorPhragmentApplication.php
deleted file mode 100644
index 164e0293fd..0000000000
--- a/src/applications/phragment/application/PhabricatorPhragmentApplication.php
+++ /dev/null
@@ -1,66 +0,0 @@
-<?php
-
-final class PhabricatorPhragmentApplication extends PhabricatorApplication {
-
- public function getName() {
- return pht('Phragment');
- }
-
- public function getBaseURI() {
- return '/phragment/';
- }
-
- public function getShortDescription() {
- return pht('Versioned Artifact Storage');
- }
-
- public function getIcon() {
- return 'fa-floppy-o';
- }
-
- public function getTitleGlyph() {
- return "\xE2\x96\x9B";
- }
-
- public function getApplicationGroup() {
- return self::GROUP_UTILITIES;
- }
-
- public function isPrototype() {
- return true;
- }
-
- public function getRoutes() {
- return array(
- '/phragment/' => array(
- '' => 'PhragmentBrowseController',
- 'browse/(?P<dblob>.*)' => 'PhragmentBrowseController',
- 'create/(?P<dblob>.*)' => 'PhragmentCreateController',
- 'update/(?P<dblob>.+)' => 'PhragmentUpdateController',
- 'policy/(?P<dblob>.+)' => 'PhragmentPolicyController',
- 'history/(?P<dblob>.+)' => 'PhragmentHistoryController',
- 'zip/(?P<dblob>.+)' => 'PhragmentZIPController',
- 'zip@(?P<snapshot>[^/]+)/(?P<dblob>.+)' => 'PhragmentZIPController',
- 'version/(?P<id>[0-9]*)/' => 'PhragmentVersionController',
- 'patch/(?P<aid>[0-9x]*)/(?P<bid>[0-9]*)/' => 'PhragmentPatchController',
- 'revert/(?P<id>[0-9]*)/(?P<dblob>.*)' => 'PhragmentRevertController',
- 'snapshot/' => array(
- 'create/(?P<dblob>.*)' => 'PhragmentSnapshotCreateController',
- 'view/(?P<id>[0-9]*)/' => 'PhragmentSnapshotViewController',
- 'delete/(?P<id>[0-9]*)/' => 'PhragmentSnapshotDeleteController',
- 'promote/' => array(
- 'latest/(?P<dblob>.*)' => 'PhragmentSnapshotPromoteController',
- '(?P<id>[0-9]*)/' => 'PhragmentSnapshotPromoteController',
- ),
- ),
- ),
- );
- }
-
- protected function getCustomCapabilities() {
- return array(
- PhragmentCanCreateCapability::CAPABILITY => array(),
- );
- }
-
-}
diff --git a/src/applications/phragment/capability/PhragmentCanCreateCapability.php b/src/applications/phragment/capability/PhragmentCanCreateCapability.php
deleted file mode 100644
index c2739c5df1..0000000000
--- a/src/applications/phragment/capability/PhragmentCanCreateCapability.php
+++ /dev/null
@@ -1,15 +0,0 @@
-<?php
-
-final class PhragmentCanCreateCapability extends PhabricatorPolicyCapability {
-
- const CAPABILITY = 'phragment.create';
-
- public function getCapabilityName() {
- return pht('Can Create Fragments');
- }
-
- public function describeCapabilityRejection() {
- return pht('You do not have permission to create fragments.');
- }
-
-}
diff --git a/src/applications/phragment/conduit/PhragmentConduitAPIMethod.php b/src/applications/phragment/conduit/PhragmentConduitAPIMethod.php
deleted file mode 100644
index 4a93c72847..0000000000
--- a/src/applications/phragment/conduit/PhragmentConduitAPIMethod.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-abstract class PhragmentConduitAPIMethod extends ConduitAPIMethod {
-
- final public function getApplication() {
- return PhabricatorApplication::getByClass(
- 'PhabricatorPhragmentApplication');
- }
-
-}
diff --git a/src/applications/phragment/conduit/PhragmentGetPatchConduitAPIMethod.php b/src/applications/phragment/conduit/PhragmentGetPatchConduitAPIMethod.php
deleted file mode 100644
index a030190343..0000000000
--- a/src/applications/phragment/conduit/PhragmentGetPatchConduitAPIMethod.php
+++ /dev/null
@@ -1,192 +0,0 @@
-<?php
-
-final class PhragmentGetPatchConduitAPIMethod
- extends PhragmentConduitAPIMethod {
-
- public function getAPIMethodName() {
- return 'phragment.getpatch';
- }
-
- public function getMethodStatus() {
- return self::METHOD_STATUS_UNSTABLE;
- }
-
- public function getMethodDescription() {
- return pht('Retrieve the patches to apply for a given set of files.');
- }
-
- protected function defineParamTypes() {
- return array(
- 'path' => 'required string',
- 'state' => 'required dict<string, string>',
- );
- }
-
- protected function defineReturnType() {
- return 'nonempty dict';
- }
-
- protected function defineErrorTypes() {
- return array(
- 'ERR_BAD_FRAGMENT' => pht('No such fragment exists.'),
- );
- }
-
- protected function execute(ConduitAPIRequest $request) {
- $path = $request->getValue('path');
- $state = $request->getValue('state');
- // The state is an array mapping file paths to hashes.
-
- $patches = array();
-
- // We need to get all of the mappings (like phragment.getstate) first
- // so that we can detect deletions and creations of files.
- $fragment = id(new PhragmentFragmentQuery())
- ->setViewer($request->getUser())
- ->withPaths(array($path))
- ->executeOne();
- if ($fragment === null) {
- throw new ConduitException('ERR_BAD_FRAGMENT');
- }
-
- $mappings = $fragment->getFragmentMappings(
- $request->getUser(),
- $fragment->getPath());
-
- $file_phids = mpull(mpull($mappings, 'getLatestVersion'), 'getFilePHID');
- $files = id(new PhabricatorFileQuery())
- ->setViewer($request->getUser())
- ->withPHIDs($file_phids)
- ->execute();
- $files = mpull($files, null, 'getPHID');
-
- // Scan all of the files that the caller currently has and iterate
- // over that.
- foreach ($state as $path => $hash) {
- // If $mappings[$path] exists, then the user has the file and it's
- // also a fragment.
- if (array_key_exists($path, $mappings)) {
- $file_phid = $mappings[$path]->getLatestVersion()->getFilePHID();
- if ($file_phid !== null) {
- // If the file PHID is present, then we need to check the
- // hashes to see if they are the same.
- $hash_caller = strtolower($state[$path]);
- $hash_current = $files[$file_phid]->getContentHash();
- if ($hash_caller === $hash_current) {
- // The user's version is identical to our version, so
- // there is no update needed.
- } else {
- // The hash differs, and the user needs to update.
- $patches[] = array(
- 'path' => $path,
- 'fileOld' => null,
- 'fileNew' => $files[$file_phid],
- 'hashOld' => $hash_caller,
- 'hashNew' => $hash_current,
- 'patchURI' => null,
- );
- }
- } else {
- // We have a record of this as a file, but there is no file
- // attached to the latest version, so we consider this to be
- // a deletion.
- $patches[] = array(
- 'path' => $path,
- 'fileOld' => null,
- 'fileNew' => null,
- 'hashOld' => $hash_caller,
- 'hashNew' => PhragmentPatchUtil::EMPTY_HASH,
- 'patchURI' => null,
- );
- }
- } else {
- // If $mappings[$path] does not exist, then the user has a file,
- // and we have absolutely no record of it what-so-ever (we haven't
- // even recorded a deletion). Assuming most applications will store
- // some form of data near their own files, this is probably a data
- // file relevant for the application that is not versioned, so we
- // don't tell the client to do anything with it.
- }
- }
-
- // Check the remaining files that we know about but the caller has
- // not reported.
- foreach ($mappings as $path => $child) {
- if (array_key_exists($path, $state)) {
- // We have already evaluated this above.
- } else {
- $file_phid = $mappings[$path]->getLatestVersion()->getFilePHID();
- if ($file_phid !== null) {
- // If the file PHID is present, then this is a new file that
- // we know about, but the caller does not. We need to tell
- // the caller to create the file.
- $hash_current = $files[$file_phid]->getContentHash();
- $patches[] = array(
- 'path' => $path,
- 'fileOld' => null,
- 'fileNew' => $files[$file_phid],
- 'hashOld' => PhragmentPatchUtil::EMPTY_HASH,
- 'hashNew' => $hash_current,
- 'patchURI' => null,
- );
- } else {
- // We have a record of deleting this file, and the caller hasn't
- // reported it, so they've probably deleted it in a previous
- // update.
- }
- }
- }
-
- // Before we can calculate patches, we need to resolve the old versions
- // of files so we can draw diffs on them.
- $hashes = array();
- foreach ($patches as $patch) {
- if ($patch['hashOld'] !== PhragmentPatchUtil::EMPTY_HASH) {
- $hashes[] = $patch['hashOld'];
- }
- }
- $old_files = array();
- if (count($hashes) !== 0) {
- $old_files = id(new PhabricatorFileQuery())
- ->setViewer($request->getUser())
- ->withContentHashes($hashes)
- ->execute();
- }
- $old_files = mpull($old_files, null, 'getContentHash');
- foreach ($patches as $key => $patch) {
- if ($patch['hashOld'] !== PhragmentPatchUtil::EMPTY_HASH) {
- if (array_key_exists($patch['hashOld'], $old_files)) {
- $patches[$key]['fileOld'] = $old_files[$patch['hashOld']];
- } else {
- // We either can't see or can't read the old file.
- $patches[$key]['hashOld'] = PhragmentPatchUtil::EMPTY_HASH;
- $patches[$key]['fileOld'] = null;
- }
- }
- }
-
- // Now run through all of the patch entries, calculate the patches
- // and return the results.
- foreach ($patches as $key => $patch) {
- $data = PhragmentPatchUtil::calculatePatch(
- $patches[$key]['fileOld'],
- $patches[$key]['fileNew']);
- unset($patches[$key]['fileOld']);
- unset($patches[$key]['fileNew']);
-
- $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
- $file = PhabricatorFile::newFromFileData(
- $data,
- array(
- 'name' => 'patch.dmp',
- 'ttl.relative' => phutil_units('24 hours in seconds'),
- ));
- unset($unguarded);
-
- $patches[$key]['patchURI'] = $file->getDownloadURI();
- }
-
- return $patches;
- }
-
-}
diff --git a/src/applications/phragment/conduit/PhragmentQueryFragmentsConduitAPIMethod.php b/src/applications/phragment/conduit/PhragmentQueryFragmentsConduitAPIMethod.php
deleted file mode 100644
index 08a1bcba49..0000000000
--- a/src/applications/phragment/conduit/PhragmentQueryFragmentsConduitAPIMethod.php
+++ /dev/null
@@ -1,85 +0,0 @@
-<?php
-
-final class PhragmentQueryFragmentsConduitAPIMethod
- extends PhragmentConduitAPIMethod {
-
- public function getAPIMethodName() {
- return 'phragment.queryfragments';
- }
-
- public function getMethodStatus() {
- return self::METHOD_STATUS_UNSTABLE;
- }
-
- public function getMethodDescription() {
- return pht('Query fragments based on their paths.');
- }
-
- protected function defineParamTypes() {
- return array(
- 'paths' => 'required list<string>',
- );
- }
-
- protected function defineReturnType() {
- return 'nonempty dict';
- }
-
- protected function defineErrorTypes() {
- return array(
- 'ERR_BAD_FRAGMENT' => pht('No such fragment exists.'),
- );
- }
-
- protected function execute(ConduitAPIRequest $request) {
- $paths = $request->getValue('paths');
-
- $fragments = id(new PhragmentFragmentQuery())
- ->setViewer($request->getUser())
- ->withPaths($paths)
- ->execute();
- $fragments = mpull($fragments, null, 'getPath');
- foreach ($paths as $path) {
- if (!array_key_exists($path, $fragments)) {
- throw new ConduitException('ERR_BAD_FRAGMENT');
- }
- }
-
- $results = array();
- foreach ($fragments as $path => $fragment) {
- $mappings = $fragment->getFragmentMappings(
- $request->getUser(),
- $fragment->getPath());
-
- $file_phids = mpull(mpull($mappings, 'getLatestVersion'), 'getFilePHID');
- $files = id(new PhabricatorFileQuery())
- ->setViewer($request->getUser())
- ->withPHIDs($file_phids)
- ->execute();
- $files = mpull($files, null, 'getPHID');
-
- $result = array();
- foreach ($mappings as $cpath => $child) {
- $file_phid = $child->getLatestVersion()->getFilePHID();
- if (!isset($files[$file_phid])) {
- // Skip any files we don't have permission to access.
- continue;
- }
-
- $file = $files[$file_phid];
- $cpath = substr($child->getPath(), strlen($fragment->getPath()) + 1);
- $result[] = array(
- 'phid' => $child->getPHID(),
- 'phidVersion' => $child->getLatestVersionPHID(),
- 'path' => $cpath,
- 'hash' => $file->getContentHash(),
- 'version' => $child->getLatestVersion()->getSequence(),
- 'uri' => $file->getViewURI(),
- );
- }
- $results[$path] = $result;
- }
- return $results;
- }
-
-}
diff --git a/src/applications/phragment/controller/PhragmentBrowseController.php b/src/applications/phragment/controller/PhragmentBrowseController.php
deleted file mode 100644
index d511835ce9..0000000000
--- a/src/applications/phragment/controller/PhragmentBrowseController.php
+++ /dev/null
@@ -1,95 +0,0 @@
-<?php
-
-final class PhragmentBrowseController extends PhragmentController {
-
- public function shouldAllowPublic() {
- return true;
- }
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $dblob = $request->getURIData('dblob');
-
- $parents = $this->loadParentFragments($dblob);
- if ($parents === null) {
- return new Aphront404Response();
- }
- $current = nonempty(last($parents), null);
-
- $path = '';
- if ($current !== null) {
- $path = $current->getPath();
- }
-
- $crumbs = $this->buildApplicationCrumbsWithPath($parents);
- if ($this->hasApplicationCapability(
- PhragmentCanCreateCapability::CAPABILITY)) {
- $crumbs->addAction(
- id(new PHUIListItemView())
- ->setName(pht('Create Fragment'))
- ->setHref($this->getApplicationURI('/create/'.$path))
- ->setIcon('fa-plus-square'));
- }
-
- $current_box = $this->createCurrentFragmentView($current, false);
-
- $list = id(new PHUIObjectItemListView())
- ->setUser($viewer);
-
- $fragments = null;
- if ($current === null) {
- // Find all root fragments.
- $fragments = id(new PhragmentFragmentQuery())
- ->setViewer($this->getRequest()->getUser())
- ->needLatestVersion(true)
- ->withDepths(array(1))
- ->execute();
- } else {
- // Find all child fragments.
- $fragments = id(new PhragmentFragmentQuery())
- ->setViewer($this->getRequest()->getUser())
- ->needLatestVersion(true)
- ->withLeadingPath($current->getPath().'/')
- ->withDepths(array($current->getDepth() + 1))
- ->execute();
- }
-
- foreach ($fragments as $fragment) {
- $item = id(new PHUIObjectItemView());
- $item->setHeader($fragment->getName());
- $item->setHref($fragment->getURI());
- if (!$fragment->isDirectory()) {
- $item->addAttribute(pht(
- 'Last Updated %s',
- phabricator_datetime(
- $fragment->getLatestVersion()->getDateCreated(),
- $viewer)));
- $item->addAttribute(pht(
- 'Latest Version %s',
- $fragment->getLatestVersion()->getSequence()));
- if ($fragment->isDeleted()) {
- $item->setDisabled(true);
- $item->addAttribute(pht('Deleted'));
- }
- } else {
- $item->addAttribute(pht('Directory'));
- }
- $list->addItem($item);
- }
-
- $title = pht('Browse Fragments');
-
- $view = array(
- $this->renderConfigurationWarningIfRequired(),
- $current_box,
- $list,
- );
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($view);
-
- }
-
-}
diff --git a/src/applications/phragment/controller/PhragmentController.php b/src/applications/phragment/controller/PhragmentController.php
deleted file mode 100644
index f9df7429be..0000000000
--- a/src/applications/phragment/controller/PhragmentController.php
+++ /dev/null
@@ -1,232 +0,0 @@
-<?php
-
-abstract class PhragmentController extends PhabricatorController {
-
- protected function loadParentFragments($path) {
- $components = explode('/', $path);
-
- $combinations = array();
- $current = '';
- foreach ($components as $component) {
- $current .= '/'.$component;
- $current = trim($current, '/');
- if (trim($current) === '') {
- continue;
- }
-
- $combinations[] = $current;
- }
-
- $fragments = array();
- $results = id(new PhragmentFragmentQuery())
- ->setViewer($this->getRequest()->getUser())
- ->needLatestVersion(true)
- ->withPaths($combinations)
- ->execute();
- foreach ($combinations as $combination) {
- $found = false;
- foreach ($results as $fragment) {
- if ($fragment->getPath() === $combination) {
- $fragments[] = $fragment;
- $found = true;
- break;
- }
- }
- if (!$found) {
- return null;
- }
- }
- return $fragments;
- }
-
- protected function buildApplicationCrumbsWithPath(array $fragments) {
- $crumbs = $this->buildApplicationCrumbs();
- $crumbs->addTextCrumb('/', '/phragment/');
- foreach ($fragments as $parent) {
- $crumbs->addTextCrumb(
- $parent->getName(),
- '/phragment/browse/'.$parent->getPath());
- }
- return $crumbs;
- }
-
- protected function createCurrentFragmentView($fragment, $is_history_view) {
- if ($fragment === null) {
- return null;
- }
-
- $viewer = $this->getRequest()->getUser();
-
- $snapshot_phids = array();
- $snapshots = id(new PhragmentSnapshotQuery())
- ->setViewer($viewer)
- ->withPrimaryFragmentPHIDs(array($fragment->getPHID()))
- ->execute();
- foreach ($snapshots as $snapshot) {
- $snapshot_phids[] = $snapshot->getPHID();
- }
-
- $file = null;
- $file_uri = null;
- if (!$fragment->isDirectory()) {
- $file = id(new PhabricatorFileQuery())
- ->setViewer($viewer)
- ->withPHIDs(array($fragment->getLatestVersion()->getFilePHID()))
- ->executeOne();
- if ($file !== null) {
- $file_uri = $file->getDownloadURI();
- }
- }
-
- $header = id(new PHUIHeaderView())
- ->setHeader($fragment->getName())
- ->setPolicyObject($fragment)
- ->setUser($viewer);
-
- $can_edit = PhabricatorPolicyFilter::hasCapability(
- $viewer,
- $fragment,
- PhabricatorPolicyCapability::CAN_EDIT);
-
- $zip_uri = $this->getApplicationURI('zip/'.$fragment->getPath());
-
- $actions = id(new PhabricatorActionListView())
- ->setUser($viewer)
- ->setObject($fragment);
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Download Fragment'))
- ->setHref($this->isCorrectlyConfigured() ? $file_uri : null)
- ->setDisabled($file === null || !$this->isCorrectlyConfigured())
- ->setIcon('fa-download'));
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Download Contents as ZIP'))
- ->setHref($this->isCorrectlyConfigured() ? $zip_uri : null)
- ->setDisabled(!$this->isCorrectlyConfigured())
- ->setIcon('fa-floppy-o'));
- if (!$fragment->isDirectory()) {
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Update Fragment'))
- ->setHref($this->getApplicationURI('update/'.$fragment->getPath()))
- ->setDisabled(!$can_edit)
- ->setWorkflow(!$can_edit)
- ->setIcon('fa-refresh'));
- } else {
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Convert to File'))
- ->setHref($this->getApplicationURI('update/'.$fragment->getPath()))
- ->setDisabled(!$can_edit)
- ->setWorkflow(!$can_edit)
- ->setIcon('fa-file-o'));
- }
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Set Fragment Policies'))
- ->setHref($this->getApplicationURI('policy/'.$fragment->getPath()))
- ->setDisabled(!$can_edit)
- ->setWorkflow(!$can_edit)
- ->setIcon('fa-asterisk'));
- if ($is_history_view) {
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('View Child Fragments'))
- ->setHref($this->getApplicationURI('browse/'.$fragment->getPath()))
- ->setIcon('fa-search-plus'));
- } else {
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('View History'))
- ->setHref($this->getApplicationURI('history/'.$fragment->getPath()))
- ->setIcon('fa-list'));
- }
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Create Snapshot'))
- ->setHref($this->getApplicationURI(
- 'snapshot/create/'.$fragment->getPath()))
- ->setDisabled(!$can_edit)
- ->setWorkflow(!$can_edit)
- ->setIcon('fa-files-o'));
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Promote Snapshot to Here'))
- ->setHref($this->getApplicationURI(
- 'snapshot/promote/latest/'.$fragment->getPath()))
- ->setWorkflow(true)
- ->setDisabled(!$can_edit)
- ->setIcon('fa-arrow-circle-up'));
-
- $properties = id(new PHUIPropertyListView())
- ->setUser($viewer)
- ->setObject($fragment)
- ->setActionList($actions);
-
- if (!$fragment->isDirectory()) {
- if ($fragment->isDeleted()) {
- $properties->addProperty(
- pht('Type'),
- pht('File (Deleted)'));
- } else {
- $properties->addProperty(
- pht('Type'),
- pht('File'));
- }
- $properties->addProperty(
- pht('Latest Version'),
- $viewer->renderHandle($fragment->getLatestVersionPHID()));
- } else {
- $properties->addProperty(
- pht('Type'),
- pht('Directory'));
- }
-
- if (count($snapshot_phids) > 0) {
- $properties->addProperty(
- pht('Snapshots'),
- $viewer->renderHandleList($snapshot_phids));
- }
-
- return id(new PHUIObjectBoxView())
- ->setHeader($header)
- ->addPropertyList($properties);
- }
-
- public function renderConfigurationWarningIfRequired() {
- $alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain');
- if ($alt === null) {
- return id(new PHUIInfoView())
- ->setTitle(pht(
- '%s must be configured!',
- 'security.alternate-file-domain'))
- ->setSeverity(PHUIInfoView::SEVERITY_ERROR)
- ->appendChild(
- phutil_tag(
- 'p',
- array(),
- pht(
- "Because Phragment generates files (such as ZIP archives and ".
- "patches) as they are requested, it requires that you configure ".
- "the `%s` option. This option on it's own will also provide ".
- "additional security when serving files across Phabricator.",
- 'security.alternate-file-domain')));
- }
- return null;
- }
-
- /**
- * We use this to disable the download links if the alternate domain is
- * not configured correctly. Although the download links will mostly work
- * for logged in users without an alternate domain, the behaviour is
- * reasonably non-consistent and will deny public users, even if policies
- * are configured otherwise (because the Files app does not support showing
- * the info page to viewers who are not logged in).
- */
- public function isCorrectlyConfigured() {
- $alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain');
- return $alt !== null;
- }
-
-}
diff --git a/src/applications/phragment/controller/PhragmentCreateController.php b/src/applications/phragment/controller/PhragmentCreateController.php
deleted file mode 100644
index 946d7cc332..0000000000
--- a/src/applications/phragment/controller/PhragmentCreateController.php
+++ /dev/null
@@ -1,135 +0,0 @@
-<?php
-
-final class PhragmentCreateController extends PhragmentController {
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $dblob = $request->getURIData('dblob');
-
- $parent = null;
- $parents = $this->loadParentFragments($dblob);
- if ($parents === null) {
- return new Aphront404Response();
- }
- if (count($parents) !== 0) {
- $parent = idx($parents, count($parents) - 1, null);
- }
-
- $parent_path = '';
- if ($parent !== null) {
- $parent_path = $parent->getPath();
- }
- $parent_path = trim($parent_path, '/');
-
- $fragment = id(new PhragmentFragment());
-
- $error_view = null;
-
- if ($request->isFormPost()) {
- $errors = array();
-
- $v_name = $request->getStr('name');
- $v_fileid = $request->getInt('fileID');
- $v_viewpolicy = $request->getStr('viewPolicy');
- $v_editpolicy = $request->getStr('editPolicy');
-
- if (strpos($v_name, '/') !== false) {
- $errors[] = pht("The fragment name can not contain '/'.");
- }
-
- $file = id(new PhabricatorFileQuery())
- ->setViewer($viewer)
- ->withIDs(array($v_fileid))
- ->executeOne();
- if (!$file) {
- $errors[] = pht("The specified file doesn't exist.");
- }
-
- if (!count($errors)) {
- $depth = 1;
- if ($parent !== null) {
- $depth = $parent->getDepth() + 1;
- }
-
- PhragmentFragment::createFromFile(
- $viewer,
- $file,
- trim($parent_path.'/'.$v_name, '/'),
- $v_viewpolicy,
- $v_editpolicy);
-
- return id(new AphrontRedirectResponse())
- ->setURI('/phragment/browse/'.trim($parent_path.'/'.$v_name, '/'));
- } else {
- $error_view = id(new PHUIInfoView())
- ->setErrors($errors)
- ->setTitle(pht('Errors while creating fragment'));
- }
- }
-
- $policies = id(new PhabricatorPolicyQuery())
- ->setViewer($viewer)
- ->setObject($fragment)
- ->execute();
-
- $form = id(new AphrontFormView())
- ->setUser($viewer)
- ->appendChild(
- id(new AphrontFormTextControl())
- ->setLabel(pht('Parent Path'))
- ->setDisabled(true)
- ->setValue('/'.trim($parent_path.'/', '/')))
- ->appendChild(
- id(new AphrontFormTextControl())
- ->setLabel(pht('Name'))
- ->setName('name'))
- ->appendChild(
- id(new AphrontFormTextControl())
- ->setLabel(pht('File ID'))
- ->setName('fileID'))
- ->appendChild(
- id(new AphrontFormPolicyControl())
- ->setUser($viewer)
- ->setName('viewPolicy')
- ->setPolicyObject($fragment)
- ->setPolicies($policies)
- ->setCapability(PhabricatorPolicyCapability::CAN_VIEW))
- ->appendChild(
- id(new AphrontFormPolicyControl())
- ->setUser($viewer)
- ->setName('editPolicy')
- ->setPolicyObject($fragment)
- ->setPolicies($policies)
- ->setCapability(PhabricatorPolicyCapability::CAN_EDIT))
- ->appendChild(
- id(new AphrontFormSubmitControl())
- ->setValue(pht('Create Fragment'))
- ->addCancelButton(
- $this->getApplicationURI('browse/'.$parent_path)));
-
- $crumbs = $this->buildApplicationCrumbsWithPath($parents);
- $crumbs->addTextCrumb(pht('Create Fragment'));
-
- $box = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Create Fragment'))
- ->setForm($form);
-
- if ($error_view) {
- $box->setInfoView($error_view);
- }
-
- $title = pht('Create Fragments');
-
- $view = array(
- $this->renderConfigurationWarningIfRequired(),
- $box,
- );
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($view);
-
- }
-
-}
diff --git a/src/applications/phragment/controller/PhragmentHistoryController.php b/src/applications/phragment/controller/PhragmentHistoryController.php
deleted file mode 100644
index 4e16deb43d..0000000000
--- a/src/applications/phragment/controller/PhragmentHistoryController.php
+++ /dev/null
@@ -1,109 +0,0 @@
-<?php
-
-final class PhragmentHistoryController extends PhragmentController {
-
- public function shouldAllowPublic() {
- return true;
- }
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $dblob = $request->getURIData('dblob');
-
- $parents = $this->loadParentFragments($dblob);
- if ($parents === null) {
- return new Aphront404Response();
- }
- $current = idx($parents, count($parents) - 1, null);
-
- $path = $current->getPath();
-
- $crumbs = $this->buildApplicationCrumbsWithPath($parents);
- if ($this->hasApplicationCapability(
- PhragmentCanCreateCapability::CAPABILITY)) {
- $crumbs->addAction(
- id(new PHUIListItemView())
- ->setName(pht('Create Fragment'))
- ->setHref($this->getApplicationURI('/create/'.$path))
- ->setIcon('fa-plus-square'));
- }
-
- $current_box = $this->createCurrentFragmentView($current, true);
-
- $versions = id(new PhragmentFragmentVersionQuery())
- ->setViewer($viewer)
- ->withFragmentPHIDs(array($current->getPHID()))
- ->execute();
-
- $list = id(new PHUIObjectItemListView())
- ->setUser($viewer);
-
- $file_phids = mpull($versions, 'getFilePHID');
- $files = id(new PhabricatorFileQuery())
- ->setViewer($viewer)
- ->withPHIDs($file_phids)
- ->execute();
- $files = mpull($files, null, 'getPHID');
-
- $can_edit = PhabricatorPolicyFilter::hasCapability(
- $viewer,
- $current,
- PhabricatorPolicyCapability::CAN_EDIT);
-
- $first = true;
- foreach ($versions as $version) {
- $item = id(new PHUIObjectItemView());
- $item->setHeader(pht('Version %s', $version->getSequence()));
- $item->setHref($version->getURI());
- $item->addAttribute(phabricator_datetime(
- $version->getDateCreated(),
- $viewer));
-
- if ($version->getFilePHID() === null) {
- $item->setDisabled(true);
- $item->addAttribute(pht('Deletion'));
- }
-
- if (!$first && $can_edit) {
- $item->addAction(id(new PHUIListItemView())
- ->setIcon('fa-refresh')
- ->setRenderNameAsTooltip(true)
- ->setWorkflow(true)
- ->setName(pht('Revert to Here'))
- ->setHref($this->getApplicationURI(
- 'revert/'.$version->getID().'/'.$current->getPath())));
- }
-
- $disabled = !isset($files[$version->getFilePHID()]);
- $action = id(new PHUIListItemView())
- ->setIcon('fa-download')
- ->setDisabled($disabled || !$this->isCorrectlyConfigured())
- ->setRenderNameAsTooltip(true)
- ->setName(pht('Download'));
- if (!$disabled && $this->isCorrectlyConfigured()) {
- $action->setHref($files[$version->getFilePHID()]
- ->getDownloadURI($version->getURI()));
- }
- $item->addAction($action);
-
- $list->addItem($item);
-
- $first = false;
- }
-
- $title = pht('Fragment History');
-
- $view = array(
- $this->renderConfigurationWarningIfRequired(),
- $current_box,
- $list,
- );
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($view);
-
- }
-
-}
diff --git a/src/applications/phragment/controller/PhragmentPatchController.php b/src/applications/phragment/controller/PhragmentPatchController.php
deleted file mode 100644
index eaa08bc29e..0000000000
--- a/src/applications/phragment/controller/PhragmentPatchController.php
+++ /dev/null
@@ -1,97 +0,0 @@
-<?php
-
-final class PhragmentPatchController extends PhragmentController {
-
- public function shouldAllowPublic() {
- return true;
- }
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $aid = $request->getURIData('aid');
- $bid = $request->getURIData('bid');
-
- // If "aid" is "x", then it means the user wants to generate
- // a patch of an empty file to the version specified by "bid".
-
- $ids = array($aid, $bid);
- if ($aid === 'x') {
- $ids = array($bid);
- }
-
- $versions = id(new PhragmentFragmentVersionQuery())
- ->setViewer($viewer)
- ->withIDs($ids)
- ->execute();
-
- $version_a = null;
- if ($aid !== 'x') {
- $version_a = idx($versions, $aid, null);
- if ($version_a === null) {
- return new Aphront404Response();
- }
- }
-
- $version_b = idx($versions, $bid, null);
- if ($version_b === null) {
- return new Aphront404Response();
- }
-
- $file_phids = array();
- if ($version_a !== null) {
- $file_phids[] = $version_a->getFilePHID();
- }
- $file_phids[] = $version_b->getFilePHID();
-
- $files = id(new PhabricatorFileQuery())
- ->setViewer($viewer)
- ->withPHIDs($file_phids)
- ->execute();
- $files = mpull($files, null, 'getPHID');
-
- $file_a = null;
- if ($version_a != null) {
- $file_a = idx($files, $version_a->getFilePHID(), null);
- }
- $file_b = idx($files, $version_b->getFilePHID(), null);
-
- $patch = PhragmentPatchUtil::calculatePatch($file_a, $file_b);
-
- if ($patch === null) {
- // There are no differences between the two files, so we output
- // an empty patch.
- $patch = '';
- }
-
- $a_sequence = 'x';
- if ($version_a !== null) {
- $a_sequence = $version_a->getSequence();
- }
-
- $name =
- $version_b->getFragment()->getName().'.'.
- $a_sequence.'.'.
- $version_b->getSequence().'.patch';
-
- $return = $version_b->getURI();
- if ($request->getExists('return')) {
- $return = $request->getStr('return');
- }
-
- $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
- $result = PhabricatorFile::newFromFileData(
- $patch,
- array(
- 'name' => $name,
- 'mime-type' => 'text/plain',
- 'ttl.relative' => phutil_units('24 hours in seconds'),
- ));
-
- $result->attachToObject($version_b->getFragmentPHID());
- unset($unguarded);
-
- return id(new AphrontRedirectResponse())
- ->setURI($result->getDownloadURI($return));
- }
-
-}
diff --git a/src/applications/phragment/controller/PhragmentPolicyController.php b/src/applications/phragment/controller/PhragmentPolicyController.php
deleted file mode 100644
index edcde80990..0000000000
--- a/src/applications/phragment/controller/PhragmentPolicyController.php
+++ /dev/null
@@ -1,106 +0,0 @@
-<?php
-
-final class PhragmentPolicyController extends PhragmentController {
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $dblob = $request->getURIData('dblob');
-
- $parents = $this->loadParentFragments($dblob);
- if ($parents === null) {
- return new Aphront404Response();
- }
- $fragment = idx($parents, count($parents) - 1, null);
-
- $error_view = null;
-
- if ($request->isFormPost()) {
- $errors = array();
-
- $v_view_policy = $request->getStr('viewPolicy');
- $v_edit_policy = $request->getStr('editPolicy');
- $v_replace_children = $request->getBool('replacePoliciesOnChildren');
-
- $fragment->setViewPolicy($v_view_policy);
- $fragment->setEditPolicy($v_edit_policy);
-
- $fragment->save();
-
- if ($v_replace_children) {
- // If you can edit a fragment, you can forcibly set the policies
- // on child fragments, regardless of whether you can see them or not.
- $children = id(new PhragmentFragmentQuery())
- ->setViewer(PhabricatorUser::getOmnipotentUser())
- ->withLeadingPath($fragment->getPath().'/')
- ->execute();
- $children_phids = mpull($children, 'getPHID');
-
- $fragment->openTransaction();
- foreach ($children as $child) {
- $child->setViewPolicy($v_view_policy);
- $child->setEditPolicy($v_edit_policy);
- $child->save();
- }
- $fragment->saveTransaction();
- }
-
- return id(new AphrontRedirectResponse())
- ->setURI('/phragment/browse/'.$fragment->getPath());
- }
-
- $policies = id(new PhabricatorPolicyQuery())
- ->setViewer($viewer)
- ->setObject($fragment)
- ->execute();
-
- $form = id(new AphrontFormView())
- ->setUser($viewer)
- ->appendChild(
- id(new AphrontFormPolicyControl())
- ->setName('viewPolicy')
- ->setPolicyObject($fragment)
- ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
- ->setPolicies($policies))
- ->appendChild(
- id(new AphrontFormPolicyControl())
- ->setName('editPolicy')
- ->setPolicyObject($fragment)
- ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
- ->setPolicies($policies))
- ->appendChild(
- id(new AphrontFormCheckboxControl())
- ->addCheckbox(
- 'replacePoliciesOnChildren',
- 'true',
- pht(
- 'Replace policies on child fragments with '.
- 'the policies above.')))
- ->appendChild(
- id(new AphrontFormSubmitControl())
- ->setValue(pht('Save Fragment Policies'))
- ->addCancelButton(
- $this->getApplicationURI('browse/'.$fragment->getPath())));
-
- $crumbs = $this->buildApplicationCrumbsWithPath($parents);
- $crumbs->addTextCrumb(pht('Edit Fragment Policies'));
-
- $box = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Edit Fragment Policies: %s', $fragment->getPath()))
- ->setValidationException(null)
- ->setForm($form);
-
- $title = pht('Edit Fragment Policies');
-
- $view = array(
- $this->renderConfigurationWarningIfRequired(),
- $box,
- );
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($view);
-
- }
-
-}
diff --git a/src/applications/phragment/controller/PhragmentRevertController.php b/src/applications/phragment/controller/PhragmentRevertController.php
deleted file mode 100644
index b9aa050327..0000000000
--- a/src/applications/phragment/controller/PhragmentRevertController.php
+++ /dev/null
@@ -1,78 +0,0 @@
-<?php
-
-final class PhragmentRevertController extends PhragmentController {
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $id = $request->getURIData('id');
- $dblob = $request->getURIData('dblob');
-
- $fragment = id(new PhragmentFragmentQuery())
- ->setViewer($viewer)
- ->withPaths(array($dblob))
- ->requireCapabilities(
- array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- ))
- ->executeOne();
- if ($fragment === null) {
- return new Aphront404Response();
- }
-
- $version = id(new PhragmentFragmentVersionQuery())
- ->setViewer($viewer)
- ->withFragmentPHIDs(array($fragment->getPHID()))
- ->withIDs(array($id))
- ->executeOne();
- if ($version === null) {
- return new Aphront404Response();
- }
-
- if ($request->isDialogFormPost()) {
- $file_phid = $version->getFilePHID();
-
- $file = null;
- if ($file_phid !== null) {
- $file = id(new PhabricatorFileQuery())
- ->setViewer($viewer)
- ->withPHIDs(array($file_phid))
- ->executeOne();
- if ($file === null) {
- throw new Exception(
- pht('The file associated with this version was not found.'));
- }
- }
-
- if ($file === null) {
- $fragment->deleteFile($viewer);
- } else {
- $fragment->updateFromFile($viewer, $file);
- }
-
- return id(new AphrontRedirectResponse())
- ->setURI($this->getApplicationURI('/history/'.$dblob));
- }
-
- return $this->createDialog($fragment, $version);
- }
-
- public function createDialog(
- PhragmentFragment $fragment,
- PhragmentFragmentVersion $version) {
-
- $viewer = $this->getViewer();
-
- $dialog = id(new AphrontDialogView())
- ->setTitle(pht('Really revert this fragment?'))
- ->setUser($this->getViewer())
- ->addSubmitButton(pht('Revert'))
- ->addCancelButton(pht('Cancel'))
- ->appendParagraph(pht(
- 'Reverting this fragment to version %d will create a new version of '.
- 'the fragment. It will not delete any version history.',
- $version->getSequence()));
- return id(new AphrontDialogResponse())->setDialog($dialog);
- }
-
-}
diff --git a/src/applications/phragment/controller/PhragmentSnapshotCreateController.php b/src/applications/phragment/controller/PhragmentSnapshotCreateController.php
deleted file mode 100644
index c32be32cd7..0000000000
--- a/src/applications/phragment/controller/PhragmentSnapshotCreateController.php
+++ /dev/null
@@ -1,170 +0,0 @@
-<?php
-
-final class PhragmentSnapshotCreateController extends PhragmentController {
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $dblob = $request->getURIData('dblob');
-
- $parents = $this->loadParentFragments($dblob);
- if ($parents === null) {
- return new Aphront404Response();
- }
- $fragment = nonempty(last($parents), null);
- if ($fragment === null) {
- return new Aphront404Response();
- }
-
- PhabricatorPolicyFilter::requireCapability(
- $viewer,
- $fragment,
- PhabricatorPolicyCapability::CAN_EDIT);
-
- $children = id(new PhragmentFragmentQuery())
- ->setViewer($viewer)
- ->needLatestVersion(true)
- ->withLeadingPath($fragment->getPath().'/')
- ->execute();
-
- $errors = array();
- if ($request->isFormPost()) {
-
- $v_name = $request->getStr('name');
- if (strlen($v_name) === 0) {
- $errors[] = pht('You must specify a name.');
- }
- if (strpos($v_name, '/') !== false) {
- $errors[] = pht('Snapshot names can not contain "/".');
- }
-
- if (!count($errors)) {
- $snapshot = null;
-
- try {
- // Create the snapshot.
- $snapshot = id(new PhragmentSnapshot())
- ->setPrimaryFragmentPHID($fragment->getPHID())
- ->setName($v_name)
- ->save();
- } catch (AphrontDuplicateKeyQueryException $e) {
- $errors[] = pht('A snapshot with this name already exists.');
- }
-
- if (!count($errors)) {
- // Add the primary fragment.
- id(new PhragmentSnapshotChild())
- ->setSnapshotPHID($snapshot->getPHID())
- ->setFragmentPHID($fragment->getPHID())
- ->setFragmentVersionPHID($fragment->getLatestVersionPHID())
- ->save();
-
- // Add all of the child fragments.
- foreach ($children as $child) {
- id(new PhragmentSnapshotChild())
- ->setSnapshotPHID($snapshot->getPHID())
- ->setFragmentPHID($child->getPHID())
- ->setFragmentVersionPHID($child->getLatestVersionPHID())
- ->save();
- }
-
- return id(new AphrontRedirectResponse())
- ->setURI('/phragment/snapshot/view/'.$snapshot->getID());
- }
- }
- }
-
- $fragment_sequence = '-';
- if ($fragment->getLatestVersion() !== null) {
- $fragment_sequence = $fragment->getLatestVersion()->getSequence();
- }
-
- $rows = array();
- $rows[] = phutil_tag(
- 'tr',
- array(),
- array(
- phutil_tag('th', array(), pht('Fragment')),
- phutil_tag('th', array(), pht('Version')),
- ));
- $rows[] = phutil_tag(
- 'tr',
- array(),
- array(
- phutil_tag('td', array(), $fragment->getPath()),
- phutil_tag('td', array(), $fragment_sequence),
- ));
- foreach ($children as $child) {
- $sequence = '-';
- if ($child->getLatestVersion() !== null) {
- $sequence = $child->getLatestVersion()->getSequence();
- }
- $rows[] = phutil_tag(
- 'tr',
- array(),
- array(
- phutil_tag('td', array(), $child->getPath()),
- phutil_tag('td', array(), $sequence),
- ));
- }
-
- $table = phutil_tag(
- 'table',
- array('class' => 'remarkup-table'),
- $rows);
-
- $container = phutil_tag(
- 'div',
- array('class' => 'phabricator-remarkup'),
- array(
- phutil_tag(
- 'p',
- array(),
- pht(
- 'The snapshot will contain the following fragments at '.
- 'the specified versions: ')),
- $table,
- ));
-
- $form = id(new AphrontFormView())
- ->setUser($viewer)
- ->appendChild(
- id(new AphrontFormTextControl())
- ->setLabel(pht('Fragment Path'))
- ->setDisabled(true)
- ->setValue('/'.$fragment->getPath()))
- ->appendChild(
- id(new AphrontFormTextControl())
- ->setLabel(pht('Snapshot Name'))
- ->setName('name'))
- ->appendChild(
- id(new AphrontFormSubmitControl())
- ->setValue(pht('Create Snapshot'))
- ->addCancelButton(
- $this->getApplicationURI('browse/'.$fragment->getPath())))
- ->appendChild(
- id(new PHUIFormDividerControl()))
- ->appendInstructions($container);
-
- $crumbs = $this->buildApplicationCrumbsWithPath($parents);
- $crumbs->addTextCrumb(pht('Create Snapshot'));
-
- $box = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Create Snapshot of %s', $fragment->getName()))
- ->setFormErrors($errors)
- ->setForm($form);
-
- $title = pht('Create Snapshot');
-
- $view = array(
- $this->renderConfigurationWarningIfRequired(),
- $box,
- );
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($view);
-
- }
-
-}
diff --git a/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php b/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php
deleted file mode 100644
index 8f112585d0..0000000000
--- a/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php
+++ /dev/null
@@ -1,47 +0,0 @@
-<?php
-
-final class PhragmentSnapshotDeleteController extends PhragmentController {
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $id = $request->getURIData('id');
-
- $snapshot = id(new PhragmentSnapshotQuery())
- ->setViewer($viewer)
- ->requireCapabilities(array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- ))
- ->withIDs(array($id))
- ->executeOne();
- if ($snapshot === null) {
- return new Aphront404Response();
- }
-
- if ($request->isDialogFormPost()) {
- $fragment_uri = $snapshot->getPrimaryFragment()->getURI();
-
- $snapshot->delete();
-
- return id(new AphrontRedirectResponse())
- ->setURI($fragment_uri);
- }
-
- return $this->createDialog();
- }
-
- public function createDialog() {
- $viewer = $this->getViewer();
-
- $dialog = id(new AphrontDialogView())
- ->setTitle(pht('Really delete this snapshot?'))
- ->setUser($this->getViewer())
- ->addSubmitButton(pht('Delete'))
- ->addCancelButton(pht('Cancel'))
- ->appendParagraph(pht(
- 'Deleting this snapshot is a permanent operation. You can not '.
- 'recover the state of the snapshot.'));
- return id(new AphrontDialogResponse())->setDialog($dialog);
- }
-
-}
diff --git a/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php b/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php
deleted file mode 100644
index 981d139742..0000000000
--- a/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php
+++ /dev/null
@@ -1,188 +0,0 @@
-<?php
-
-final class PhragmentSnapshotPromoteController extends PhragmentController {
-
- private $targetSnapshot;
- private $targetFragment;
- private $snapshots;
- private $options;
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $id = $request->getURIData('id');
- $dblob = $request->getURIData('dblob');
-
- // When the user is promoting a snapshot to the latest version, the
- // identifier is a fragment path.
- if ($dblob !== null) {
- $this->targetFragment = id(new PhragmentFragmentQuery())
- ->setViewer($viewer)
- ->requireCapabilities(array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- ))
- ->withPaths(array($dblob))
- ->executeOne();
- if ($this->targetFragment === null) {
- return new Aphront404Response();
- }
-
- $this->snapshots = id(new PhragmentSnapshotQuery())
- ->setViewer($viewer)
- ->withPrimaryFragmentPHIDs(array($this->targetFragment->getPHID()))
- ->execute();
- }
-
- // When the user is promoting a snapshot to another snapshot, the
- // identifier is another snapshot ID.
- if ($id !== null) {
- $this->targetSnapshot = id(new PhragmentSnapshotQuery())
- ->setViewer($viewer)
- ->requireCapabilities(array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- ))
- ->withIDs(array($id))
- ->executeOne();
- if ($this->targetSnapshot === null) {
- return new Aphront404Response();
- }
-
- $this->snapshots = id(new PhragmentSnapshotQuery())
- ->setViewer($viewer)
- ->withPrimaryFragmentPHIDs(array(
- $this->targetSnapshot->getPrimaryFragmentPHID(),
- ))
- ->execute();
- }
-
- // If there's no identifier, just 404.
- if ($this->snapshots === null) {
- return new Aphront404Response();
- }
-
- // Work out what options the user has.
- $this->options = mpull(
- $this->snapshots,
- 'getName',
- 'getID');
- if ($id !== null) {
- unset($this->options[$id]);
- }
-
- // If there's no options, show a dialog telling the
- // user there are no snapshots to promote.
- if (count($this->options) === 0) {
- return id(new AphrontDialogResponse())->setDialog(
- id(new AphrontDialogView())
- ->setTitle(pht('No snapshots to promote'))
- ->appendParagraph(pht(
- 'There are no snapshots available to promote.'))
- ->setUser($this->getViewer())
- ->addCancelButton(pht('Cancel')));
- }
-
- // Handle snapshot promotion.
- if ($request->isDialogFormPost()) {
- $snapshot = id(new PhragmentSnapshotQuery())
- ->setViewer($viewer)
- ->withIDs(array($request->getStr('snapshot')))
- ->executeOne();
- if ($snapshot === null) {
- return new Aphront404Response();
- }
-
- $snapshot->openTransaction();
- // Delete all existing child entries.
- $children = id(new PhragmentSnapshotChildQuery())
- ->setViewer(PhabricatorUser::getOmnipotentUser())
- ->withSnapshotPHIDs(array($snapshot->getPHID()))
- ->execute();
- foreach ($children as $child) {
- $child->delete();
- }
-
- if ($id === null) {
- // The user is promoting the snapshot to the latest version.
- $children = id(new PhragmentFragmentQuery())
- ->setViewer($viewer)
- ->needLatestVersion(true)
- ->withLeadingPath($this->targetFragment->getPath().'/')
- ->execute();
-
- // Add the primary fragment.
- id(new PhragmentSnapshotChild())
- ->setSnapshotPHID($snapshot->getPHID())
- ->setFragmentPHID($this->targetFragment->getPHID())
- ->setFragmentVersionPHID(
- $this->targetFragment->getLatestVersionPHID())
- ->save();
-
- // Add all of the child fragments.
- foreach ($children as $child) {
- id(new PhragmentSnapshotChild())
- ->setSnapshotPHID($snapshot->getPHID())
- ->setFragmentPHID($child->getPHID())
- ->setFragmentVersionPHID($child->getLatestVersionPHID())
- ->save();
- }
- } else {
- // The user is promoting the snapshot to another snapshot. We just
- // copy the other snapshot's child entries and change the snapshot
- // PHID to make it identical.
- $children = id(new PhragmentSnapshotChildQuery())
- ->setViewer($viewer)
- ->withSnapshotPHIDs(array($this->targetSnapshot->getPHID()))
- ->execute();
- foreach ($children as $child) {
- id(new PhragmentSnapshotChild())
- ->setSnapshotPHID($snapshot->getPHID())
- ->setFragmentPHID($child->getFragmentPHID())
- ->setFragmentVersionPHID($child->getFragmentVersionPHID())
- ->save();
- }
- }
- $snapshot->saveTransaction();
-
- if ($id === null) {
- return id(new AphrontRedirectResponse())
- ->setURI($this->targetFragment->getURI());
- } else {
- return id(new AphrontRedirectResponse())
- ->setURI($this->targetSnapshot->getURI());
- }
- }
-
- return $this->createDialog($id);
- }
-
- public function createDialog($id) {
- $viewer = $this->getViewer();
-
- $dialog = id(new AphrontDialogView())
- ->setTitle(pht('Promote which snapshot?'))
- ->setUser($this->getViewer())
- ->addSubmitButton(pht('Promote'))
- ->addCancelButton(pht('Cancel'));
-
- if ($id === null) {
- // The user is promoting a snapshot to the latest version.
- $dialog->appendParagraph(pht(
- 'Select the snapshot you want to promote to the latest version:'));
- } else {
- // The user is promoting a snapshot to another snapshot.
- $dialog->appendParagraph(pht(
- "Select the snapshot you want to promote to '%s':",
- $this->targetSnapshot->getName()));
- }
-
- $dialog->appendChild(
- id(new AphrontFormSelectControl())
- ->setUser($viewer)
- ->setName('snapshot')
- ->setOptions($this->options));
-
- return id(new AphrontDialogResponse())->setDialog($dialog);
- }
-
-}
diff --git a/src/applications/phragment/controller/PhragmentSnapshotViewController.php b/src/applications/phragment/controller/PhragmentSnapshotViewController.php
deleted file mode 100644
index 052b5036fb..0000000000
--- a/src/applications/phragment/controller/PhragmentSnapshotViewController.php
+++ /dev/null
@@ -1,145 +0,0 @@
-<?php
-
-final class PhragmentSnapshotViewController extends PhragmentController {
-
- public function shouldAllowPublic() {
- return true;
- }
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $id = $request->getURIData('id');
-
- $snapshot = id(new PhragmentSnapshotQuery())
- ->setViewer($viewer)
- ->withIDs(array($id))
- ->executeOne();
- if ($snapshot === null) {
- return new Aphront404Response();
- }
-
- $box = $this->createSnapshotView($snapshot);
-
- $fragment = id(new PhragmentFragmentQuery())
- ->setViewer($viewer)
- ->withPHIDs(array($snapshot->getPrimaryFragmentPHID()))
- ->executeOne();
- if ($fragment === null) {
- return new Aphront404Response();
- }
-
- $parents = $this->loadParentFragments($fragment->getPath());
- if ($parents === null) {
- return new Aphront404Response();
- }
-
- $crumbs = $this->buildApplicationCrumbsWithPath($parents);
- $crumbs->addTextCrumb(pht('"%s" Snapshot', $snapshot->getName()));
-
- $children = id(new PhragmentSnapshotChildQuery())
- ->setViewer($viewer)
- ->needFragments(true)
- ->needFragmentVersions(true)
- ->withSnapshotPHIDs(array($snapshot->getPHID()))
- ->execute();
-
- $list = id(new PHUIObjectItemListView())
- ->setUser($viewer);
-
- foreach ($children as $child) {
- $item = id(new PHUIObjectItemView())
- ->setHeader($child->getFragment()->getPath());
-
- if ($child->getFragmentVersion() !== null) {
- $item
- ->setHref($child->getFragmentVersion()->getURI())
- ->addAttribute(pht(
- 'Version %s',
- $child->getFragmentVersion()->getSequence()));
- } else {
- $item
- ->setHref($child->getFragment()->getURI())
- ->addAttribute(pht('Directory'));
- }
-
- $list->addItem($item);
- }
-
- $title = pht('View Snapshot');
-
- $view = array(
- $this->renderConfigurationWarningIfRequired(),
- $box,
- $list,
- );
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($view);
- }
-
- protected function createSnapshotView($snapshot) {
- if ($snapshot === null) {
- return null;
- }
-
- $viewer = $this->getRequest()->getUser();
-
- $header = id(new PHUIHeaderView())
- ->setHeader(pht('"%s" Snapshot', $snapshot->getName()))
- ->setPolicyObject($snapshot)
- ->setUser($viewer);
-
- $zip_uri = $this->getApplicationURI(
- 'zip@'.$snapshot->getName().
- '/'.$snapshot->getPrimaryFragment()->getPath());
-
- $can_edit = PhabricatorPolicyFilter::hasCapability(
- $viewer,
- $snapshot,
- PhabricatorPolicyCapability::CAN_EDIT);
-
- $actions = id(new PhabricatorActionListView())
- ->setUser($viewer)
- ->setObject($snapshot);
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Download Snapshot as ZIP'))
- ->setHref($this->isCorrectlyConfigured() ? $zip_uri : null)
- ->setDisabled(!$this->isCorrectlyConfigured())
- ->setIcon('fa-floppy-o'));
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Delete Snapshot'))
- ->setHref($this->getApplicationURI(
- 'snapshot/delete/'.$snapshot->getID().'/'))
- ->setDisabled(!$can_edit)
- ->setWorkflow(true)
- ->setIcon('fa-times'));
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Promote Another Snapshot to Here'))
- ->setHref($this->getApplicationURI(
- 'snapshot/promote/'.$snapshot->getID().'/'))
- ->setDisabled(!$can_edit)
- ->setWorkflow(true)
- ->setIcon('fa-arrow-up'));
-
- $properties = id(new PHUIPropertyListView())
- ->setUser($viewer)
- ->setObject($snapshot)
- ->setActionList($actions);
-
- $properties->addProperty(
- pht('Name'),
- $snapshot->getName());
- $properties->addProperty(
- pht('Fragment'),
- $viewer->renderHandle($snapshot->getPrimaryFragmentPHID()));
-
- return id(new PHUIObjectBoxView())
- ->setHeader($header)
- ->addPropertyList($properties);
- }
-}
diff --git a/src/applications/phragment/controller/PhragmentUpdateController.php b/src/applications/phragment/controller/PhragmentUpdateController.php
deleted file mode 100644
index 7fb91ccd4e..0000000000
--- a/src/applications/phragment/controller/PhragmentUpdateController.php
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-
-final class PhragmentUpdateController extends PhragmentController {
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $dblob = $request->getURIData('dblob');
-
- $parents = $this->loadParentFragments($dblob);
- if ($parents === null) {
- return new Aphront404Response();
- }
- $fragment = idx($parents, count($parents) - 1, null);
-
- $error_view = null;
-
- if ($request->isFormPost()) {
- $errors = array();
-
- $v_fileid = $request->getInt('fileID');
-
- $file = id(new PhabricatorFile())->load($v_fileid);
- if ($file === null) {
- $errors[] = pht('The specified file doesn\'t exist.');
- }
-
- if (!count($errors)) {
- // If the file is a ZIP archive (has application/zip mimetype)
- // then we extract the zip and apply versions for each of the
- // individual fragments, creating and deleting files as needed.
- if ($file->getMimeType() === 'application/zip') {
- $fragment->updateFromZIP($viewer, $file);
- } else {
- $fragment->updateFromFile($viewer, $file);
- }
-
- return id(new AphrontRedirectResponse())
- ->setURI('/phragment/browse/'.$fragment->getPath());
- } else {
- $error_view = id(new PHUIInfoView())
- ->setErrors($errors)
- ->setTitle(pht('Errors while updating fragment'));
- }
- }
-
- $form = id(new AphrontFormView())
- ->setUser($viewer)
- ->appendChild(
- id(new AphrontFormTextControl())
- ->setLabel(pht('File ID'))
- ->setName('fileID'))
- ->appendChild(
- id(new AphrontFormSubmitControl())
- ->setValue(pht('Update Fragment'))
- ->addCancelButton(
- $this->getApplicationURI('browse/'.$fragment->getPath())));
-
- $crumbs = $this->buildApplicationCrumbsWithPath($parents);
- $crumbs->addTextCrumb(pht('Update Fragment'));
-
- $box = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Update Fragment: %s', $fragment->getPath()))
- ->setValidationException(null)
- ->setForm($form);
-
- $title = pht('Update Fragment');
-
- $view = array(
- $this->renderConfigurationWarningIfRequired(),
- $box,
- );
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($view);
-
- }
-
-}
diff --git a/src/applications/phragment/controller/PhragmentVersionController.php b/src/applications/phragment/controller/PhragmentVersionController.php
deleted file mode 100644
index 2b1ae2bdf0..0000000000
--- a/src/applications/phragment/controller/PhragmentVersionController.php
+++ /dev/null
@@ -1,125 +0,0 @@
-<?php
-
-final class PhragmentVersionController extends PhragmentController {
-
- public function shouldAllowPublic() {
- return true;
- }
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $id = $request->getURIData('id');
-
- $version = id(new PhragmentFragmentVersionQuery())
- ->setViewer($viewer)
- ->withIDs(array($id))
- ->executeOne();
- if ($version === null) {
- return new Aphront404Response();
- }
-
- $parents = $this->loadParentFragments($version->getFragment()->getPath());
- if ($parents === null) {
- return new Aphront404Response();
- }
- $current = idx($parents, count($parents) - 1, null);
-
- $crumbs = $this->buildApplicationCrumbsWithPath($parents);
- $crumbs->addTextCrumb(pht('View Version %d', $version->getSequence()));
-
- $file = id(new PhabricatorFileQuery())
- ->setViewer($viewer)
- ->withPHIDs(array($version->getFilePHID()))
- ->executeOne();
- if ($file !== null) {
- $file_uri = $file->getDownloadURI();
- }
-
- $header = id(new PHUIHeaderView())
- ->setHeader(pht(
- '%s at version %d',
- $version->getFragment()->getName(),
- $version->getSequence()))
- ->setPolicyObject($version)
- ->setUser($viewer);
-
- $actions = id(new PhabricatorActionListView())
- ->setUser($viewer)
- ->setObject($version);
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Download Version'))
- ->setDisabled($file === null || !$this->isCorrectlyConfigured())
- ->setHref($this->isCorrectlyConfigured() ? $file_uri : null)
- ->setIcon('fa-download'));
-
- $properties = id(new PHUIPropertyListView())
- ->setUser($viewer)
- ->setObject($version)
- ->setActionList($actions);
- $properties->addProperty(
- pht('File'),
- $viewer->renderHandle($version->getFilePHID()));
-
- $box = id(new PHUIObjectBoxView())
- ->setHeader($header)
- ->addPropertyList($properties);
-
- $title = pht('View Version');
-
- $view = array(
- $this->renderConfigurationWarningIfRequired(),
- $box,
- $this->renderPreviousVersionList($version),
- );
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($view);
- }
-
- private function renderPreviousVersionList(
- PhragmentFragmentVersion $version) {
- $viewer = $this->getViewer();
-
- $previous_versions = id(new PhragmentFragmentVersionQuery())
- ->setViewer($viewer)
- ->withFragmentPHIDs(array($version->getFragmentPHID()))
- ->withSequenceBefore($version->getSequence())
- ->execute();
-
- $list = id(new PHUIObjectItemListView())
- ->setUser($viewer);
-
- foreach ($previous_versions as $previous_version) {
- $item = id(new PHUIObjectItemView());
- $item->setHeader(pht('Version %s', $previous_version->getSequence()));
- $item->setHref($previous_version->getURI());
- $item->addAttribute(phabricator_datetime(
- $previous_version->getDateCreated(),
- $viewer));
- $patch_uri = $this->getApplicationURI(
- 'patch/'.$previous_version->getID().'/'.$version->getID());
- $item->addAction(id(new PHUIListItemView())
- ->setIcon('fa-file-o')
- ->setName(pht('Get Patch'))
- ->setHref($this->isCorrectlyConfigured() ? $patch_uri : null)
- ->setDisabled(!$this->isCorrectlyConfigured()));
- $list->addItem($item);
- }
-
- $item = id(new PHUIObjectItemView());
- $item->setHeader(pht('Prior to Version 0'));
- $item->addAttribute(pht('Prior to any content (empty file)'));
- $item->addAction(id(new PHUIListItemView())
- ->setIcon('fa-file-o')
- ->setName(pht('Get Patch'))
- ->setHref($this->getApplicationURI(
- 'patch/x/'.$version->getID())));
- $list->addItem($item);
-
- return $list;
- }
-
-}
diff --git a/src/applications/phragment/controller/PhragmentZIPController.php b/src/applications/phragment/controller/PhragmentZIPController.php
deleted file mode 100644
index 167a67857f..0000000000
--- a/src/applications/phragment/controller/PhragmentZIPController.php
+++ /dev/null
@@ -1,154 +0,0 @@
-<?php
-
-final class PhragmentZIPController extends PhragmentController {
-
- private $snapshotCache;
-
- public function shouldAllowPublic() {
- return true;
- }
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $dblob = $request->getURIData('dblob');
- $snapshot = $request->getURIData('snapshot');
-
- $parents = $this->loadParentFragments($dblob);
- if ($parents === null) {
- return new Aphront404Response();
- }
- $fragment = idx($parents, count($parents) - 1, null);
-
- if ($snapshot !== null) {
- $snapshot = id(new PhragmentSnapshotQuery())
- ->setViewer($viewer)
- ->withPrimaryFragmentPHIDs(array($fragment->getPHID()))
- ->withNames(array($snapshot))
- ->executeOne();
- if ($snapshot === null) {
- return new Aphront404Response();
- }
-
- $cache = id(new PhragmentSnapshotChildQuery())
- ->setViewer($viewer)
- ->needFragmentVersions(true)
- ->withSnapshotPHIDs(array($snapshot->getPHID()))
- ->execute();
- $this->snapshotCache = mpull(
- $cache,
- 'getFragmentVersion',
- 'getFragmentPHID');
- }
-
- $temp = new TempFile();
-
- $zip = null;
- try {
- $zip = new ZipArchive();
- } catch (Exception $e) {
- $dialog = new AphrontDialogView();
- $dialog->setUser($viewer);
-
- $inst = pht(
- 'This system does not have the ZIP PHP extension installed. This '.
- 'is required to download ZIPs from Phragment.');
-
- $dialog->setTitle(pht('ZIP Extension Not Installed'));
- $dialog->appendParagraph($inst);
-
- $dialog->addCancelButton('/phragment/browse/'.$dblob);
- return id(new AphrontDialogResponse())->setDialog($dialog);
- }
-
- if (!$zip->open((string)$temp, ZipArchive::CREATE)) {
- throw new Exception(pht('Unable to create ZIP archive!'));
- }
-
- $mappings = $this->getFragmentMappings(
- $fragment, $fragment->getPath(), $snapshot);
-
- $phids = array();
- foreach ($mappings as $path => $file_phid) {
- $phids[] = $file_phid;
- }
-
- $files = id(new PhabricatorFileQuery())
- ->setViewer($viewer)
- ->withPHIDs($phids)
- ->execute();
- $files = mpull($files, null, 'getPHID');
- foreach ($mappings as $path => $file_phid) {
- if (!isset($files[$file_phid])) {
- // The path is most likely pointing to a deleted fragment, which
- // hence no longer has a file associated with it.
- unset($mappings[$path]);
- continue;
- }
- $mappings[$path] = $files[$file_phid];
- }
-
- foreach ($mappings as $path => $file) {
- if ($file !== null) {
- $zip->addFromString($path, $file->loadFileData());
- }
- }
- $zip->close();
-
- $zip_name = $fragment->getName();
- if (substr($zip_name, -4) !== '.zip') {
- $zip_name .= '.zip';
- }
-
- $data = Filesystem::readFile((string)$temp);
-
- $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
- $file = PhabricatorFile::newFromFileData(
- $data,
- array(
- 'name' => $zip_name,
- 'ttl.relative' => phutil_units('24 hours in seconds'),
- ));
-
- $file->attachToObject($fragment->getPHID());
- unset($unguarded);
-
- $return = $fragment->getURI();
- if ($request->getExists('return')) {
- $return = $request->getStr('return');
- }
-
- return id(new AphrontRedirectResponse())
- ->setIsExternal(true)
- ->setURI($file->getDownloadURI($return));
- }
-
- /**
- * Returns a list of mappings like array('some/path.txt' => 'file PHID');
- */
- private function getFragmentMappings(
- PhragmentFragment $current,
- $base_path,
- $snapshot) {
- $mappings = $current->getFragmentMappings(
- $this->getRequest()->getUser(),
- $base_path);
-
- $result = array();
- foreach ($mappings as $path => $fragment) {
- $version = $this->getVersion($fragment, $snapshot);
- if ($version !== null) {
- $result[$path] = $version->getFilePHID();
- }
- }
- return $result;
- }
-
- private function getVersion($fragment, $snapshot) {
- if ($snapshot === null) {
- return $fragment->getLatestVersion();
- } else {
- return idx($this->snapshotCache, $fragment->getPHID(), null);
- }
- }
-
-}
diff --git a/src/applications/phragment/phid/PhragmentFragmentPHIDType.php b/src/applications/phragment/phid/PhragmentFragmentPHIDType.php
deleted file mode 100644
index 2947bca98e..0000000000
--- a/src/applications/phragment/phid/PhragmentFragmentPHIDType.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-final class PhragmentFragmentPHIDType extends PhabricatorPHIDType {
-
- const TYPECONST = 'PHRF';
-
- public function getTypeName() {
- return pht('Fragment');
- }
-
- public function newObject() {
- return new PhragmentFragment();
- }
-
- public function getPHIDTypeApplicationClass() {
- return 'PhabricatorPhragmentApplication';
- }
-
- protected function buildQueryForObjects(
- PhabricatorObjectQuery $query,
- array $phids) {
-
- return id(new PhragmentFragmentQuery())
- ->withPHIDs($phids);
- }
-
- public function loadHandles(
- PhabricatorHandleQuery $query,
- array $handles,
- array $objects) {
-
- $viewer = $query->getViewer();
- foreach ($handles as $phid => $handle) {
- $fragment = $objects[$phid];
-
- $handle->setName(pht(
- 'Fragment %s: %s',
- $fragment->getID(),
- $fragment->getName()));
- $handle->setURI($fragment->getURI());
- }
- }
-
-}
diff --git a/src/applications/phragment/phid/PhragmentFragmentVersionPHIDType.php b/src/applications/phragment/phid/PhragmentFragmentVersionPHIDType.php
deleted file mode 100644
index 2fa00b15ce..0000000000
--- a/src/applications/phragment/phid/PhragmentFragmentVersionPHIDType.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-final class PhragmentFragmentVersionPHIDType extends PhabricatorPHIDType {
-
- const TYPECONST = 'PHRV';
-
- public function getTypeName() {
- return pht('Fragment Version');
- }
-
- public function newObject() {
- return new PhragmentFragmentVersion();
- }
-
- public function getPHIDTypeApplicationClass() {
- return 'PhabricatorPhragmentApplication';
- }
-
- protected function buildQueryForObjects(
- PhabricatorObjectQuery $query,
- array $phids) {
-
- return id(new PhragmentFragmentVersionQuery())
- ->withPHIDs($phids);
- }
-
- public function loadHandles(
- PhabricatorHandleQuery $query,
- array $handles,
- array $objects) {
-
- $viewer = $query->getViewer();
- foreach ($handles as $phid => $handle) {
- $version = $objects[$phid];
-
- $handle->setName(pht(
- 'Fragment Version %d: %s',
- $version->getSequence(),
- $version->getFragment()->getName()));
- $handle->setURI($version->getURI());
- }
- }
-
-}
diff --git a/src/applications/phragment/phid/PhragmentSnapshotPHIDType.php b/src/applications/phragment/phid/PhragmentSnapshotPHIDType.php
deleted file mode 100644
index d97026a601..0000000000
--- a/src/applications/phragment/phid/PhragmentSnapshotPHIDType.php
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-
-final class PhragmentSnapshotPHIDType extends PhabricatorPHIDType {
-
- const TYPECONST = 'PHRS';
-
- public function getTypeName() {
- return pht('Snapshot');
- }
-
- public function newObject() {
- return new PhragmentSnapshot();
- }
-
- public function getPHIDTypeApplicationClass() {
- return 'PhabricatorPhragmentApplication';
- }
-
- protected function buildQueryForObjects(
- PhabricatorObjectQuery $query,
- array $phids) {
-
- return id(new PhragmentSnapshotQuery())
- ->withPHIDs($phids);
- }
-
- public function loadHandles(
- PhabricatorHandleQuery $query,
- array $handles,
- array $objects) {
-
- $viewer = $query->getViewer();
- foreach ($handles as $phid => $handle) {
- $snapshot = $objects[$phid];
-
- $handle->setName(pht(
- 'Snapshot: %s',
- $snapshot->getName()));
- $handle->setURI($snapshot->getURI());
- }
- }
-
-}
diff --git a/src/applications/phragment/query/PhragmentFragmentQuery.php b/src/applications/phragment/query/PhragmentFragmentQuery.php
deleted file mode 100644
index 56444217db..0000000000
--- a/src/applications/phragment/query/PhragmentFragmentQuery.php
+++ /dev/null
@@ -1,130 +0,0 @@
-<?php
-
-final class PhragmentFragmentQuery
- extends PhabricatorCursorPagedPolicyAwareQuery {
-
- private $ids;
- private $phids;
- private $paths;
- private $leadingPath;
- private $depths;
- private $needLatestVersion;
-
- public function withIDs(array $ids) {
- $this->ids = $ids;
- return $this;
- }
-
- public function withPHIDs(array $phids) {
- $this->phids = $phids;
- return $this;
- }
-
- public function withPaths(array $paths) {
- $this->paths = $paths;
- return $this;
- }
-
- public function withLeadingPath($path) {
- $this->leadingPath = $path;
- return $this;
- }
-
- public function withDepths($depths) {
- $this->depths = $depths;
- return $this;
- }
-
- public function needLatestVersion($need_latest_version) {
- $this->needLatestVersion = $need_latest_version;
- return $this;
- }
-
- protected function loadPage() {
- $table = new PhragmentFragment();
- $conn_r = $table->establishConnection('r');
-
- $data = queryfx_all(
- $conn_r,
- 'SELECT * FROM %T %Q %Q %Q',
- $table->getTableName(),
- $this->buildWhereClause($conn_r),
- $this->buildOrderClause($conn_r),
- $this->buildLimitClause($conn_r));
-
- return $table->loadAllFromArray($data);
- }
-
- protected function buildWhereClause(AphrontDatabaseConnection $conn) {
- $where = array();
-
- if ($this->ids) {
- $where[] = qsprintf(
- $conn,
- 'id IN (%Ld)',
- $this->ids);
- }
-
- if ($this->phids) {
- $where[] = qsprintf(
- $conn,
- 'phid IN (%Ls)',
- $this->phids);
- }
-
- if ($this->paths) {
- $where[] = qsprintf(
- $conn,
- 'path IN (%Ls)',
- $this->paths);
- }
-
- if ($this->leadingPath) {
- $where[] = qsprintf(
- $conn,
- 'path LIKE %>',
- $this->leadingPath);
- }
-
- if ($this->depths) {
- $where[] = qsprintf(
- $conn,
- 'depth IN (%Ld)',
- $this->depths);
- }
-
- $where[] = $this->buildPagingClause($conn);
-
- return $this->formatWhereClause($conn, $where);
- }
-
- protected function didFilterPage(array $page) {
- if ($this->needLatestVersion) {
- $versions = array();
-
- $version_phids = array_filter(mpull($page, 'getLatestVersionPHID'));
- if ($version_phids) {
- $versions = id(new PhabricatorObjectQuery())
- ->setViewer($this->getViewer())
- ->withPHIDs($version_phids)
- ->setParentQuery($this)
- ->execute();
- $versions = mpull($versions, null, 'getPHID');
- }
-
- foreach ($page as $key => $fragment) {
- $version_phid = $fragment->getLatestVersionPHID();
- if (empty($versions[$version_phid])) {
- continue;
- }
- $fragment->attachLatestVersion($versions[$version_phid]);
- }
- }
-
- return $page;
- }
-
- public function getQueryApplicationClass() {
- return 'PhabricatorPhragmentApplication';
- }
-}
diff --git a/src/applications/phragment/query/PhragmentFragmentVersionQuery.php b/src/applications/phragment/query/PhragmentFragmentVersionQuery.php
deleted file mode 100644
index e95c3260a8..0000000000
--- a/src/applications/phragment/query/PhragmentFragmentVersionQuery.php
+++ /dev/null
@@ -1,123 +0,0 @@
-<?php
-
-final class PhragmentFragmentVersionQuery
- extends PhabricatorCursorPagedPolicyAwareQuery {
-
- private $ids;
- private $phids;
- private $fragmentPHIDs;
- private $sequences;
- private $sequenceBefore;
-
- public function withIDs(array $ids) {
- $this->ids = $ids;
- return $this;
- }
-
- public function withPHIDs(array $phids) {
- $this->phids = $phids;
- return $this;
- }
-
- public function withFragmentPHIDs(array $fragment_phids) {
- $this->fragmentPHIDs = $fragment_phids;
- return $this;
- }
-
- public function withSequences(array $sequences) {
- $this->sequences = $sequences;
- return $this;
- }
-
- public function withSequenceBefore($current) {
- $this->sequenceBefore = $current;
- return $this;
- }
-
- protected function loadPage() {
- $table = new PhragmentFragmentVersion();
- $conn_r = $table->establishConnection('r');
-
- $data = queryfx_all(
- $conn_r,
- 'SELECT * FROM %T %Q %Q %Q',
- $table->getTableName(),
- $this->buildWhereClause($conn_r),
- $this->buildOrderClause($conn_r),
- $this->buildLimitClause($conn_r));
-
- return $table->loadAllFromArray($data);
- }
-
- protected function buildWhereClause(AphrontDatabaseConnection $conn) {
- $where = array();
-
- if ($this->ids) {
- $where[] = qsprintf(
- $conn,
- 'id IN (%Ld)',
- $this->ids);
- }
-
- if ($this->phids) {
- $where[] = qsprintf(
- $conn,
- 'phid IN (%Ls)',
- $this->phids);
- }
-
- if ($this->fragmentPHIDs) {
- $where[] = qsprintf(
- $conn,
- 'fragmentPHID IN (%Ls)',
- $this->fragmentPHIDs);
- }
-
- if ($this->sequences) {
- $where[] = qsprintf(
- $conn,
- 'sequence IN (%Ld)',
- $this->sequences);
- }
-
- if ($this->sequenceBefore !== null) {
- $where[] = qsprintf(
- $conn,
- 'sequence < %d',
- $this->sequenceBefore);
- }
-
- $where[] = $this->buildPagingClause($conn);
-
- return $this->formatWhereClause($conn, $where);
- }
-
- protected function willFilterPage(array $page) {
- $fragments = array();
-
- $fragment_phids = array_filter(mpull($page, 'getFragmentPHID'));
- if ($fragment_phids) {
- $fragments = id(new PhabricatorObjectQuery())
- ->setViewer($this->getViewer())
- ->withPHIDs($fragment_phids)
- ->setParentQuery($this)
- ->execute();
- $fragments = mpull($fragments, null, 'getPHID');
- }
-
- foreach ($page as $key => $version) {
- $fragment_phid = $version->getFragmentPHID();
- if (empty($fragments[$fragment_phid])) {
- unset($page[$key]);
- continue;
- }
- $version->attachFragment($fragments[$fragment_phid]);
- }
-
- return $page;
- }
-
- public function getQueryApplicationClass() {
- return 'PhabricatorPhragmentApplication';
- }
-}
diff --git a/src/applications/phragment/query/PhragmentSnapshotChildQuery.php b/src/applications/phragment/query/PhragmentSnapshotChildQuery.php
deleted file mode 100644
index faa3493499..0000000000
--- a/src/applications/phragment/query/PhragmentSnapshotChildQuery.php
+++ /dev/null
@@ -1,174 +0,0 @@
-<?php
-
-final class PhragmentSnapshotChildQuery
- extends PhabricatorCursorPagedPolicyAwareQuery {
-
- private $ids;
- private $snapshotPHIDs;
- private $fragmentPHIDs;
- private $fragmentVersionPHIDs;
- private $needFragments;
- private $needFragmentVersions;
-
- public function withIDs(array $ids) {
- $this->ids = $ids;
- return $this;
- }
-
- public function withSnapshotPHIDs(array $snapshot_phids) {
- $this->snapshotPHIDs = $snapshot_phids;
- return $this;
- }
-
- public function withFragmentPHIDs(array $fragment_phids) {
- $this->fragmentPHIDs = $fragment_phids;
- return $this;
- }
-
- public function withFragmentVersionPHIDs(array $fragment_version_phids) {
- $this->fragmentVersionPHIDs = $fragment_version_phids;
- return $this;
- }
-
- public function needFragments($need_fragments) {
- $this->needFragments = $need_fragments;
- return $this;
- }
-
- public function needFragmentVersions($need_fragment_versions) {
- $this->needFragmentVersions = $need_fragment_versions;
- return $this;
- }
-
- protected function loadPage() {
- $table = new PhragmentSnapshotChild();
- $conn_r = $table->establishConnection('r');
-
- $data = queryfx_all(
- $conn_r,
- 'SELECT * FROM %T %Q %Q %Q',
- $table->getTableName(),
- $this->buildWhereClause($conn_r),
- $this->buildOrderClause($conn_r),
- $this->buildLimitClause($conn_r));
-
- return $table->loadAllFromArray($data);
- }
-
- protected function buildWhereClause(AphrontDatabaseConnection $conn) {
- $where = array();
-
- if ($this->ids) {
- $where[] = qsprintf(
- $conn,
- 'id IN (%Ld)',
- $this->ids);
- }
-
- if ($this->snapshotPHIDs) {
- $where[] = qsprintf(
- $conn,
- 'snapshotPHID IN (%Ls)',
- $this->snapshotPHIDs);
- }
-
- if ($this->fragmentPHIDs) {
- $where[] = qsprintf(
- $conn,
- 'fragmentPHID IN (%Ls)',
- $this->fragmentPHIDs);
- }
-
- if ($this->fragmentVersionPHIDs) {
- $where[] = qsprintf(
- $conn,
- 'fragmentVersionPHID IN (%Ls)',
- $this->fragmentVersionPHIDs);
- }
-
- $where[] = $this->buildPagingClause($conn);
-
- return $this->formatWhereClause($conn, $where);
- }
-
- protected function willFilterPage(array $page) {
- $snapshots = array();
-
- $snapshot_phids = array_filter(mpull($page, 'getSnapshotPHID'));
- if ($snapshot_phids) {
- $snapshots = id(new PhabricatorObjectQuery())
- ->setViewer($this->getViewer())
- ->withPHIDs($snapshot_phids)
- ->setParentQuery($this)
- ->execute();
- $snapshots = mpull($snapshots, null, 'getPHID');
- }
-
- foreach ($page as $key => $child) {
- $snapshot_phid = $child->getSnapshotPHID();
- if (empty($snapshots[$snapshot_phid])) {
- unset($page[$key]);
- continue;
- }
- $child->attachSnapshot($snapshots[$snapshot_phid]);
- }
-
- return $page;
- }
-
- protected function didFilterPage(array $page) {
- if ($this->needFragments) {
- $fragments = array();
-
- $fragment_phids = array_filter(mpull($page, 'getFragmentPHID'));
- if ($fragment_phids) {
- $fragments = id(new PhabricatorObjectQuery())
- ->setViewer($this->getViewer())
- ->withPHIDs($fragment_phids)
- ->setParentQuery($this)
- ->execute();
- $fragments = mpull($fragments, null, 'getPHID');
- }
-
- foreach ($page as $key => $child) {
- $fragment_phid = $child->getFragmentPHID();
- if (empty($fragments[$fragment_phid])) {
- unset($page[$key]);
- continue;
- }
- $child->attachFragment($fragments[$fragment_phid]);
- }
- }
-
- if ($this->needFragmentVersions) {
- $fragment_versions = array();
-
- $fragment_version_phids = array_filter(mpull(
- $page,
- 'getFragmentVersionPHID'));
- if ($fragment_version_phids) {
- $fragment_versions = id(new PhabricatorObjectQuery())
- ->setViewer($this->getViewer())
- ->withPHIDs($fragment_version_phids)
- ->setParentQuery($this)
- ->execute();
- $fragment_versions = mpull($fragment_versions, null, 'getPHID');
- }
-
- foreach ($page as $key => $child) {
- $fragment_version_phid = $child->getFragmentVersionPHID();
- if (empty($fragment_versions[$fragment_version_phid])) {
- continue;
- }
- $child->attachFragmentVersion(
- $fragment_versions[$fragment_version_phid]);
- }
- }
-
- return $page;
- }
-
- public function getQueryApplicationClass() {
- return 'PhabricatorPhragmentApplication';
- }
-}
diff --git a/src/applications/phragment/query/PhragmentSnapshotQuery.php b/src/applications/phragment/query/PhragmentSnapshotQuery.php
deleted file mode 100644
index a4805650fc..0000000000
--- a/src/applications/phragment/query/PhragmentSnapshotQuery.php
+++ /dev/null
@@ -1,111 +0,0 @@
-<?php
-
-final class PhragmentSnapshotQuery
- extends PhabricatorCursorPagedPolicyAwareQuery {
-
- private $ids;
- private $phids;
- private $primaryFragmentPHIDs;
- private $names;
-
- public function withIDs(array $ids) {
- $this->ids = $ids;
- return $this;
- }
-
- public function withPHIDs(array $phids) {
- $this->phids = $phids;
- return $this;
- }
-
- public function withPrimaryFragmentPHIDs(array $primary_fragment_phids) {
- $this->primaryFragmentPHIDs = $primary_fragment_phids;
- return $this;
- }
-
- public function withNames(array $names) {
- $this->names = $names;
- return $this;
- }
-
- protected function loadPage() {
- $table = new PhragmentSnapshot();
- $conn_r = $table->establishConnection('r');
-
- $data = queryfx_all(
- $conn_r,
- 'SELECT * FROM %T %Q %Q %Q',
- $table->getTableName(),
- $this->buildWhereClause($conn_r),
- $this->buildOrderClause($conn_r),
- $this->buildLimitClause($conn_r));
-
- return $table->loadAllFromArray($data);
- }
-
- protected function buildWhereClause(AphrontDatabaseConnection $conn) {
- $where = array();
-
- if ($this->ids !== null) {
- $where[] = qsprintf(
- $conn,
- 'id IN (%Ld)',
- $this->ids);
- }
-
- if ($this->phids !== null) {
- $where[] = qsprintf(
- $conn,
- 'phid IN (%Ls)',
- $this->phids);
- }
-
- if ($this->primaryFragmentPHIDs !== null) {
- $where[] = qsprintf(
- $conn,
- 'primaryFragmentPHID IN (%Ls)',
- $this->primaryFragmentPHIDs);
- }
-
- if ($this->names !== null) {
- $where[] = qsprintf(
- $conn,
- 'name IN (%Ls)',
- $this->names);
- }
-
- $where[] = $this->buildPagingClause($conn);
-
- return $this->formatWhereClause($conn, $where);
- }
-
- protected function willFilterPage(array $page) {
- $fragments = array();
-
- $fragment_phids = array_filter(mpull($page, 'getPrimaryFragmentPHID'));
- if ($fragment_phids) {
- $fragments = id(new PhabricatorObjectQuery())
- ->setViewer($this->getViewer())
- ->withPHIDs($fragment_phids)
- ->setParentQuery($this)
- ->execute();
- $fragments = mpull($fragments, null, 'getPHID');
- }
-
- foreach ($page as $key => $snapshot) {
- $fragment_phid = $snapshot->getPrimaryFragmentPHID();
- if (empty($fragments[$fragment_phid])) {
- unset($page[$key]);
- continue;
- }
- $snapshot->attachPrimaryFragment($fragments[$fragment_phid]);
- }
-
- return $page;
- }
-
- public function getQueryApplicationClass() {
- return 'PhabricatorPhragmentApplication';
- }
-
-}
diff --git a/src/applications/phragment/storage/PhragmentDAO.php b/src/applications/phragment/storage/PhragmentDAO.php
deleted file mode 100644
index 5b3a9a20d7..0000000000
--- a/src/applications/phragment/storage/PhragmentDAO.php
+++ /dev/null
@@ -1,9 +0,0 @@
-<?php
-
-abstract class PhragmentDAO extends PhabricatorLiskDAO {
-
- public function getApplicationName() {
- return 'phragment';
- }
-
-}
diff --git a/src/applications/phragment/storage/PhragmentFragment.php b/src/applications/phragment/storage/PhragmentFragment.php
deleted file mode 100644
index 0f04783419..0000000000
--- a/src/applications/phragment/storage/PhragmentFragment.php
+++ /dev/null
@@ -1,349 +0,0 @@
-<?php
-
-final class PhragmentFragment extends PhragmentDAO
- implements PhabricatorPolicyInterface {
-
- protected $path;
- protected $depth;
- protected $latestVersionPHID;
- protected $viewPolicy;
- protected $editPolicy;
-
- private $latestVersion = self::ATTACHABLE;
- private $file = self::ATTACHABLE;
-
- protected function getConfiguration() {
- return array(
- self::CONFIG_AUX_PHID => true,
- self::CONFIG_COLUMN_SCHEMA => array(
- 'path' => 'text128',
- 'depth' => 'uint32',
- 'latestVersionPHID' => 'phid?',
- ),
- self::CONFIG_KEY_SCHEMA => array(
- 'key_path' => array(
- 'columns' => array('path'),
- 'unique' => true,
- ),
- ),
- ) + parent::getConfiguration();
- }
-
- public function generatePHID() {
- return PhabricatorPHID::generateNewPHID(
- PhragmentFragmentPHIDType::TYPECONST);
- }
-
- public function getURI() {
- return '/phragment/browse/'.$this->getPath();
- }
-
- public function getName() {
- return basename($this->path);
- }
-
- public function getFile() {
- return $this->assertAttached($this->file);
- }
-
- public function attachFile(PhabricatorFile $file) {
- return $this->file = $file;
- }
-
- public function isDirectory() {
- return $this->latestVersionPHID === null;
- }
-
- public function isDeleted() {
- return $this->getLatestVersion()->getFilePHID() === null;
- }
-
- public function getLatestVersion() {
- if ($this->latestVersionPHID === null) {
- return null;
- }
- return $this->assertAttached($this->latestVersion);
- }
-
- public function attachLatestVersion(PhragmentFragmentVersion $version) {
- return $this->latestVersion = $version;
- }
-
-
-/* -( Updating ) --------------------------------------------------------- */
-
-
- /**
- * Create a new fragment from a file.
- */
- public static function createFromFile(
- PhabricatorUser $viewer,
- PhabricatorFile $file = null,
- $path = null,
- $view_policy = null,
- $edit_policy = null) {
-
- $fragment = id(new PhragmentFragment());
- $fragment->setPath($path);
- $fragment->setDepth(count(explode('/', $path)));
- $fragment->setLatestVersionPHID(null);
- $fragment->setViewPolicy($view_policy);
- $fragment->setEditPolicy($edit_policy);
- $fragment->save();
-
- // Directory fragments have no versions associated with them, so we
- // just return the fragment at this point.
- if ($file === null) {
- return $fragment;
- }
-
- if ($file->getMimeType() === 'application/zip') {
- $fragment->updateFromZIP($viewer, $file);
- } else {
- $fragment->updateFromFile($viewer, $file);
- }
-
- return $fragment;
- }
-
-
- /**
- * Set the specified file as the next version for the fragment.
- */
- public function updateFromFile(
- PhabricatorUser $viewer,
- PhabricatorFile $file) {
-
- $existing = id(new PhragmentFragmentVersionQuery())
- ->setViewer($viewer)
- ->withFragmentPHIDs(array($this->getPHID()))
- ->execute();
- $sequence = count($existing);
-
- $this->openTransaction();
- $version = id(new PhragmentFragmentVersion());
- $version->setSequence($sequence);
- $version->setFragmentPHID($this->getPHID());
- $version->setFilePHID($file->getPHID());
- $version->save();
-
- $this->setLatestVersionPHID($version->getPHID());
- $this->save();
- $this->saveTransaction();
-
- $file->attachToObject($version->getPHID());
- }
-
- /**
- * Apply the specified ZIP archive onto the fragment, removing
- * and creating fragments as needed.
- */
- public function updateFromZIP(
- PhabricatorUser $viewer,
- PhabricatorFile $file) {
-
- if ($file->getMimeType() !== 'application/zip') {
- throw new Exception(
- pht("File must have mimetype '%s'.", 'application/zip'));
- }
-
- // First apply the ZIP as normal.
- $this->updateFromFile($viewer, $file);
-
- // Ensure we have ZIP support.
- $zip = null;
- try {
- $zip = new ZipArchive();
- } catch (Exception $e) {
- // The server doesn't have php5-zip, so we can't do recursive updates.
- return;
- }
-
- $temp = new TempFile();
- Filesystem::writeFile($temp, $file->loadFileData());
- if (!$zip->open($temp)) {
- throw new Exception(pht('Unable to open ZIP.'));
- }
-
- // Get all of the paths and their data from the ZIP.
- $mappings = array();
- for ($i = 0; $i < $zip->numFiles; $i++) {
- $path = trim($zip->getNameIndex($i), '/');
- $stream = $zip->getStream($path);
- $data = null;
- // If the stream is false, then it is a directory entry. We leave
- // $data set to null for directories so we know not to create a
- // version entry for them.
- if ($stream !== false) {
- $data = stream_get_contents($stream);
- fclose($stream);
- }
- $mappings[$path] = $data;
- }
-
- // We need to detect any directories that are in the ZIP folder that
- // aren't explicitly noted in the ZIP. This can happen if the file
- // entries in the ZIP look like:
- //
- // * something/blah.png
- // * something/other.png
- // * test.png
- //
- // Where there is no explicit "something/" entry.
- foreach ($mappings as $path_key => $data) {
- if ($data === null) {
- continue;
- }
- $directory = dirname($path_key);
- while ($directory !== '.') {
- if (!array_key_exists($directory, $mappings)) {
- $mappings[$directory] = null;
- }
- if (dirname($directory) === $directory) {
- // dirname() will not reduce this directory any further; to
- // prevent infinite loop we just break out here.
- break;
- }
- $directory = dirname($directory);
- }
- }
-
- // Adjust the paths relative to this fragment so we can look existing
- // fragments up in the DB.
- $base_path = $this->getPath();
- $paths = array();
- foreach ($mappings as $p => $data) {
- $paths[] = $base_path.'/'.$p;
- }
-
- // FIXME: What happens when a child exists, but the current user
- // can't see it. We're going to create a new child with the exact
- // same path and then bad things will happen.
- $children = id(new PhragmentFragmentQuery())
- ->setViewer($viewer)
- ->needLatestVersion(true)
- ->withLeadingPath($this->getPath().'/')
- ->execute();
- $children = mpull($children, null, 'getPath');
-
- // Iterate over the existing fragments.
- foreach ($children as $full_path => $child) {
- $path = substr($full_path, strlen($base_path) + 1);
- if (array_key_exists($path, $mappings)) {
- if ($child->isDirectory() && $mappings[$path] === null) {
- // Don't create a version entry for a directory
- // (unless it's been converted into a file).
- continue;
- }
-
- // The file is being updated.
- $file = PhabricatorFile::newFromFileData(
- $mappings[$path],
- array('name' => basename($path)));
- $child->updateFromFile($viewer, $file);
- } else {
- // The file is being deleted.
- $child->deleteFile($viewer);
- }
- }
-
- // Iterate over the mappings to find new files.
- foreach ($mappings as $path => $data) {
- if (!array_key_exists($base_path.'/'.$path, $children)) {
- // The file is being created. If the data is null,
- // then this is explicitly a directory being created.
- $file = null;
- if ($mappings[$path] !== null) {
- $file = PhabricatorFile::newFromFileData(
- $mappings[$path],
- array('name' => basename($path)));
- }
- self::createFromFile(
- $viewer,
- $file,
- $base_path.'/'.$path,
- $this->getViewPolicy(),
- $this->getEditPolicy());
- }
- }
- }
-
- /**
- * Delete the contents of the specified fragment.
- */
- public function deleteFile(PhabricatorUser $viewer) {
- $existing = id(new PhragmentFragmentVersionQuery())
- ->setViewer($viewer)
- ->withFragmentPHIDs(array($this->getPHID()))
- ->execute();
- $sequence = count($existing);
-
- $this->openTransaction();
- $version = id(new PhragmentFragmentVersion());
- $version->setSequence($sequence);
- $version->setFragmentPHID($this->getPHID());
- $version->setFilePHID(null);
- $version->save();
-
- $this->setLatestVersionPHID($version->getPHID());
- $this->save();
- $this->saveTransaction();
- }
-
-
-/* -( Utility ) ---------------------------------------------------------- */
-
-
- public function getFragmentMappings(
- PhabricatorUser $viewer,
- $base_path) {
-
- $children = id(new PhragmentFragmentQuery())
- ->setViewer($viewer)
- ->needLatestVersion(true)
- ->withLeadingPath($this->getPath().'/')
- ->withDepths(array($this->getDepth() + 1))
- ->execute();
-
- if (count($children) === 0) {
- $path = substr($this->getPath(), strlen($base_path) + 1);
- return array($path => $this);
- } else {
- $mappings = array();
- foreach ($children as $child) {
- $child_mappings = $child->getFragmentMappings(
- $viewer,
- $base_path);
- foreach ($child_mappings as $key => $value) {
- $mappings[$key] = $value;
- }
- }
- return $mappings;
- }
- }
-
-
-/* -( Policy Interface )--------------------------------------------------- */
-
-
- public function getCapabilities() {
- return array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- );
- }
-
- public function getPolicy($capability) {
- switch ($capability) {
- case PhabricatorPolicyCapability::CAN_VIEW:
- return $this->getViewPolicy();
- case PhabricatorPolicyCapability::CAN_EDIT:
- return $this->getEditPolicy();
- }
- }
-
- public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
- return false;
- }
-
-}
diff --git a/src/applications/phragment/storage/PhragmentFragmentVersion.php b/src/applications/phragment/storage/PhragmentFragmentVersion.php
deleted file mode 100644
index 88e501ee29..0000000000
--- a/src/applications/phragment/storage/PhragmentFragmentVersion.php
+++ /dev/null
@@ -1,72 +0,0 @@
-<?php
-
-final class PhragmentFragmentVersion extends PhragmentDAO
- implements PhabricatorPolicyInterface {
-
- protected $sequence;
- protected $fragmentPHID;
- protected $filePHID;
-
- private $fragment = self::ATTACHABLE;
- private $file = self::ATTACHABLE;
-
- protected function getConfiguration() {
- return array(
- self::CONFIG_AUX_PHID => true,
- self::CONFIG_COLUMN_SCHEMA => array(
- 'sequence' => 'uint32',
- 'filePHID' => 'phid?',
- ),
- self::CONFIG_KEY_SCHEMA => array(
- 'key_version' => array(
- 'columns' => array('fragmentPHID', 'sequence'),
- 'unique' => true,
- ),
- ),
- ) + parent::getConfiguration();
- }
-
- public function generatePHID() {
- return PhabricatorPHID::generateNewPHID(
- PhragmentFragmentVersionPHIDType::TYPECONST);
- }
-
- public function getURI() {
- return '/phragment/version/'.$this->getID().'/';
- }
-
- public function getFragment() {
- return $this->assertAttached($this->fragment);
- }
-
- public function attachFragment(PhragmentFragment $fragment) {
- return $this->fragment = $fragment;
- }
-
- public function getFile() {
- return $this->assertAttached($this->file);
- }
-
- public function attachFile(PhabricatorFile $file) {
- return $this->file = $file;
- }
-
- public function getCapabilities() {
- return array(
- PhabricatorPolicyCapability::CAN_VIEW,
- );
- }
-
- public function getPolicy($capability) {
- return $this->getFragment()->getPolicy($capability);
- }
-
- public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
- return $this->getFragment()->hasAutomaticCapability($capability, $viewer);
- }
-
- public function describeAutomaticCapability($capability) {
- return $this->getFragment()->describeAutomaticCapability($capability);
- }
-
-}
diff --git a/src/applications/phragment/storage/PhragmentSchemaSpec.php b/src/applications/phragment/storage/PhragmentSchemaSpec.php
deleted file mode 100644
index b2be73c172..0000000000
--- a/src/applications/phragment/storage/PhragmentSchemaSpec.php
+++ /dev/null
@@ -1,9 +0,0 @@
-<?php
-
-final class PhragmentSchemaSpec extends PhabricatorConfigSchemaSpec {
-
- public function buildSchemata() {
- $this->buildEdgeSchemata(new PhragmentFragment());
- }
-
-}
diff --git a/src/applications/phragment/storage/PhragmentSnapshot.php b/src/applications/phragment/storage/PhragmentSnapshot.php
deleted file mode 100644
index 53c5443c7a..0000000000
--- a/src/applications/phragment/storage/PhragmentSnapshot.php
+++ /dev/null
@@ -1,76 +0,0 @@
-<?php
-
-final class PhragmentSnapshot extends PhragmentDAO
- implements PhabricatorPolicyInterface {
-
- protected $primaryFragmentPHID;
- protected $name;
-
- private $primaryFragment = self::ATTACHABLE;
-
- protected function getConfiguration() {
- return array(
- self::CONFIG_AUX_PHID => true,
- self::CONFIG_COLUMN_SCHEMA => array(
- 'name' => 'text128',
- ),
- self::CONFIG_KEY_SCHEMA => array(
- 'key_name' => array(
- 'columns' => array('primaryFragmentPHID', 'name'),
- 'unique' => true,
- ),
- ),
- ) + parent::getConfiguration();
- }
-
- public function generatePHID() {
- return PhabricatorPHID::generateNewPHID(
- PhragmentSnapshotPHIDType::TYPECONST);
- }
-
- public function getURI() {
- return '/phragment/snapshot/view/'.$this->getID().'/';
- }
-
- public function getPrimaryFragment() {
- return $this->assertAttached($this->primaryFragment);
- }
-
- public function attachPrimaryFragment(PhragmentFragment $fragment) {
- return $this->primaryFragment = $fragment;
- }
-
- public function delete() {
- $children = id(new PhragmentSnapshotChild())
- ->loadAllWhere('snapshotPHID = %s', $this->getPHID());
- $this->openTransaction();
- foreach ($children as $child) {
- $child->delete();
- }
- $result = parent::delete();
- $this->saveTransaction();
- return $result;
- }
-
-
-/* -( Policy Interface )--------------------------------------------------- */
-
-
- public function getCapabilities() {
- return $this->getPrimaryFragment()->getCapabilities();
- }
-
- public function getPolicy($capability) {
- return $this->getPrimaryFragment()->getPolicy($capability);
- }
-
- public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
- return $this->getPrimaryFragment()
- ->hasAutomaticCapability($capability, $viewer);
- }
-
- public function describeAutomaticCapability($capability) {
- return $this->getPrimaryFragment()
- ->describeAutomaticCapability($capability);
- }
-}
diff --git a/src/applications/phragment/storage/PhragmentSnapshotChild.php b/src/applications/phragment/storage/PhragmentSnapshotChild.php
deleted file mode 100644
index 3f0692ede6..0000000000
--- a/src/applications/phragment/storage/PhragmentSnapshotChild.php
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-
-final class PhragmentSnapshotChild extends PhragmentDAO
- implements PhabricatorPolicyInterface {
-
- protected $snapshotPHID;
- protected $fragmentPHID;
- protected $fragmentVersionPHID;
-
- private $snapshot = self::ATTACHABLE;
- private $fragment = self::ATTACHABLE;
- private $fragmentVersion = self::ATTACHABLE;
-
- protected function getConfiguration() {
- return array(
- self::CONFIG_COLUMN_SCHEMA => array(
- 'fragmentVersionPHID' => 'phid?',
- ),
- self::CONFIG_KEY_SCHEMA => array(
- 'key_child' => array(
- 'columns' => array(
- 'snapshotPHID',
- 'fragmentPHID',
- 'fragmentVersionPHID',
- ),
- 'unique' => true,
- ),
- ),
- ) + parent::getConfiguration();
- }
-
- public function getSnapshot() {
- return $this->assertAttached($this->snapshot);
- }
-
- public function attachSnapshot(PhragmentSnapshot $snapshot) {
- return $this->snapshot = $snapshot;
- }
-
- public function getFragment() {
- return $this->assertAttached($this->fragment);
- }
-
- public function attachFragment(PhragmentFragment $fragment) {
- return $this->fragment = $fragment;
- }
-
- public function getFragmentVersion() {
- if ($this->fragmentVersionPHID === null) {
- return null;
- }
- return $this->assertAttached($this->fragmentVersion);
- }
-
- public function attachFragmentVersion(PhragmentFragmentVersion $version) {
- return $this->fragmentVersion = $version;
- }
-
-
-/* -( Policy Interface )--------------------------------------------------- */
-
-
- public function getCapabilities() {
- return array(
- PhabricatorPolicyCapability::CAN_VIEW,
- );
- }
-
- public function getPolicy($capability) {
- return $this->getSnapshot()->getPolicy($capability);
- }
-
- public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
- return $this->getSnapshot()
- ->hasAutomaticCapability($capability, $viewer);
- }
-
- public function describeAutomaticCapability($capability) {
- return $this->getSnapshot()
- ->describeAutomaticCapability($capability);
- }
-}
diff --git a/src/applications/phragment/util/PhragmentPatchUtil.php b/src/applications/phragment/util/PhragmentPatchUtil.php
deleted file mode 100644
index 5319ba6034..0000000000
--- a/src/applications/phragment/util/PhragmentPatchUtil.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-
-final class PhragmentPatchUtil extends Phobject {
-
- const EMPTY_HASH = '0000000000000000000000000000000000000000';
-
- /**
- * Calculate the DiffMatchPatch patch between two Phabricator files.
- *
- * @phutil-external-symbol class diff_match_patch
- */
- public static function calculatePatch(
- PhabricatorFile $old = null,
- PhabricatorFile $new = null) {
-
- $root = dirname(phutil_get_library_root('phabricator'));
- require_once $root.'/externals/diff_match_patch/diff_match_patch.php';
-
- $old_hash = self::EMPTY_HASH;
- $new_hash = self::EMPTY_HASH;
-
- if ($old !== null) {
- $old_hash = $old->getContentHash();
- }
- if ($new !== null) {
- $new_hash = $new->getContentHash();
- }
-
- $old_content = '';
- $new_content = '';
-
- if ($old_hash === $new_hash) {
- return null;
- }
-
- if ($old_hash !== self::EMPTY_HASH) {
- $old_content = $old->loadFileData();
- } else {
- $old_content = '';
- }
-
- if ($new_hash !== self::EMPTY_HASH) {
- $new_content = $new->loadFileData();
- } else {
- $new_content = '';
- }
-
- $dmp = new diff_match_patch();
- $dmp_patches = $dmp->patch_make($old_content, $new_content);
- return $dmp->patch_toText($dmp_patches);
- }
-
-}
diff --git a/src/applications/phriction/query/PhrictionContentQuery.php b/src/applications/phriction/query/PhrictionContentQuery.php
index 6efab5e1c6..8ac92be351 100644
--- a/src/applications/phriction/query/PhrictionContentQuery.php
+++ b/src/applications/phriction/query/PhrictionContentQuery.php
@@ -1,128 +1,124 @@
<?php
final class PhrictionContentQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $documentPHIDs;
private $versions;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withDocumentPHIDs(array $phids) {
$this->documentPHIDs = $phids;
return $this;
}
public function withVersions(array $versions) {
$this->versions = $versions;
return $this;
}
public function newResultObject() {
return new PhrictionContent();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'c.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'c.phid IN (%Ls)',
$this->phids);
}
if ($this->versions !== null) {
$where[] = qsprintf(
$conn,
'version IN (%Ld)',
$this->versions);
}
if ($this->documentPHIDs !== null) {
$where[] = qsprintf(
$conn,
'd.phid IN (%Ls)',
$this->documentPHIDs);
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->shouldJoinDocumentTable()) {
$joins[] = qsprintf(
$conn,
'JOIN %T d ON d.phid = c.documentPHID',
id(new PhrictionDocument())->getTableName());
}
return $joins;
}
protected function willFilterPage(array $contents) {
$document_phids = mpull($contents, 'getDocumentPHID');
$documents = id(new PhrictionDocumentQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($document_phids)
->execute();
$documents = mpull($documents, null, 'getPHID');
foreach ($contents as $key => $content) {
$document_phid = $content->getDocumentPHID();
$document = idx($documents, $document_phid);
if (!$document) {
unset($contents[$key]);
$this->didRejectResult($content);
continue;
}
$content->attachDocument($document);
}
return $contents;
}
private function shouldJoinDocumentTable() {
if ($this->documentPHIDs !== null) {
return true;
}
return false;
}
protected function getPrimaryTableAlias() {
return 'c';
}
public function getQueryApplicationClass() {
return 'PhabricatorPhrictionApplication';
}
}
diff --git a/src/applications/phriction/query/PhrictionDocumentQuery.php b/src/applications/phriction/query/PhrictionDocumentQuery.php
index e7b5a0529e..298db97dbb 100644
--- a/src/applications/phriction/query/PhrictionDocumentQuery.php
+++ b/src/applications/phriction/query/PhrictionDocumentQuery.php
@@ -1,402 +1,398 @@
<?php
final class PhrictionDocumentQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $slugs;
private $depths;
private $slugPrefix;
private $statuses;
private $parentPaths;
private $ancestorPaths;
private $needContent;
const ORDER_HIERARCHY = 'hierarchy';
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withSlugs(array $slugs) {
$this->slugs = $slugs;
return $this;
}
public function withDepths(array $depths) {
$this->depths = $depths;
return $this;
}
public function withSlugPrefix($slug_prefix) {
$this->slugPrefix = $slug_prefix;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function withParentPaths(array $paths) {
$this->parentPaths = $paths;
return $this;
}
public function withAncestorPaths(array $paths) {
$this->ancestorPaths = $paths;
return $this;
}
public function needContent($need_content) {
$this->needContent = $need_content;
return $this;
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
public function newResultObject() {
return new PhrictionDocument();
}
protected function willFilterPage(array $documents) {
if ($documents) {
$ancestor_slugs = array();
foreach ($documents as $key => $document) {
$document_slug = $document->getSlug();
foreach (PhabricatorSlug::getAncestry($document_slug) as $ancestor) {
$ancestor_slugs[$ancestor][] = $key;
}
}
if ($ancestor_slugs) {
$table = new PhrictionDocument();
$conn_r = $table->establishConnection('r');
$ancestors = queryfx_all(
$conn_r,
'SELECT * FROM %T WHERE slug IN (%Ls)',
$document->getTableName(),
array_keys($ancestor_slugs));
$ancestors = $table->loadAllFromArray($ancestors);
$ancestors = mpull($ancestors, null, 'getSlug');
foreach ($ancestor_slugs as $ancestor_slug => $document_keys) {
$ancestor = idx($ancestors, $ancestor_slug);
foreach ($document_keys as $document_key) {
$documents[$document_key]->attachAncestor(
$ancestor_slug,
$ancestor);
}
}
}
}
// To view a Phriction document, you must also be able to view all of the
// ancestor documents. Filter out documents which have ancestors that are
// not visible.
$document_map = array();
foreach ($documents as $document) {
$document_map[$document->getSlug()] = $document;
foreach ($document->getAncestors() as $key => $ancestor) {
if ($ancestor) {
$document_map[$key] = $ancestor;
}
}
}
$filtered_map = $this->applyPolicyFilter(
$document_map,
array(PhabricatorPolicyCapability::CAN_VIEW));
// Filter all of the documents where a parent is not visible.
foreach ($documents as $document_key => $document) {
// If the document itself is not visible, filter it.
if (!isset($filtered_map[$document->getSlug()])) {
$this->didRejectResult($documents[$document_key]);
unset($documents[$document_key]);
continue;
}
// If an ancestor exists but is not visible, filter the document.
foreach ($document->getAncestors() as $ancestor_key => $ancestor) {
if (!$ancestor) {
continue;
}
if (!isset($filtered_map[$ancestor_key])) {
$this->didRejectResult($documents[$document_key]);
unset($documents[$document_key]);
break;
}
}
}
if (!$documents) {
return $documents;
}
if ($this->needContent) {
$contents = id(new PhrictionContentQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs(mpull($documents, 'getContentPHID'))
->execute();
$contents = mpull($contents, null, 'getPHID');
foreach ($documents as $key => $document) {
$content_phid = $document->getContentPHID();
if (empty($contents[$content_phid])) {
unset($documents[$key]);
continue;
}
$document->attachContent($contents[$content_phid]);
}
}
return $documents;
}
protected function buildSelectClauseParts(AphrontDatabaseConnection $conn) {
$select = parent::buildSelectClauseParts($conn);
if ($this->shouldJoinContentTable()) {
$select[] = qsprintf($conn, 'c.title');
}
return $select;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->shouldJoinContentTable()) {
$content_dao = new PhrictionContent();
$joins[] = qsprintf(
$conn,
'JOIN %T c ON d.contentPHID = c.phid',
$content_dao->getTableName());
}
return $joins;
}
private function shouldJoinContentTable() {
if ($this->getOrderVector()->containsKey('title')) {
return true;
}
return false;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'd.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'd.phid IN (%Ls)',
$this->phids);
}
if ($this->slugs !== null) {
$where[] = qsprintf(
$conn,
'd.slug IN (%Ls)',
$this->slugs);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'd.status IN (%Ls)',
$this->statuses);
}
if ($this->slugPrefix !== null) {
$where[] = qsprintf(
$conn,
'd.slug LIKE %>',
$this->slugPrefix);
}
if ($this->depths !== null) {
$where[] = qsprintf(
$conn,
'd.depth IN (%Ld)',
$this->depths);
}
if ($this->parentPaths !== null || $this->ancestorPaths !== null) {
$sets = array(
array(
'paths' => $this->parentPaths,
'parents' => true,
),
array(
'paths' => $this->ancestorPaths,
'parents' => false,
),
);
$paths = array();
foreach ($sets as $set) {
$set_paths = $set['paths'];
if ($set_paths === null) {
continue;
}
if (!$set_paths) {
throw new PhabricatorEmptyQueryException(
pht('No parent/ancestor paths specified.'));
}
$is_parents = $set['parents'];
foreach ($set_paths as $path) {
$path_normal = PhabricatorSlug::normalize($path);
if ($path !== $path_normal) {
throw new Exception(
pht(
'Document path "%s" is not a valid path. The normalized '.
'form of this path is "%s".',
$path,
$path_normal));
}
$depth = PhabricatorSlug::getDepth($path_normal);
if ($is_parents) {
$min_depth = $depth + 1;
$max_depth = $depth + 1;
} else {
$min_depth = $depth + 1;
$max_depth = null;
}
$paths[] = array(
$path_normal,
$min_depth,
$max_depth,
);
}
}
$path_clauses = array();
foreach ($paths as $path) {
$parts = array();
list($prefix, $min, $max) = $path;
// If we're getting children or ancestors of the root document, they
// aren't actually stored with the leading "/" in the database, so
// just skip this part of the clause.
if ($prefix !== '/') {
$parts[] = qsprintf(
$conn,
'd.slug LIKE %>',
$prefix);
}
if ($min !== null) {
$parts[] = qsprintf(
$conn,
'd.depth >= %d',
$min);
}
if ($max !== null) {
$parts[] = qsprintf(
$conn,
'd.depth <= %d',
$max);
}
if ($parts) {
$path_clauses[] = qsprintf($conn, '%LA', $parts);
}
}
if ($path_clauses) {
$where[] = qsprintf($conn, '%LO', $path_clauses);
}
}
return $where;
}
public function getBuiltinOrders() {
return parent::getBuiltinOrders() + array(
self::ORDER_HIERARCHY => array(
'vector' => array('depth', 'title', 'updated', 'id'),
'name' => pht('Hierarchy'),
),
);
}
public function getOrderableColumns() {
return parent::getOrderableColumns() + array(
'depth' => array(
'table' => 'd',
'column' => 'depth',
'reverse' => true,
'type' => 'int',
),
'title' => array(
'table' => 'c',
'column' => 'title',
'reverse' => true,
'type' => 'string',
),
'updated' => array(
'table' => 'd',
'column' => 'editedEpoch',
'type' => 'int',
'unique' => false,
),
);
}
protected function newPagingMapFromCursorObject(
PhabricatorQueryCursor $cursor,
array $keys) {
$document = $cursor->getObject();
$map = array(
'id' => (int)$document->getID(),
'depth' => $document->getDepth(),
'updated' => (int)$document->getEditedEpoch(),
);
if (isset($keys['title'])) {
$map['title'] = $cursor->getRawRowProperty('title');
}
return $map;
}
protected function getPrimaryTableAlias() {
return 'd';
}
public function getQueryApplicationClass() {
return 'PhabricatorPhrictionApplication';
}
}
diff --git a/src/applications/phurl/query/PhabricatorPhurlURLQuery.php b/src/applications/phurl/query/PhabricatorPhurlURLQuery.php
index 6efbbd5b4c..c30cedf09d 100644
--- a/src/applications/phurl/query/PhabricatorPhurlURLQuery.php
+++ b/src/applications/phurl/query/PhabricatorPhurlURLQuery.php
@@ -1,112 +1,108 @@
<?php
final class PhabricatorPhurlURLQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $names;
private $longURLs;
private $aliases;
private $authorPHIDs;
public function newResultObject() {
return new PhabricatorPhurlURL();
}
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withNames(array $names) {
$this->names = $names;
return $this;
}
public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint(
id(new PhabricatorPhurlURLNameNgrams()),
$ngrams);
}
public function withLongURLs(array $long_urls) {
$this->longURLs = $long_urls;
return $this;
}
public function withAliases(array $aliases) {
$this->aliases = $aliases;
return $this;
}
public function withAuthorPHIDs(array $author_phids) {
$this->authorPHIDs = $author_phids;
return $this;
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'url.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'url.phid IN (%Ls)',
$this->phids);
}
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'url.authorPHID IN (%Ls)',
$this->authorPHIDs);
}
if ($this->names !== null) {
$where[] = qsprintf(
$conn,
'url.name IN (%Ls)',
$this->names);
}
if ($this->longURLs !== null) {
$where[] = qsprintf(
$conn,
'url.longURL IN (%Ls)',
$this->longURLs);
}
if ($this->aliases !== null) {
$where[] = qsprintf(
$conn,
'url.alias IN (%Ls)',
$this->aliases);
}
return $where;
}
protected function getPrimaryTableAlias() {
return 'url';
}
public function getQueryApplicationClass() {
return 'PhabricatorPhurlApplication';
}
}
diff --git a/src/applications/policy/config/PhabricatorPolicyConfigOptions.php b/src/applications/policy/config/PhabricatorPolicyConfigOptions.php
index eacc32cebe..f7ac362b0f 100644
--- a/src/applications/policy/config/PhabricatorPolicyConfigOptions.php
+++ b/src/applications/policy/config/PhabricatorPolicyConfigOptions.php
@@ -1,72 +1,72 @@
<?php
final class PhabricatorPolicyConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Policy');
}
public function getDescription() {
return pht('Options relating to object visibility.');
}
public function getIcon() {
return 'fa-lock';
}
public function getGroup() {
return 'apps';
}
public function getOptions() {
$policy_locked_type = 'custom:PolicyLockOptionType';
$policy_locked_example = array(
'people.create.users' => 'admin',
);
$json = new PhutilJSON();
$policy_locked_example = $json->encodeFormatted($policy_locked_example);
return array(
$this->newOption('policy.allow-public', 'bool', false)
->setBoolOptions(
array(
pht('Allow Public Visibility'),
pht('Require Login'),
))
->setSummary(pht('Allow users to set object visibility to public.'))
->setDescription(
pht(
- "Phabricator allows you to set the visibility of objects (like ".
+ "This software allows you to set the visibility of objects (like ".
"repositories and tasks) to 'Public', which means **anyone ".
"on the internet can see them, without needing to log in or ".
"have an account**.".
"\n\n".
"This is intended for open source projects. Many installs will ".
"never want to make anything public, so this policy is disabled ".
"by default. You can enable it here, which will let you set the ".
"policy for objects to 'Public'.".
"\n\n".
"Enabling this setting will immediately open up some features, ".
"like the user directory. Anyone on the internet will be able to ".
"access these features.".
"\n\n".
"With this setting disabled, the 'Public' policy is not ".
"available, and the most open policy is 'All Users' (which means ".
"users must have accounts and be logged in to view things).")),
$this->newOption('policy.locked', $policy_locked_type, array())
->setLocked(true)
->setSummary(pht(
'Lock specific application policies so they can not be edited.'))
->setDescription(pht(
- 'Phabricator has application policies which can dictate whether '.
+ 'This software has application policies which can dictate whether '.
'users can take certain actions, such as creating new users. '."\n\n".
'This setting allows for "locking" these policies such that no '.
'further edits can be made on a per-policy basis.'))
->addExample(
$policy_locked_example,
pht('Lock Create User Policy To Admins')),
);
}
}
diff --git a/src/applications/policy/query/PhabricatorPolicyQuery.php b/src/applications/policy/query/PhabricatorPolicyQuery.php
index 018007db28..9bd6ba5994 100644
--- a/src/applications/policy/query/PhabricatorPolicyQuery.php
+++ b/src/applications/policy/query/PhabricatorPolicyQuery.php
@@ -1,428 +1,432 @@
<?php
final class PhabricatorPolicyQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $object;
private $phids;
const OBJECT_POLICY_PREFIX = 'obj.';
public function setObject(PhabricatorPolicyInterface $object) {
$this->object = $object;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public static function loadPolicies(
PhabricatorUser $viewer,
PhabricatorPolicyInterface $object) {
$results = array();
$map = array();
foreach ($object->getCapabilities() as $capability) {
$map[$capability] = $object->getPolicy($capability);
}
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->withPHIDs($map)
->execute();
foreach ($map as $capability => $phid) {
$results[$capability] = $policies[$phid];
}
return $results;
}
public static function renderPolicyDescriptions(
PhabricatorUser $viewer,
PhabricatorPolicyInterface $object) {
$policies = self::loadPolicies($viewer, $object);
foreach ($policies as $capability => $policy) {
$policies[$capability] = $policy->newRef($viewer)
->newCapabilityLink($object, $capability);
}
return $policies;
}
protected function loadPage() {
if ($this->object && $this->phids) {
throw new Exception(
pht(
'You can not issue a policy query with both %s and %s.',
'setObject()',
'setPHIDs()'));
} else if ($this->object) {
$phids = $this->loadObjectPolicyPHIDs();
} else {
$phids = $this->phids;
}
$phids = array_fuse($phids);
$results = array();
// First, load global policies.
foreach (self::getGlobalPolicies() as $phid => $policy) {
if (isset($phids[$phid])) {
$results[$phid] = $policy;
unset($phids[$phid]);
}
}
// Now, load object policies.
foreach (self::getObjectPolicies($this->object) as $phid => $policy) {
if (isset($phids[$phid])) {
$results[$phid] = $policy;
unset($phids[$phid]);
}
}
// If we still need policies, we're going to have to fetch data. Bucket
// the remaining policies into rule-based policies and handle-based
// policies.
if ($phids) {
$rule_policies = array();
$handle_policies = array();
foreach ($phids as $phid) {
$phid_type = phid_get_type($phid);
if ($phid_type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
$rule_policies[$phid] = $phid;
} else {
$handle_policies[$phid] = $phid;
}
}
if ($handle_policies) {
$handles = id(new PhabricatorHandleQuery())
->setViewer($this->getViewer())
->withPHIDs($handle_policies)
->execute();
foreach ($handle_policies as $phid) {
$results[$phid] = PhabricatorPolicy::newFromPolicyAndHandle(
$phid,
$handles[$phid]);
}
}
if ($rule_policies) {
$rules = id(new PhabricatorPolicy())->loadAllWhere(
'phid IN (%Ls)',
$rule_policies);
$results += mpull($rules, null, 'getPHID');
}
}
$results = msort($results, 'getSortKey');
return $results;
}
public static function isGlobalPolicy($policy) {
$global_policies = self::getGlobalPolicies();
if (isset($global_policies[$policy])) {
return true;
}
return false;
}
public static function getGlobalPolicy($policy) {
if (!self::isGlobalPolicy($policy)) {
throw new Exception(pht("Policy '%s' is not a global policy!", $policy));
}
return idx(self::getGlobalPolicies(), $policy);
}
private static function getGlobalPolicies() {
static $constants = array(
PhabricatorPolicies::POLICY_PUBLIC,
PhabricatorPolicies::POLICY_USER,
PhabricatorPolicies::POLICY_ADMIN,
PhabricatorPolicies::POLICY_NOONE,
);
$results = array();
foreach ($constants as $constant) {
$results[$constant] = id(new PhabricatorPolicy())
->setType(PhabricatorPolicyType::TYPE_GLOBAL)
->setPHID($constant)
->setName(self::getGlobalPolicyName($constant))
->setShortName(self::getGlobalPolicyShortName($constant))
->makeEphemeral();
}
return $results;
}
private static function getGlobalPolicyName($policy) {
switch ($policy) {
case PhabricatorPolicies::POLICY_PUBLIC:
return pht('Public (No Login Required)');
case PhabricatorPolicies::POLICY_USER:
return pht('All Users');
case PhabricatorPolicies::POLICY_ADMIN:
return pht('Administrators');
case PhabricatorPolicies::POLICY_NOONE:
return pht('No One');
default:
return pht('Unknown Policy');
}
}
private static function getGlobalPolicyShortName($policy) {
switch ($policy) {
case PhabricatorPolicies::POLICY_PUBLIC:
return pht('Public');
default:
return null;
}
}
private function loadObjectPolicyPHIDs() {
$phids = array();
$viewer = $this->getViewer();
if ($viewer->getPHID()) {
$pref_key = PhabricatorPolicyFavoritesSetting::SETTINGKEY;
$favorite_limit = 10;
$default_limit = 5;
// If possible, show the user's 10 most recently used projects.
$favorites = $viewer->getUserSetting($pref_key);
if (!is_array($favorites)) {
$favorites = array();
}
$favorite_phids = array_keys($favorites);
$favorite_phids = array_slice($favorite_phids, -$favorite_limit);
if ($favorite_phids) {
$projects = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withPHIDs($favorite_phids)
->withIsMilestone(false)
->setLimit($favorite_limit)
->execute();
$projects = mpull($projects, null, 'getPHID');
} else {
$projects = array();
}
// If we didn't find enough favorites, add some default projects. These
// are just arbitrary projects that the viewer is a member of, but may
// be useful on smaller installs and for new users until they can use
// the control enough time to establish useful favorites.
if (count($projects) < $default_limit) {
$default_projects = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withMemberPHIDs(array($viewer->getPHID()))
->withIsMilestone(false)
->withStatuses(
array(
PhabricatorProjectStatus::STATUS_ACTIVE,
))
->setLimit($default_limit)
->execute();
$default_projects = mpull($default_projects, null, 'getPHID');
$projects = $projects + $default_projects;
$projects = array_slice($projects, 0, $default_limit);
}
foreach ($projects as $project) {
$phids[] = $project->getPHID();
}
// Include the "current viewer" policy. This improves consistency, but
// is also useful for creating private instances of normally-shared object
// types, like repositories.
$phids[] = $viewer->getPHID();
}
$capabilities = $this->object->getCapabilities();
foreach ($capabilities as $capability) {
$policy = $this->object->getPolicy($capability);
if (!$policy) {
continue;
}
$phids[] = $policy;
}
// If this install doesn't have "Public" enabled, don't include it as an
// option unless the object already has a "Public" policy. In this case we
// retain the policy but enforce it as though it was "All Users".
$show_public = PhabricatorEnv::getEnvConfig('policy.allow-public');
foreach (self::getGlobalPolicies() as $phid => $policy) {
if ($phid == PhabricatorPolicies::POLICY_PUBLIC) {
if (!$show_public) {
continue;
}
}
$phids[] = $phid;
}
foreach (self::getObjectPolicies($this->object) as $phid => $policy) {
$phids[] = $phid;
}
return $phids;
}
protected function shouldDisablePolicyFiltering() {
// Policy filtering of policies is currently perilous and not required by
// the application.
return true;
}
public function getQueryApplicationClass() {
return 'PhabricatorPolicyApplication';
}
public static function isSpecialPolicy($identifier) {
+ if ($identifier === null) {
+ return true;
+ }
+
if (self::isObjectPolicy($identifier)) {
return true;
}
if (self::isGlobalPolicy($identifier)) {
return true;
}
return false;
}
/* -( Object Policies )---------------------------------------------------- */
public static function isObjectPolicy($identifier) {
$prefix = self::OBJECT_POLICY_PREFIX;
return !strncmp($identifier, $prefix, strlen($prefix));
}
public static function getObjectPolicy($identifier) {
if (!self::isObjectPolicy($identifier)) {
return null;
}
$policies = self::getObjectPolicies(null);
return idx($policies, $identifier);
}
public static function getObjectPolicyRule($identifier) {
if (!self::isObjectPolicy($identifier)) {
return null;
}
$rules = self::getObjectPolicyRules(null);
return idx($rules, $identifier);
}
public static function getObjectPolicies($object) {
$rule_map = self::getObjectPolicyRules($object);
$results = array();
foreach ($rule_map as $key => $rule) {
$results[$key] = id(new PhabricatorPolicy())
->setType(PhabricatorPolicyType::TYPE_OBJECT)
->setPHID($key)
->setIcon($rule->getObjectPolicyIcon())
->setName($rule->getObjectPolicyName())
->setShortName($rule->getObjectPolicyShortName())
->makeEphemeral();
}
return $results;
}
public static function getObjectPolicyRules($object) {
$rules = id(new PhutilClassMapQuery())
->setAncestorClass('PhabricatorPolicyRule')
->execute();
$results = array();
foreach ($rules as $rule) {
$key = $rule->getObjectPolicyKey();
if (!$key) {
continue;
}
$full_key = $rule->getObjectPolicyFullKey();
if (isset($results[$full_key])) {
throw new Exception(
pht(
'Two policy rules (of classes "%s" and "%s") define the same '.
'object policy key ("%s"), but each object policy rule must use '.
'a unique key.',
get_class($rule),
get_class($results[$full_key]),
$key));
}
$results[$full_key] = $rule;
}
if ($object !== null) {
foreach ($results as $key => $rule) {
if (!$rule->canApplyToObject($object)) {
unset($results[$key]);
}
}
}
return $results;
}
public static function getDefaultPolicyForObject(
PhabricatorUser $viewer,
PhabricatorPolicyInterface $object,
$capability) {
$phid = $object->getPHID();
if (!$phid) {
return null;
}
$type = phid_get_type($phid);
$map = self::getDefaultObjectTypePolicyMap();
if (empty($map[$type][$capability])) {
return null;
}
$policy_phid = $map[$type][$capability];
return id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->withPHIDs(array($policy_phid))
->executeOne();
}
private static function getDefaultObjectTypePolicyMap() {
static $map;
if ($map === null) {
$map = array();
$apps = PhabricatorApplication::getAllApplications();
foreach ($apps as $app) {
$map += $app->getDefaultObjectTypePolicyMap();
}
}
return $map;
}
}
diff --git a/src/applications/policy/storage/PhabricatorPolicy.php b/src/applications/policy/storage/PhabricatorPolicy.php
index 7904f17927..5e5ec14107 100644
--- a/src/applications/policy/storage/PhabricatorPolicy.php
+++ b/src/applications/policy/storage/PhabricatorPolicy.php
@@ -1,514 +1,525 @@
<?php
final class PhabricatorPolicy
extends PhabricatorPolicyDAO
implements
PhabricatorPolicyInterface,
PhabricatorDestructibleInterface {
const ACTION_ALLOW = 'allow';
const ACTION_DENY = 'deny';
private $name;
private $shortName;
private $type;
private $href;
private $workflow;
private $icon;
protected $rules = array();
protected $defaultAction = self::ACTION_DENY;
private $ruleObjects = self::ATTACHABLE;
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'rules' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'defaultAction' => 'text32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPolicyPHIDTypePolicy::TYPECONST);
}
public static function newFromPolicyAndHandle(
$policy_identifier,
PhabricatorObjectHandle $handle = null) {
$is_global = PhabricatorPolicyQuery::isGlobalPolicy($policy_identifier);
if ($is_global) {
return PhabricatorPolicyQuery::getGlobalPolicy($policy_identifier);
}
$policy = PhabricatorPolicyQuery::getObjectPolicy($policy_identifier);
if ($policy) {
return $policy;
}
if (!$handle) {
throw new Exception(
pht(
"Policy identifier is an object PHID ('%s'), but no object handle ".
"was provided. A handle must be provided for object policies.",
$policy_identifier));
}
$handle_phid = $handle->getPHID();
if ($policy_identifier != $handle_phid) {
throw new Exception(
pht(
"Policy identifier is an object PHID ('%s'), but the provided ".
"handle has a different PHID ('%s'). The handle must correspond ".
"to the policy identifier.",
$policy_identifier,
$handle_phid));
}
$policy = id(new PhabricatorPolicy())
->setPHID($policy_identifier)
->setHref($handle->getURI());
$phid_type = phid_get_type($policy_identifier);
switch ($phid_type) {
case PhabricatorProjectProjectPHIDType::TYPECONST:
$policy
->setType(PhabricatorPolicyType::TYPE_PROJECT)
->setName($handle->getName())
->setIcon($handle->getIcon());
break;
case PhabricatorPeopleUserPHIDType::TYPECONST:
$policy->setType(PhabricatorPolicyType::TYPE_USER);
$policy->setName($handle->getFullName());
break;
case PhabricatorPolicyPHIDTypePolicy::TYPECONST:
// TODO: This creates a weird handle-based version of a rule policy.
// It behaves correctly, but can't be applied since it doesn't have
// any rules. It is used to render transactions, and might need some
// cleanup.
break;
default:
$policy->setType(PhabricatorPolicyType::TYPE_MASKED);
$policy->setName($handle->getFullName());
break;
}
$policy->makeEphemeral();
return $policy;
}
public function setType($type) {
$this->type = $type;
return $this;
}
public function getType() {
if (!$this->type) {
return PhabricatorPolicyType::TYPE_CUSTOM;
}
return $this->type;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
if (!$this->name) {
return pht('Custom Policy');
}
return $this->name;
}
public function setShortName($short_name) {
$this->shortName = $short_name;
return $this;
}
public function getShortName() {
if ($this->shortName) {
return $this->shortName;
}
return $this->getName();
}
public function setHref($href) {
$this->href = $href;
return $this;
}
public function getHref() {
return $this->href;
}
public function setWorkflow($workflow) {
$this->workflow = $workflow;
return $this;
}
public function getWorkflow() {
return $this->workflow;
}
public function setIcon($icon) {
$this->icon = $icon;
return $this;
}
public function getIcon() {
if ($this->icon) {
return $this->icon;
}
switch ($this->getType()) {
case PhabricatorPolicyType::TYPE_GLOBAL:
static $map = array(
PhabricatorPolicies::POLICY_PUBLIC => 'fa-globe',
PhabricatorPolicies::POLICY_USER => 'fa-users',
PhabricatorPolicies::POLICY_ADMIN => 'fa-eye',
PhabricatorPolicies::POLICY_NOONE => 'fa-ban',
);
return idx($map, $this->getPHID(), 'fa-question-circle');
case PhabricatorPolicyType::TYPE_USER:
return 'fa-user';
case PhabricatorPolicyType::TYPE_PROJECT:
return 'fa-briefcase';
case PhabricatorPolicyType::TYPE_CUSTOM:
case PhabricatorPolicyType::TYPE_MASKED:
return 'fa-certificate';
default:
return 'fa-question-circle';
}
}
public function getSortKey() {
return sprintf(
'%02d%s',
PhabricatorPolicyType::getPolicyTypeOrder($this->getType()),
$this->getSortName());
}
private function getSortName() {
if ($this->getType() == PhabricatorPolicyType::TYPE_GLOBAL) {
static $map = array(
PhabricatorPolicies::POLICY_PUBLIC => 0,
PhabricatorPolicies::POLICY_USER => 1,
PhabricatorPolicies::POLICY_ADMIN => 2,
PhabricatorPolicies::POLICY_NOONE => 3,
);
return idx($map, $this->getPHID());
}
return $this->getName();
}
public static function getPolicyExplanation(
PhabricatorUser $viewer,
$policy) {
$type = phid_get_type($policy);
if ($type === PhabricatorProjectProjectPHIDType::TYPECONST) {
$handle = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs(array($policy))
->executeOne();
return pht(
'Members of the project "%s" can take this action.',
$handle->getFullName());
}
return self::getOpaquePolicyExplanation($viewer, $policy);
}
public static function getOpaquePolicyExplanation(
PhabricatorUser $viewer,
$policy) {
$rule = PhabricatorPolicyQuery::getObjectPolicyRule($policy);
if ($rule) {
return $rule->getPolicyExplanation();
}
switch ($policy) {
case PhabricatorPolicies::POLICY_PUBLIC:
return pht(
'This object is public and can be viewed by anyone, even if they '.
- 'do not have a Phabricator account.');
+ 'do not have an account on this server.');
case PhabricatorPolicies::POLICY_USER:
return pht('Logged in users can take this action.');
case PhabricatorPolicies::POLICY_ADMIN:
return pht('Administrators can take this action.');
case PhabricatorPolicies::POLICY_NOONE:
return pht('By default, no one can take this action.');
default:
$handle = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs(array($policy))
->executeOne();
$type = phid_get_type($policy);
if ($type == PhabricatorProjectProjectPHIDType::TYPECONST) {
return pht(
'Members of a particular project can take this action. (You '.
'can not see this object, so the name of this project is '.
'restricted.)');
} else if ($type == PhabricatorPeopleUserPHIDType::TYPECONST) {
return pht(
'%s can take this action.',
$handle->getFullName());
} else if ($type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
return pht(
'This object has a custom policy controlling who can take this '.
'action.');
} else {
return pht(
'This object has an unknown or invalid policy setting ("%s").',
$policy);
}
}
}
public function getFullName() {
switch ($this->getType()) {
case PhabricatorPolicyType::TYPE_PROJECT:
return pht('Members of Project: %s', $this->getName());
case PhabricatorPolicyType::TYPE_MASKED:
return pht('Other: %s', $this->getName());
case PhabricatorPolicyType::TYPE_USER:
return pht('Only User: %s', $this->getName());
default:
return $this->getName();
}
}
public function newRef(PhabricatorUser $viewer) {
return id(new PhabricatorPolicyRef())
->setViewer($viewer)
->setPolicy($this);
}
public function isProjectPolicy() {
return ($this->getType() === PhabricatorPolicyType::TYPE_PROJECT);
}
public function isCustomPolicy() {
return ($this->getType() === PhabricatorPolicyType::TYPE_CUSTOM);
}
public function isMaskedPolicy() {
return ($this->getType() === PhabricatorPolicyType::TYPE_MASKED);
}
/**
* Return a list of custom rule classes (concrete subclasses of
* @{class:PhabricatorPolicyRule}) this policy uses.
*
* @return list<string> List of class names.
*/
public function getCustomRuleClasses() {
$classes = array();
foreach ($this->getRules() as $rule) {
if (!is_array($rule)) {
// This rule is invalid. We'll reject it later, but don't need to
// extract anything from it for now.
continue;
}
$class = idx($rule, 'rule');
try {
if (class_exists($class)) {
$classes[$class] = $class;
}
} catch (Exception $ex) {
continue;
}
}
return array_keys($classes);
}
/**
* Return a list of all values used by a given rule class to implement this
* policy. This is used to bulk load data (like project memberships) in order
* to apply policy filters efficiently.
*
* @param string Policy rule classname.
* @return list<wild> List of values used in this policy.
*/
public function getCustomRuleValues($rule_class) {
$values = array();
foreach ($this->getRules() as $rule) {
if ($rule['rule'] == $rule_class) {
$values[] = $rule['value'];
}
}
return $values;
}
public function attachRuleObjects(array $objects) {
$this->ruleObjects = $objects;
return $this;
}
public function getRuleObjects() {
return $this->assertAttached($this->ruleObjects);
}
/**
* Return `true` if this policy is stronger (more restrictive) than some
* other policy.
*
* Because policies are complicated, determining which policies are
* "stronger" is not trivial. This method uses a very coarse working
* definition of policy strength which is cheap to compute, unambiguous,
* and intuitive in the common cases.
*
* This method returns `true` if the //class// of this policy is stronger
* than the other policy, even if the policies are (or might be) the same in
* practice. For example, "Members of Project X" is considered a stronger
* policy than "All Users", even though "Project X" might (in some rare
* cases) contain every user.
*
* Generally, the ordering here is:
*
* - Public
* - All Users
* - (Everything Else)
* - No One
*
* In the "everything else" bucket, we can't make any broad claims about
* which policy is stronger (and we especially can't make those claims
* cheaply).
*
* Even if we fully evaluated each policy, the two policies might be
* "Members of X" and "Members of Y", each of which permits access to some
* set of unique users. In this case, neither is strictly stronger than
* the other.
*
* @param PhabricatorPolicy Other policy.
* @return bool `true` if this policy is more restrictive than the other
* policy.
*/
public function isStrongerThan(PhabricatorPolicy $other) {
$this_policy = $this->getPHID();
$other_policy = $other->getPHID();
$strengths = array(
PhabricatorPolicies::POLICY_PUBLIC => -2,
PhabricatorPolicies::POLICY_USER => -1,
// (Default policies have strength 0.)
PhabricatorPolicies::POLICY_NOONE => 1,
);
- $this_strength = idx($strengths, $this->getPHID(), 0);
- $other_strength = idx($strengths, $other->getPHID(), 0);
+ $this_strength = idx($strengths, $this_policy, 0);
+ $other_strength = idx($strengths, $other_policy, 0);
return ($this_strength > $other_strength);
}
+ public function isStrongerThanOrEqualTo(PhabricatorPolicy $other) {
+ $this_policy = $this->getPHID();
+ $other_policy = $other->getPHID();
+
+ if ($this_policy === $other_policy) {
+ return true;
+ }
+
+ return $this->isStrongerThan($other);
+ }
+
public function isValidPolicyForEdit() {
return $this->getType() !== PhabricatorPolicyType::TYPE_MASKED;
}
public static function getSpecialRules(
PhabricatorPolicyInterface $object,
PhabricatorUser $viewer,
$capability,
$active_only) {
$exceptions = array();
if ($object instanceof PhabricatorPolicyCodexInterface) {
$codex = id(PhabricatorPolicyCodex::newFromObject($object, $viewer))
->setCapability($capability);
$rules = $codex->getPolicySpecialRuleDescriptions();
foreach ($rules as $rule) {
$is_active = $rule->getIsActive();
if ($is_active) {
$rule_capabilities = $rule->getCapabilities();
if ($rule_capabilities) {
if (!in_array($capability, $rule_capabilities)) {
$is_active = false;
}
}
}
if (!$is_active && $active_only) {
continue;
}
$description = $rule->getDescription();
if (!$is_active) {
$description = phutil_tag(
'span',
array(
'class' => 'phui-policy-section-view-inactive-rule',
),
$description);
}
$exceptions[] = $description;
}
}
if (!$exceptions) {
if (method_exists($object, 'describeAutomaticCapability')) {
$exceptions = (array)$object->describeAutomaticCapability($capability);
$exceptions = array_filter($exceptions);
}
}
return $exceptions;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
// NOTE: We implement policies only so we can comply with the interface.
// The actual query skips them, as enforcing policies on policies seems
// perilous and isn't currently required by the application.
return PhabricatorPolicies::POLICY_PUBLIC;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->delete();
}
}
diff --git a/src/applications/ponder/query/PonderAnswerQuery.php b/src/applications/ponder/query/PonderAnswerQuery.php
index 2901f4d6a5..f100f05ae3 100644
--- a/src/applications/ponder/query/PonderAnswerQuery.php
+++ b/src/applications/ponder/query/PonderAnswerQuery.php
@@ -1,88 +1,84 @@
<?php
final class PonderAnswerQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $authorPHIDs;
private $questionIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withAuthorPHIDs(array $phids) {
$this->authorPHIDs = $phids;
return $this;
}
public function withQuestionIDs(array $ids) {
$this->questionIDs = $ids;
return $this;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'authorPHID IN (%Ls)',
$this->authorPHIDs);
}
return $where;
}
public function newResultObject() {
return new PonderAnswer();
}
- protected function loadPage() {
- return $this->loadStandardPage(new PonderAnswer());
- }
-
protected function willFilterPage(array $answers) {
$questions = id(new PonderQuestionQuery())
->setViewer($this->getViewer())
->withIDs(mpull($answers, 'getQuestionID'))
->execute();
foreach ($answers as $key => $answer) {
$question = idx($questions, $answer->getQuestionID());
if (!$question) {
unset($answers[$key]);
continue;
}
$answer->attachQuestion($question);
}
return $answers;
}
public function getQueryApplicationClass() {
return 'PhabricatorPonderApplication';
}
}
diff --git a/src/applications/ponder/query/PonderQuestionQuery.php b/src/applications/ponder/query/PonderQuestionQuery.php
index b2de14e52c..323f34aac5 100644
--- a/src/applications/ponder/query/PonderQuestionQuery.php
+++ b/src/applications/ponder/query/PonderQuestionQuery.php
@@ -1,154 +1,150 @@
<?php
final class PonderQuestionQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $status;
private $authorPHIDs;
private $answererPHIDs;
private $needProjectPHIDs;
private $needAnswers;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withAuthorPHIDs(array $phids) {
$this->authorPHIDs = $phids;
return $this;
}
public function withStatuses($status) {
$this->status = $status;
return $this;
}
public function withAnswererPHIDs(array $phids) {
$this->answererPHIDs = $phids;
return $this;
}
public function needAnswers($need_answers) {
$this->needAnswers = $need_answers;
return $this;
}
public function needProjectPHIDs($need_projects) {
$this->needProjectPHIDs = $need_projects;
return $this;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'q.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'q.phid IN (%Ls)',
$this->phids);
}
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'q.authorPHID IN (%Ls)',
$this->authorPHIDs);
}
if ($this->status !== null) {
$where[] = qsprintf(
$conn,
'q.status IN (%Ls)',
$this->status);
}
return $where;
}
public function newResultObject() {
return new PonderQuestion();
}
- protected function loadPage() {
- return $this->loadStandardPage(new PonderQuestion());
- }
-
protected function willFilterPage(array $questions) {
$phids = mpull($questions, 'getPHID');
if ($this->needAnswers) {
$aquery = id(new PonderAnswerQuery())
->setViewer($this->getViewer())
->setOrderVector(array('-id'))
->withQuestionIDs(mpull($questions, 'getID'));
$answers = $aquery->execute();
$answers = mgroup($answers, 'getQuestionID');
foreach ($questions as $question) {
$question_answers = idx($answers, $question->getID(), array());
$question->attachAnswers(mpull($question_answers, null, 'getPHID'));
}
}
if ($this->needProjectPHIDs) {
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs($phids)
->withEdgeTypes(
array(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
));
$edge_query->execute();
foreach ($questions as $question) {
$project_phids = $edge_query->getDestinationPHIDs(
array($question->getPHID()));
$question->attachProjectPHIDs($project_phids);
}
}
return $questions;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->answererPHIDs) {
$answer_table = new PonderAnswer();
$joins[] = qsprintf(
$conn,
'JOIN %T a ON a.questionID = q.id AND a.authorPHID IN (%Ls)',
$answer_table->getTableName(),
$this->answererPHIDs);
}
return $joins;
}
protected function getPrimaryTableAlias() {
return 'q';
}
public function getQueryApplicationClass() {
return 'PhabricatorPonderApplication';
}
}
diff --git a/src/applications/project/query/PhabricatorProjectColumnPositionQuery.php b/src/applications/project/query/PhabricatorProjectColumnPositionQuery.php
index 438c558e6e..2673902780 100644
--- a/src/applications/project/query/PhabricatorProjectColumnPositionQuery.php
+++ b/src/applications/project/query/PhabricatorProjectColumnPositionQuery.php
@@ -1,77 +1,73 @@
<?php
final class PhabricatorProjectColumnPositionQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $boardPHIDs;
private $objectPHIDs;
private $columnPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withBoardPHIDs(array $board_phids) {
$this->boardPHIDs = $board_phids;
return $this;
}
public function withObjectPHIDs(array $object_phids) {
$this->objectPHIDs = $object_phids;
return $this;
}
public function withColumnPHIDs(array $column_phids) {
$this->columnPHIDs = $column_phids;
return $this;
}
public function newResultObject() {
return new PhabricatorProjectColumnPosition();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = array();
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->boardPHIDs !== null) {
$where[] = qsprintf(
$conn,
'boardPHID IN (%Ls)',
$this->boardPHIDs);
}
if ($this->objectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'objectPHID IN (%Ls)',
$this->objectPHIDs);
}
if ($this->columnPHIDs !== null) {
$where[] = qsprintf(
$conn,
'columnPHID IN (%Ls)',
$this->columnPHIDs);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorProjectApplication';
}
}
diff --git a/src/applications/project/query/PhabricatorProjectColumnQuery.php b/src/applications/project/query/PhabricatorProjectColumnQuery.php
index 380dab5208..478d872cb4 100644
--- a/src/applications/project/query/PhabricatorProjectColumnQuery.php
+++ b/src/applications/project/query/PhabricatorProjectColumnQuery.php
@@ -1,235 +1,231 @@
<?php
final class PhabricatorProjectColumnQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $projectPHIDs;
private $proxyPHIDs;
private $statuses;
private $isProxyColumn;
private $triggerPHIDs;
private $needTriggers;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withProjectPHIDs(array $project_phids) {
$this->projectPHIDs = $project_phids;
return $this;
}
public function withProxyPHIDs(array $proxy_phids) {
$this->proxyPHIDs = $proxy_phids;
return $this;
}
public function withStatuses(array $status) {
$this->statuses = $status;
return $this;
}
public function withIsProxyColumn($is_proxy) {
$this->isProxyColumn = $is_proxy;
return $this;
}
public function withTriggerPHIDs(array $trigger_phids) {
$this->triggerPHIDs = $trigger_phids;
return $this;
}
public function needTriggers($need_triggers) {
$this->needTriggers = true;
return $this;
}
public function newResultObject() {
return new PhabricatorProjectColumn();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $page) {
$projects = array();
$project_phids = array_filter(mpull($page, 'getProjectPHID'));
if ($project_phids) {
$projects = id(new PhabricatorProjectQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($project_phids)
->execute();
$projects = mpull($projects, null, 'getPHID');
}
foreach ($page as $key => $column) {
$phid = $column->getProjectPHID();
$project = idx($projects, $phid);
if (!$project) {
$this->didRejectResult($page[$key]);
unset($page[$key]);
continue;
}
$column->attachProject($project);
}
$proxy_phids = array_filter(mpull($page, 'getProjectPHID'));
return $page;
}
protected function didFilterPage(array $page) {
$proxy_phids = array();
foreach ($page as $column) {
$proxy_phid = $column->getProxyPHID();
if ($proxy_phid !== null) {
$proxy_phids[$proxy_phid] = $proxy_phid;
}
}
if ($proxy_phids) {
$proxies = id(new PhabricatorObjectQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($proxy_phids)
->execute();
$proxies = mpull($proxies, null, 'getPHID');
} else {
$proxies = array();
}
foreach ($page as $key => $column) {
$proxy_phid = $column->getProxyPHID();
if ($proxy_phid !== null) {
$proxy = idx($proxies, $proxy_phid);
// Only attach valid proxies, so we don't end up getting surprised if
// an install somehow gets junk into their database.
if (!($proxy instanceof PhabricatorColumnProxyInterface)) {
$proxy = null;
}
if (!$proxy) {
$this->didRejectResult($column);
unset($page[$key]);
continue;
}
} else {
$proxy = null;
}
$column->attachProxy($proxy);
}
if ($this->needTriggers) {
$trigger_phids = array();
foreach ($page as $column) {
if ($column->canHaveTrigger()) {
$trigger_phid = $column->getTriggerPHID();
if ($trigger_phid) {
$trigger_phids[] = $trigger_phid;
}
}
}
if ($trigger_phids) {
$triggers = id(new PhabricatorProjectTriggerQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($trigger_phids)
->execute();
$triggers = mpull($triggers, null, 'getPHID');
} else {
$triggers = array();
}
foreach ($page as $column) {
$trigger = null;
if ($column->canHaveTrigger()) {
$trigger_phid = $column->getTriggerPHID();
if ($trigger_phid) {
$trigger = idx($triggers, $trigger_phid);
}
}
$column->attachTrigger($trigger);
}
}
return $page;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->projectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'projectPHID IN (%Ls)',
$this->projectPHIDs);
}
if ($this->proxyPHIDs !== null) {
$where[] = qsprintf(
$conn,
'proxyPHID IN (%Ls)',
$this->proxyPHIDs);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'status IN (%Ld)',
$this->statuses);
}
if ($this->triggerPHIDs !== null) {
$where[] = qsprintf(
$conn,
'triggerPHID IN (%Ls)',
$this->triggerPHIDs);
}
if ($this->isProxyColumn !== null) {
if ($this->isProxyColumn) {
$where[] = qsprintf($conn, 'proxyPHID IS NOT NULL');
} else {
$where[] = qsprintf($conn, 'proxyPHID IS NULL');
}
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorProjectApplication';
}
}
diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php
index 22e0b59c94..b02fa647a2 100644
--- a/src/applications/project/query/PhabricatorProjectQuery.php
+++ b/src/applications/project/query/PhabricatorProjectQuery.php
@@ -1,901 +1,897 @@
<?php
final class PhabricatorProjectQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $memberPHIDs;
private $watcherPHIDs;
private $slugs;
private $slugNormals;
private $slugMap;
private $allSlugs;
private $names;
private $namePrefixes;
private $nameTokens;
private $icons;
private $colors;
private $ancestorPHIDs;
private $parentPHIDs;
private $isMilestone;
private $hasSubprojects;
private $minDepth;
private $maxDepth;
private $minMilestoneNumber;
private $maxMilestoneNumber;
private $subtypes;
private $status = 'status-any';
const STATUS_ANY = 'status-any';
const STATUS_OPEN = 'status-open';
const STATUS_CLOSED = 'status-closed';
const STATUS_ACTIVE = 'status-active';
const STATUS_ARCHIVED = 'status-archived';
private $statuses;
private $needSlugs;
private $needMembers;
private $needAncestorMembers;
private $needWatchers;
private $needImages;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withStatus($status) {
$this->status = $status;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function withMemberPHIDs(array $member_phids) {
$this->memberPHIDs = $member_phids;
return $this;
}
public function withWatcherPHIDs(array $watcher_phids) {
$this->watcherPHIDs = $watcher_phids;
return $this;
}
public function withSlugs(array $slugs) {
$this->slugs = $slugs;
return $this;
}
public function withNames(array $names) {
$this->names = $names;
return $this;
}
public function withNamePrefixes(array $prefixes) {
$this->namePrefixes = $prefixes;
return $this;
}
public function withNameTokens(array $tokens) {
$this->nameTokens = array_values($tokens);
return $this;
}
public function withIcons(array $icons) {
$this->icons = $icons;
return $this;
}
public function withColors(array $colors) {
$this->colors = $colors;
return $this;
}
public function withParentProjectPHIDs($parent_phids) {
$this->parentPHIDs = $parent_phids;
return $this;
}
public function withAncestorProjectPHIDs($ancestor_phids) {
$this->ancestorPHIDs = $ancestor_phids;
return $this;
}
public function withIsMilestone($is_milestone) {
$this->isMilestone = $is_milestone;
return $this;
}
public function withHasSubprojects($has_subprojects) {
$this->hasSubprojects = $has_subprojects;
return $this;
}
public function withDepthBetween($min, $max) {
$this->minDepth = $min;
$this->maxDepth = $max;
return $this;
}
public function withMilestoneNumberBetween($min, $max) {
$this->minMilestoneNumber = $min;
$this->maxMilestoneNumber = $max;
return $this;
}
public function withSubtypes(array $subtypes) {
$this->subtypes = $subtypes;
return $this;
}
public function needMembers($need_members) {
$this->needMembers = $need_members;
return $this;
}
public function needAncestorMembers($need_ancestor_members) {
$this->needAncestorMembers = $need_ancestor_members;
return $this;
}
public function needWatchers($need_watchers) {
$this->needWatchers = $need_watchers;
return $this;
}
public function needImages($need_images) {
$this->needImages = $need_images;
return $this;
}
public function needSlugs($need_slugs) {
$this->needSlugs = $need_slugs;
return $this;
}
public function newResultObject() {
return new PhabricatorProject();
}
protected function getDefaultOrderVector() {
return array('name');
}
public function getBuiltinOrders() {
return array(
'name' => array(
'vector' => array('name'),
'name' => pht('Name'),
),
) + parent::getBuiltinOrders();
}
public function getOrderableColumns() {
return parent::getOrderableColumns() + array(
'name' => array(
'table' => $this->getPrimaryTableAlias(),
'column' => 'name',
'reverse' => true,
'type' => 'string',
'unique' => true,
),
'milestoneNumber' => array(
'table' => $this->getPrimaryTableAlias(),
'column' => 'milestoneNumber',
'type' => 'int',
),
'status' => array(
'table' => $this->getPrimaryTableAlias(),
'column' => 'status',
'type' => 'int',
),
);
}
protected function newPagingMapFromPartialObject($object) {
return array(
'id' => (int)$object->getID(),
'name' => $object->getName(),
'status' => $object->getStatus(),
);
}
public function getSlugMap() {
if ($this->slugMap === null) {
throw new PhutilInvalidStateException('execute');
}
return $this->slugMap;
}
protected function willExecute() {
$this->slugMap = array();
$this->slugNormals = array();
$this->allSlugs = array();
if ($this->slugs) {
foreach ($this->slugs as $slug) {
if (PhabricatorSlug::isValidProjectSlug($slug)) {
$normal = PhabricatorSlug::normalizeProjectSlug($slug);
$this->slugNormals[$slug] = $normal;
$this->allSlugs[$normal] = $normal;
}
// NOTE: At least for now, we query for the normalized slugs but also
// for the slugs exactly as entered. This allows older projects with
// slugs that are no longer valid to continue to work.
$this->allSlugs[$slug] = $slug;
}
}
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $projects) {
$ancestor_paths = array();
foreach ($projects as $project) {
foreach ($project->getAncestorProjectPaths() as $path) {
$ancestor_paths[$path] = $path;
}
}
if ($ancestor_paths) {
$ancestors = id(new PhabricatorProject())->loadAllWhere(
'projectPath IN (%Ls)',
$ancestor_paths);
} else {
$ancestors = array();
}
$projects = $this->linkProjectGraph($projects, $ancestors);
$viewer_phid = $this->getViewer()->getPHID();
$material_type = PhabricatorProjectMaterializedMemberEdgeType::EDGECONST;
$watcher_type = PhabricatorObjectHasWatcherEdgeType::EDGECONST;
$types = array();
$types[] = $material_type;
if ($this->needWatchers) {
$types[] = $watcher_type;
}
$all_graph = $this->getAllReachableAncestors($projects);
// See T13484. If the graph is damaged (and contains a cycle or an edge
// pointing at a project which has been destroyed), some of the nodes we
// started with may be filtered out by reachability tests. If any of the
// projects we are linking up don't have available ancestors, filter them
// out.
foreach ($projects as $key => $project) {
$project_phid = $project->getPHID();
if (!isset($all_graph[$project_phid])) {
$this->didRejectResult($project);
unset($projects[$key]);
continue;
}
}
if (!$projects) {
return array();
}
// NOTE: Although we may not need much information about ancestors, we
// always need to test if the viewer is a member, because we will return
// ancestor projects to the policy filter via ExtendedPolicy calls. If
// we skip populating membership data on a parent, the policy framework
// will think the user is not a member of the parent project.
$all_sources = array();
foreach ($all_graph as $project) {
// For milestones, we need parent members.
if ($project->isMilestone()) {
$parent_phid = $project->getParentProjectPHID();
$all_sources[$parent_phid] = $parent_phid;
}
$phid = $project->getPHID();
$all_sources[$phid] = $phid;
}
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs($all_sources)
->withEdgeTypes($types);
$need_all_edges =
$this->needMembers ||
$this->needWatchers ||
$this->needAncestorMembers;
// If we only need to know if the viewer is a member, we can restrict
// the query to just their PHID.
$any_edges = true;
if (!$need_all_edges) {
if ($viewer_phid) {
$edge_query->withDestinationPHIDs(array($viewer_phid));
} else {
// If we don't need members or watchers and don't have a viewer PHID
// (viewer is logged-out or omnipotent), they'll never be a member
// so we don't need to issue this query at all.
$any_edges = false;
}
}
if ($any_edges) {
$edge_query->execute();
}
$membership_projects = array();
foreach ($all_graph as $project) {
$project_phid = $project->getPHID();
if ($project->isMilestone()) {
$source_phids = array($project->getParentProjectPHID());
} else {
$source_phids = array($project_phid);
}
if ($any_edges) {
$member_phids = $edge_query->getDestinationPHIDs(
$source_phids,
array($material_type));
} else {
$member_phids = array();
}
if (in_array($viewer_phid, $member_phids)) {
$membership_projects[$project_phid] = $project;
}
if ($this->needMembers || $this->needAncestorMembers) {
$project->attachMemberPHIDs($member_phids);
}
if ($this->needWatchers) {
$watcher_phids = $edge_query->getDestinationPHIDs(
array($project_phid),
array($watcher_type));
$project->attachWatcherPHIDs($watcher_phids);
$project->setIsUserWatcher(
$viewer_phid,
in_array($viewer_phid, $watcher_phids));
}
}
// If we loaded ancestor members, we've already populated membership
// lists above, so we can skip this step.
if (!$this->needAncestorMembers) {
$member_graph = $this->getAllReachableAncestors($membership_projects);
foreach ($all_graph as $phid => $project) {
$is_member = isset($member_graph[$phid]);
$project->setIsUserMember($viewer_phid, $is_member);
}
}
return $projects;
}
protected function didFilterPage(array $projects) {
$viewer = $this->getViewer();
if ($this->needImages) {
$need_images = $projects;
// First, try to load custom profile images for any projects with custom
// images.
$file_phids = array();
foreach ($need_images as $key => $project) {
$image_phid = $project->getProfileImagePHID();
if ($image_phid) {
$file_phids[$key] = $image_phid;
}
}
if ($file_phids) {
$files = id(new PhabricatorFileQuery())
->setParentQuery($this)
->setViewer($viewer)
->withPHIDs($file_phids)
->execute();
$files = mpull($files, null, 'getPHID');
foreach ($file_phids as $key => $image_phid) {
$file = idx($files, $image_phid);
if (!$file) {
continue;
}
$need_images[$key]->attachProfileImageFile($file);
unset($need_images[$key]);
}
}
// For projects with default images, or projects where the custom image
// failed to load, load a builtin image.
if ($need_images) {
$builtin_map = array();
$builtins = array();
foreach ($need_images as $key => $project) {
$icon = $project->getIcon();
$builtin_name = PhabricatorProjectIconSet::getIconImage($icon);
$builtin_name = 'projects/'.$builtin_name;
$builtin = id(new PhabricatorFilesOnDiskBuiltinFile())
->setName($builtin_name);
$builtin_key = $builtin->getBuiltinFileKey();
$builtins[] = $builtin;
$builtin_map[$key] = $builtin_key;
}
$builtin_files = PhabricatorFile::loadBuiltins(
$viewer,
$builtins);
foreach ($need_images as $key => $project) {
$builtin_key = $builtin_map[$key];
$builtin_file = $builtin_files[$builtin_key];
$project->attachProfileImageFile($builtin_file);
}
}
}
$this->loadSlugs($projects);
return $projects;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->status != self::STATUS_ANY) {
switch ($this->status) {
case self::STATUS_OPEN:
case self::STATUS_ACTIVE:
$filter = array(
PhabricatorProjectStatus::STATUS_ACTIVE,
);
break;
case self::STATUS_CLOSED:
case self::STATUS_ARCHIVED:
$filter = array(
PhabricatorProjectStatus::STATUS_ARCHIVED,
);
break;
default:
throw new Exception(
pht(
"Unknown project status '%s'!",
$this->status));
}
$where[] = qsprintf(
$conn,
'project.status IN (%Ld)',
$filter);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'project.status IN (%Ls)',
$this->statuses);
}
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'project.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'project.phid IN (%Ls)',
$this->phids);
}
if ($this->memberPHIDs !== null) {
$where[] = qsprintf(
$conn,
'e.dst IN (%Ls)',
$this->memberPHIDs);
}
if ($this->watcherPHIDs !== null) {
$where[] = qsprintf(
$conn,
'w.dst IN (%Ls)',
$this->watcherPHIDs);
}
if ($this->slugs !== null) {
$where[] = qsprintf(
$conn,
'slug.slug IN (%Ls)',
$this->allSlugs);
}
if ($this->names !== null) {
$where[] = qsprintf(
$conn,
'project.name IN (%Ls)',
$this->names);
}
if ($this->namePrefixes) {
$parts = array();
foreach ($this->namePrefixes as $name_prefix) {
$parts[] = qsprintf(
$conn,
'project.name LIKE %>',
$name_prefix);
}
$where[] = qsprintf($conn, '%LO', $parts);
}
if ($this->icons !== null) {
$where[] = qsprintf(
$conn,
'project.icon IN (%Ls)',
$this->icons);
}
if ($this->colors !== null) {
$where[] = qsprintf(
$conn,
'project.color IN (%Ls)',
$this->colors);
}
if ($this->parentPHIDs !== null) {
$where[] = qsprintf(
$conn,
'project.parentProjectPHID IN (%Ls)',
$this->parentPHIDs);
}
if ($this->ancestorPHIDs !== null) {
$ancestor_paths = queryfx_all(
$conn,
'SELECT projectPath, projectDepth FROM %T WHERE phid IN (%Ls)',
id(new PhabricatorProject())->getTableName(),
$this->ancestorPHIDs);
if (!$ancestor_paths) {
throw new PhabricatorEmptyQueryException();
}
$sql = array();
foreach ($ancestor_paths as $ancestor_path) {
$sql[] = qsprintf(
$conn,
'(project.projectPath LIKE %> AND project.projectDepth > %d)',
$ancestor_path['projectPath'],
$ancestor_path['projectDepth']);
}
$where[] = qsprintf($conn, '%LO', $sql);
$where[] = qsprintf(
$conn,
'project.parentProjectPHID IS NOT NULL');
}
if ($this->isMilestone !== null) {
if ($this->isMilestone) {
$where[] = qsprintf(
$conn,
'project.milestoneNumber IS NOT NULL');
} else {
$where[] = qsprintf(
$conn,
'project.milestoneNumber IS NULL');
}
}
if ($this->hasSubprojects !== null) {
$where[] = qsprintf(
$conn,
'project.hasSubprojects = %d',
(int)$this->hasSubprojects);
}
if ($this->minDepth !== null) {
$where[] = qsprintf(
$conn,
'project.projectDepth >= %d',
$this->minDepth);
}
if ($this->maxDepth !== null) {
$where[] = qsprintf(
$conn,
'project.projectDepth <= %d',
$this->maxDepth);
}
if ($this->minMilestoneNumber !== null) {
$where[] = qsprintf(
$conn,
'project.milestoneNumber >= %d',
$this->minMilestoneNumber);
}
if ($this->maxMilestoneNumber !== null) {
$where[] = qsprintf(
$conn,
'project.milestoneNumber <= %d',
$this->maxMilestoneNumber);
}
if ($this->subtypes !== null) {
$where[] = qsprintf(
$conn,
'project.subtype IN (%Ls)',
$this->subtypes);
}
return $where;
}
protected function shouldGroupQueryResultRows() {
if ($this->memberPHIDs || $this->watcherPHIDs || $this->nameTokens) {
return true;
}
if ($this->slugs) {
return true;
}
return parent::shouldGroupQueryResultRows();
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->memberPHIDs !== null) {
$joins[] = qsprintf(
$conn,
'JOIN %T e ON e.src = project.phid AND e.type = %d',
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
PhabricatorProjectMaterializedMemberEdgeType::EDGECONST);
}
if ($this->watcherPHIDs !== null) {
$joins[] = qsprintf(
$conn,
'JOIN %T w ON w.src = project.phid AND w.type = %d',
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
PhabricatorObjectHasWatcherEdgeType::EDGECONST);
}
if ($this->slugs !== null) {
$joins[] = qsprintf(
$conn,
'JOIN %T slug on slug.projectPHID = project.phid',
id(new PhabricatorProjectSlug())->getTableName());
}
if ($this->nameTokens !== null) {
$name_tokens = $this->getNameTokensForQuery($this->nameTokens);
foreach ($name_tokens as $key => $token) {
$token_table = 'token_'.$key;
$joins[] = qsprintf(
$conn,
'JOIN %T %T ON %T.projectID = project.id AND %T.token LIKE %>',
PhabricatorProject::TABLE_DATASOURCE_TOKEN,
$token_table,
$token_table,
$token_table,
$token);
}
}
return $joins;
}
public function getQueryApplicationClass() {
return 'PhabricatorProjectApplication';
}
protected function getPrimaryTableAlias() {
return 'project';
}
private function linkProjectGraph(array $projects, array $ancestors) {
$ancestor_map = mpull($ancestors, null, 'getPHID');
$projects_map = mpull($projects, null, 'getPHID');
$all_map = $projects_map + $ancestor_map;
$done = array();
foreach ($projects as $key => $project) {
$seen = array($project->getPHID() => true);
if (!$this->linkProject($project, $all_map, $done, $seen)) {
$this->didRejectResult($project);
unset($projects[$key]);
continue;
}
foreach ($project->getAncestorProjects() as $ancestor) {
$seen[$ancestor->getPHID()] = true;
}
}
return $projects;
}
private function linkProject($project, array $all, array $done, array $seen) {
$parent_phid = $project->getParentProjectPHID();
// This project has no parent, so just attach `null` and return.
if (!$parent_phid) {
$project->attachParentProject(null);
return true;
}
// This project has a parent, but it failed to load.
if (empty($all[$parent_phid])) {
return false;
}
// Test for graph cycles. If we encounter one, we're going to hide the
// entire cycle since we can't meaningfully resolve it.
if (isset($seen[$parent_phid])) {
return false;
}
$seen[$parent_phid] = true;
$parent = $all[$parent_phid];
$project->attachParentProject($parent);
if (!empty($done[$parent_phid])) {
return true;
}
return $this->linkProject($parent, $all, $done, $seen);
}
private function getAllReachableAncestors(array $projects) {
$ancestors = array();
$seen = mpull($projects, null, 'getPHID');
$stack = $projects;
while ($stack) {
$project = array_pop($stack);
$phid = $project->getPHID();
$ancestors[$phid] = $project;
$parent_phid = $project->getParentProjectPHID();
if (!$parent_phid) {
continue;
}
if (isset($seen[$parent_phid])) {
continue;
}
$seen[$parent_phid] = true;
$stack[] = $project->getParentProject();
}
return $ancestors;
}
private function loadSlugs(array $projects) {
// Build a map from primary slugs to projects.
$primary_map = array();
foreach ($projects as $project) {
$primary_slug = $project->getPrimarySlug();
if ($primary_slug === null) {
continue;
}
$primary_map[$primary_slug] = $project;
}
// Link up all of the queried slugs which correspond to primary
// slugs. If we can link up everything from this (no slugs were queried,
// or only primary slugs were queried) we don't need to load anything
// else.
$unknown = $this->slugNormals;
foreach ($unknown as $input => $normal) {
if (isset($primary_map[$input])) {
$match = $input;
} else if (isset($primary_map[$normal])) {
$match = $normal;
} else {
continue;
}
$this->slugMap[$input] = array(
'slug' => $match,
'projectPHID' => $primary_map[$match]->getPHID(),
);
unset($unknown[$input]);
}
// If we need slugs, we have to load everything.
// If we still have some queried slugs which we haven't mapped, we only
// need to look for them.
// If we've mapped everything, we don't have to do any work.
$project_phids = mpull($projects, 'getPHID');
if ($this->needSlugs) {
$slugs = id(new PhabricatorProjectSlug())->loadAllWhere(
'projectPHID IN (%Ls)',
$project_phids);
} else if ($unknown) {
$slugs = id(new PhabricatorProjectSlug())->loadAllWhere(
'projectPHID IN (%Ls) AND slug IN (%Ls)',
$project_phids,
$unknown);
} else {
$slugs = array();
}
// Link up any slugs we were not able to link up earlier.
$extra_map = mpull($slugs, 'getProjectPHID', 'getSlug');
foreach ($unknown as $input => $normal) {
if (isset($extra_map[$input])) {
$match = $input;
} else if (isset($extra_map[$normal])) {
$match = $normal;
} else {
continue;
}
$this->slugMap[$input] = array(
'slug' => $match,
'projectPHID' => $extra_map[$match],
);
unset($unknown[$input]);
}
if ($this->needSlugs) {
$slug_groups = mgroup($slugs, 'getProjectPHID');
foreach ($projects as $project) {
$project_slugs = idx($slug_groups, $project->getPHID(), array());
$project->attachSlugs($project_slugs);
}
}
}
private function getNameTokensForQuery(array $tokens) {
// When querying for projects by name, only actually search for the five
// longest tokens. MySQL can get grumpy with a large number of JOINs
// with LIKEs and queries for more than 5 tokens are essentially never
// legitimate searches for projects, but users copy/pasting nonsense.
// See also PHI47.
$length_map = array();
foreach ($tokens as $token) {
$length_map[$token] = strlen($token);
}
arsort($length_map);
$length_map = array_slice($length_map, 0, 5, true);
return array_keys($length_map);
}
}
diff --git a/src/applications/project/query/PhabricatorProjectTriggerQuery.php b/src/applications/project/query/PhabricatorProjectTriggerQuery.php
index 452e3e53f1..306fcb50fe 100644
--- a/src/applications/project/query/PhabricatorProjectTriggerQuery.php
+++ b/src/applications/project/query/PhabricatorProjectTriggerQuery.php
@@ -1,135 +1,131 @@
<?php
final class PhabricatorProjectTriggerQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $activeColumnMin;
private $activeColumnMax;
private $needUsage;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function needUsage($need_usage) {
$this->needUsage = $need_usage;
return $this;
}
public function withActiveColumnCountBetween($min, $max) {
$this->activeColumnMin = $min;
$this->activeColumnMax = $max;
return $this;
}
public function newResultObject() {
return new PhabricatorProjectTrigger();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'trigger.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'trigger.phid IN (%Ls)',
$this->phids);
}
if ($this->activeColumnMin !== null) {
$where[] = qsprintf(
$conn,
'trigger_usage.activeColumnCount >= %d',
$this->activeColumnMin);
}
if ($this->activeColumnMax !== null) {
$where[] = qsprintf(
$conn,
'trigger_usage.activeColumnCount <= %d',
$this->activeColumnMax);
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->shouldJoinUsageTable()) {
$joins[] = qsprintf(
$conn,
'JOIN %R trigger_usage ON trigger.phid = trigger_usage.triggerPHID',
new PhabricatorProjectTriggerUsage());
}
return $joins;
}
private function shouldJoinUsageTable() {
if ($this->activeColumnMin !== null) {
return true;
}
if ($this->activeColumnMax !== null) {
return true;
}
return false;
}
protected function didFilterPage(array $triggers) {
if ($this->needUsage) {
$usage_map = id(new PhabricatorProjectTriggerUsage())->loadAllWhere(
'triggerPHID IN (%Ls)',
mpull($triggers, 'getPHID'));
$usage_map = mpull($usage_map, null, 'getTriggerPHID');
foreach ($triggers as $trigger) {
$trigger_phid = $trigger->getPHID();
$usage = idx($usage_map, $trigger_phid);
if (!$usage) {
$usage = id(new PhabricatorProjectTriggerUsage())
->setTriggerPHID($trigger_phid)
->setExamplePHID(null)
->setColumnCount(0)
->setActiveColumnCount(0);
}
$trigger->attachUsage($usage);
}
}
return $triggers;
}
public function getQueryApplicationClass() {
return 'PhabricatorProjectApplication';
}
protected function getPrimaryTableAlias() {
return 'trigger';
}
}
diff --git a/src/applications/project/xaction/PhabricatorProjectImageTransaction.php b/src/applications/project/xaction/PhabricatorProjectImageTransaction.php
index f6d10f0961..9ed506d299 100644
--- a/src/applications/project/xaction/PhabricatorProjectImageTransaction.php
+++ b/src/applications/project/xaction/PhabricatorProjectImageTransaction.php
@@ -1,136 +1,108 @@
<?php
final class PhabricatorProjectImageTransaction
extends PhabricatorProjectTransactionType {
const TRANSACTIONTYPE = 'project:image';
public function generateOldValue($object) {
return $object->getProfileImagePHID();
}
public function applyInternalEffects($object, $value) {
$object->setProfileImagePHID($value);
}
- public function applyExternalEffects($object, $value) {
- $old = $this->getOldValue();
- $new = $value;
- $all = array();
- if ($old) {
- $all[] = $old;
- }
- if ($new) {
- $all[] = $new;
- }
-
- $files = id(new PhabricatorFileQuery())
- ->setViewer($this->getActor())
- ->withPHIDs($all)
- ->execute();
- $files = mpull($files, null, 'getPHID');
-
- $old_file = idx($files, $old);
- if ($old_file) {
- $old_file->detachFromObject($object->getPHID());
- }
-
- $new_file = idx($files, $new);
- if ($new_file) {
- $new_file->attachToObject($object->getPHID());
- }
- }
-
public function getTitle() {
$old = $this->getOldValue();
$new = $this->getNewValue();
// TODO: Some day, it would be nice to show the images.
if (!$old) {
return pht(
"%s set this project's image to %s.",
$this->renderAuthor(),
$this->renderNewHandle());
} else if (!$new) {
return pht(
"%s removed this project's image.",
$this->renderAuthor());
} else {
return pht(
"%s updated this project's image from %s to %s.",
$this->renderAuthor(),
$this->renderOldHandle(),
$this->renderNewHandle());
}
}
public function getTitleForFeed() {
$old = $this->getOldValue();
$new = $this->getNewValue();
// TODO: Some day, it would be nice to show the images.
if (!$old) {
return pht(
'%s set the image for %s to %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderNewHandle());
} else if (!$new) {
return pht(
'%s removed the image for %s.',
$this->renderAuthor(),
$this->renderObject());
} else {
return pht(
'%s updated the image for %s from %s to %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderOldHandle(),
$this->renderNewHandle());
}
}
public function getIcon() {
return 'fa-photo';
}
public function extractFilePHIDs($object, $value) {
if ($value) {
return array($value);
}
return array();
}
public function validateTransactions($object, array $xactions) {
$errors = array();
$viewer = $this->getActor();
foreach ($xactions as $xaction) {
$file_phid = $xaction->getNewValue();
// Only validate if file was uploaded
if ($file_phid) {
$file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($file_phid))
->executeOne();
if (!$file) {
$errors[] = $this->newInvalidError(
pht('"%s" is not a valid file PHID.',
$file_phid));
} else {
if (!$file->isViewableImage()) {
$mime_type = $file->getMimeType();
$errors[] = $this->newInvalidError(
pht('File mime type of "%s" is not a valid viewable image.',
$mime_type));
}
}
}
}
return $errors;
}
}
diff --git a/src/applications/releeph/application/PhabricatorReleephApplication.php b/src/applications/releeph/application/PhabricatorReleephApplication.php
deleted file mode 100644
index 2590478f1a..0000000000
--- a/src/applications/releeph/application/PhabricatorReleephApplication.php
+++ /dev/null
@@ -1,92 +0,0 @@
-<?php
-
-final class PhabricatorReleephApplication extends PhabricatorApplication {
-
- public function getName() {
- return pht('Releeph');
- }
-
- public function getShortDescription() {
- return pht('Pull Requests');
- }
-
- public function getBaseURI() {
- return '/releeph/';
- }
-
- public function getIcon() {
- return 'fa-flag-checkered';
- }
-
- public function getApplicationGroup() {
- return self::GROUP_UTILITIES;
- }
-
- public function isPrototype() {
- return true;
- }
-
- public function getRoutes() {
- return array(
- '/Y(?P<requestID>[1-9]\d*)' => 'ReleephRequestViewController',
-
- // TODO: Remove these older routes eventually.
- '/RQ(?P<requestID>[1-9]\d*)' => 'ReleephRequestViewController',
- '/releeph/request/(?P<requestID>[1-9]\d*)/'
- => 'ReleephRequestViewController',
-
- '/releeph/' => array(
- '' => 'ReleephProductListController',
- '(?:product|project)/' => array(
- '(?:query/(?P<queryKey>[^/]+)/)?' => 'ReleephProductListController',
- 'create/' => 'ReleephProductCreateController',
- '(?P<projectID>[1-9]\d*)/' => array(
- '(?:query/(?P<queryKey>[^/]+)/)?' => 'ReleephProductViewController',
- 'edit/' => 'ReleephProductEditController',
- 'cutbranch/' => 'ReleephBranchCreateController',
- 'action/(?P<action>.+)/' => 'ReleephProductActionController',
- 'history/' => 'ReleephProductHistoryController',
- ),
- ),
-
- 'branch/' => array(
- 'edit/(?P<branchID>[1-9]\d*)/'
- => 'ReleephBranchEditController',
- '(?P<action>close|re-open)/(?P<branchID>[1-9]\d*)/'
- => 'ReleephBranchAccessController',
- 'preview/' => 'ReleephBranchNamePreviewController',
- '(?P<branchID>[1-9]\d*)/' => array(
- 'history/' => 'ReleephBranchHistoryController',
- '(?:query/(?P<queryKey>[^/]+)/)?' => 'ReleephBranchViewController',
- ),
- 'pull/(?P<branchID>[1-9]\d*)/'
- => 'ReleephRequestEditController',
- ),
-
- 'request/' => array(
- 'create/' => 'ReleephRequestEditController',
- 'differentialcreate/' => array(
- 'D(?P<diffRevID>[1-9]\d*)' =>
- 'ReleephRequestDifferentialCreateController',
- ),
- 'edit/(?P<requestID>[1-9]\d*)/'
- => 'ReleephRequestEditController',
- 'action/(?P<action>.+)/(?P<requestID>[1-9]\d*)/'
- => 'ReleephRequestActionController',
- 'typeahead/' =>
- 'ReleephRequestTypeaheadController',
- 'comment/(?P<requestID>[1-9]\d*)/'
- => 'ReleephRequestCommentController',
- ),
- ),
- );
- }
-
- public function getMailCommandObjects() {
- // TODO: Pull requests don't implement any interfaces which give them
- // meaningful commands, so don't expose ReleephRequest here for now.
- // Once we add relevant commands, return it here.
- return array();
- }
-
-}
diff --git a/src/applications/releeph/commitfinder/ReleephCommitFinder.php b/src/applications/releeph/commitfinder/ReleephCommitFinder.php
deleted file mode 100644
index 8c1712c802..0000000000
--- a/src/applications/releeph/commitfinder/ReleephCommitFinder.php
+++ /dev/null
@@ -1,120 +0,0 @@
-<?php
-
-final class ReleephCommitFinder extends Phobject {
-
- private $releephProject;
- private $user;
- private $objectPHID;
-
- public function setUser(PhabricatorUser $user) {
- $this->user = $user;
- return $this;
- }
- public function getUser() {
- return $this->user;
- }
-
- public function setReleephProject(ReleephProject $rp) {
- $this->releephProject = $rp;
- return $this;
- }
-
- public function getRequestedObjectPHID() {
- return $this->objectPHID;
- }
-
- public function fromPartial($partial_string) {
- $this->objectPHID = null;
-
- // Look for diffs
- $matches = array();
- if (preg_match('/^D([1-9]\d*)$/', $partial_string, $matches)) {
- $diff_id = $matches[1];
- $diff_rev = id(new DifferentialRevisionQuery())
- ->setViewer($this->getUser())
- ->withIDs(array($diff_id))
- ->needCommitPHIDs(true)
- ->executeOne();
- if (!$diff_rev) {
- throw new ReleephCommitFinderException(
- pht(
- '%s does not refer to an existing diff.',
- $partial_string));
- }
- $commit_phids = $diff_rev->getCommitPHIDs();
-
- if (!$commit_phids) {
- throw new ReleephCommitFinderException(
- pht(
- '%s has no commits associated with it yet.',
- $partial_string));
- }
-
- $this->objectPHID = $diff_rev->getPHID();
-
- $commits = id(new DiffusionCommitQuery())
- ->setViewer($this->getUser())
- ->withPHIDs($commit_phids)
- ->execute();
- $commits = msort($commits, 'getEpoch');
- return head($commits);
- }
-
- // Look for a raw commit number, or r<callsign><commit-number>.
- $repository = $this->releephProject->getRepository();
- $dr_data = null;
- $matches = array();
- if (preg_match('/^r(?P<callsign>[A-Z]+)(?P<commit>\w+)$/',
- $partial_string, $matches)) {
- $callsign = $matches['callsign'];
- if ($callsign != $repository->getCallsign()) {
- throw new ReleephCommitFinderException(
- pht(
- '%s is in a different repository to this Releeph project (%s).',
- $partial_string,
- $repository->getCallsign()));
- } else {
- $dr_data = $matches;
- }
- } else {
- $dr_data = array(
- 'callsign' => $repository->getCallsign(),
- 'commit' => $partial_string,
- );
- }
-
- try {
- $dr_data['user'] = $this->getUser();
- $dr = DiffusionRequest::newFromDictionary($dr_data);
- } catch (Exception $ex) {
- $message = pht(
- 'No commit matches %s: %s',
- $partial_string,
- $ex->getMessage());
- throw new ReleephCommitFinderException($message);
- }
-
- $phabricator_repository_commit = $dr->loadCommit();
-
- if (!$phabricator_repository_commit) {
- throw new ReleephCommitFinderException(
- pht(
- "The commit %s doesn't exist in this repository.",
- $partial_string));
- }
-
- // When requesting a single commit, if it has an associated review we
- // imply the review was requested instead. This is always correct for now
- // and consistent with the older behavior, although it might not be the
- // right rule in the future.
- $phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
- $phabricator_repository_commit->getPHID(),
- DiffusionCommitHasRevisionEdgeType::EDGECONST);
- if ($phids) {
- $this->objectPHID = head($phids);
- }
-
- return $phabricator_repository_commit;
- }
-
-}
diff --git a/src/applications/releeph/commitfinder/ReleephCommitFinderException.php b/src/applications/releeph/commitfinder/ReleephCommitFinderException.php
deleted file mode 100644
index 8250de5927..0000000000
--- a/src/applications/releeph/commitfinder/ReleephCommitFinderException.php
+++ /dev/null
@@ -1,3 +0,0 @@
-<?php
-
-final class ReleephCommitFinderException extends Exception {}
diff --git a/src/applications/releeph/conduit/ReleephConduitAPIMethod.php b/src/applications/releeph/conduit/ReleephConduitAPIMethod.php
deleted file mode 100644
index 87c3d641ff..0000000000
--- a/src/applications/releeph/conduit/ReleephConduitAPIMethod.php
+++ /dev/null
@@ -1,17 +0,0 @@
-<?php
-
-abstract class ReleephConduitAPIMethod extends ConduitAPIMethod {
-
- public function getMethodStatus() {
- return self::METHOD_STATUS_UNSTABLE;
- }
-
- public function getMethodStatusDescription() {
- return pht('All Releeph methods are subject to abrupt change.');
- }
-
- final public function getApplication() {
- return PhabricatorApplication::getByClass('PhabricatorReleephApplication');
- }
-
-}
diff --git a/src/applications/releeph/conduit/ReleephGetBranchesConduitAPIMethod.php b/src/applications/releeph/conduit/ReleephGetBranchesConduitAPIMethod.php
deleted file mode 100644
index 5516b4edb6..0000000000
--- a/src/applications/releeph/conduit/ReleephGetBranchesConduitAPIMethod.php
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-
-final class ReleephGetBranchesConduitAPIMethod extends ReleephConduitAPIMethod {
-
- public function getAPIMethodName() {
- return 'releeph.getbranches';
- }
-
- public function getMethodDescription() {
- return pht('Return information about all active Releeph branches.');
- }
-
- protected function defineParamTypes() {
- return array(
- );
- }
-
- protected function defineReturnType() {
- return 'nonempty list<dict<string, wild>>';
- }
-
- protected function execute(ConduitAPIRequest $request) {
- $results = array();
-
- $projects = id(new ReleephProductQuery())
- ->setViewer($request->getUser())
- ->withActive(1)
- ->execute();
-
- foreach ($projects as $project) {
- $repository = $project->getRepository();
-
- $branches = id(new ReleephBranch())->loadAllWhere(
- 'releephProjectID = %d AND isActive = 1',
- $project->getID());
-
- foreach ($branches as $branch) {
- $full_branch_name = $branch->getName();
-
- $cut_point_commit = id(new PhabricatorRepositoryCommit())->loadOneWhere(
- 'phid = %s',
- $branch->getCutPointCommitPHID());
-
- $results[] = array(
- 'project' => $project->getName(),
- 'repository' => $repository->getCallsign(),
- 'branch' => $branch->getBasename(),
- 'fullBranchName' => $full_branch_name,
- 'symbolicName' => $branch->getSymbolicName(),
- 'cutPoint' => $cut_point_commit->getCommitIdentifier(),
- );
- }
- }
-
- return $results;
- }
-
-}
diff --git a/src/applications/releeph/conduit/ReleephQueryBranchesConduitAPIMethod.php b/src/applications/releeph/conduit/ReleephQueryBranchesConduitAPIMethod.php
deleted file mode 100644
index 400bbee604..0000000000
--- a/src/applications/releeph/conduit/ReleephQueryBranchesConduitAPIMethod.php
+++ /dev/null
@@ -1,73 +0,0 @@
-<?php
-
-final class ReleephQueryBranchesConduitAPIMethod
- extends ReleephConduitAPIMethod {
-
- public function getAPIMethodName() {
- return 'releeph.querybranches';
- }
-
- public function getMethodDescription() {
- return pht('Query information about Releeph branches.');
- }
-
- protected function defineParamTypes() {
- return array(
- 'ids' => 'optional list<id>',
- 'phids' => 'optional list<phid>',
- 'productPHIDs' => 'optional list<phid>',
- ) + $this->getPagerParamTypes();
- }
-
- protected function defineReturnType() {
- return 'query-results';
- }
-
- protected function execute(ConduitAPIRequest $request) {
- $viewer = $request->getUser();
-
- $query = id(new ReleephBranchQuery())
- ->setViewer($viewer);
-
- $ids = $request->getValue('ids');
- if ($ids !== null) {
- $query->withIDs($ids);
- }
-
- $phids = $request->getValue('phids');
- if ($phids !== null) {
- $query->withPHIDs($phids);
- }
-
- $product_phids = $request->getValue('productPHIDs');
- if ($product_phids !== null) {
- $query->withProductPHIDs($product_phids);
- }
-
- $pager = $this->newPager($request);
- $branches = $query->executeWithCursorPager($pager);
-
- $data = array();
- foreach ($branches as $branch) {
- $id = $branch->getID();
-
- $uri = '/releeph/branch/'.$id.'/';
- $uri = PhabricatorEnv::getProductionURI($uri);
-
- $data[] = array(
- 'id' => $id,
- 'phid' => $branch->getPHID(),
- 'uri' => $uri,
- 'name' => $branch->getName(),
- 'productPHID' => $branch->getProduct()->getPHID(),
- );
- }
-
- return $this->addPagerResults(
- array(
- 'data' => $data,
- ),
- $pager);
- }
-
-}
diff --git a/src/applications/releeph/conduit/ReleephQueryProductsConduitAPIMethod.php b/src/applications/releeph/conduit/ReleephQueryProductsConduitAPIMethod.php
deleted file mode 100644
index fe4d1d1df9..0000000000
--- a/src/applications/releeph/conduit/ReleephQueryProductsConduitAPIMethod.php
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-
-final class ReleephQueryProductsConduitAPIMethod
- extends ReleephConduitAPIMethod {
-
- public function getAPIMethodName() {
- return 'releeph.queryproducts';
- }
-
- public function getMethodDescription() {
- return pht('Query information about Releeph products.');
- }
-
- protected function defineParamTypes() {
- return array(
- 'ids' => 'optional list<id>',
- 'phids' => 'optional list<phid>',
- 'repositoryPHIDs' => 'optional list<phid>',
- 'isActive' => 'optional bool',
- ) + $this->getPagerParamTypes();
- }
-
- protected function defineReturnType() {
- return 'query-results';
- }
-
- protected function execute(ConduitAPIRequest $request) {
- $viewer = $request->getUser();
-
- $query = id(new ReleephProductQuery())
- ->setViewer($viewer);
-
- $ids = $request->getValue('ids');
- if ($ids !== null) {
- $query->withIDs($ids);
- }
-
- $phids = $request->getValue('phids');
- if ($phids !== null) {
- $query->withPHIDs($phids);
- }
-
- $repository_phids = $request->getValue('repositoryPHIDs');
- if ($repository_phids !== null) {
- $query->withRepositoryPHIDs($repository_phids);
- }
-
- $is_active = $request->getValue('isActive');
- if ($is_active !== null) {
- $query->withActive($is_active);
- }
-
- $pager = $this->newPager($request);
- $products = $query->executeWithCursorPager($pager);
-
- $data = array();
- foreach ($products as $product) {
- $id = $product->getID();
-
- $uri = '/releeph/product/'.$id.'/';
- $uri = PhabricatorEnv::getProductionURI($uri);
-
- $data[] = array(
- 'id' => $id,
- 'phid' => $product->getPHID(),
- 'uri' => $uri,
- 'name' => $product->getName(),
- 'isActive' => (bool)$product->getIsActive(),
- 'repositoryPHID' => $product->getRepositoryPHID(),
- );
- }
-
- return $this->addPagerResults(
- array(
- 'data' => $data,
- ),
- $pager);
- }
-
-}
diff --git a/src/applications/releeph/conduit/ReleephQueryRequestsConduitAPIMethod.php b/src/applications/releeph/conduit/ReleephQueryRequestsConduitAPIMethod.php
deleted file mode 100644
index 5cd5032c2a..0000000000
--- a/src/applications/releeph/conduit/ReleephQueryRequestsConduitAPIMethod.php
+++ /dev/null
@@ -1,77 +0,0 @@
-<?php
-
-final class ReleephQueryRequestsConduitAPIMethod
- extends ReleephConduitAPIMethod {
-
- public function getAPIMethodName() {
- return 'releeph.queryrequests';
- }
-
- public function getMethodDescription() {
- return pht(
- 'Return information about all Releeph requests linked to the given ids.');
- }
-
- protected function defineParamTypes() {
- return array(
- 'revisionPHIDs' => 'optional list<phid>',
- 'requestedCommitPHIDs' => 'optional list<phid>',
- );
- }
-
- protected function defineReturnType() {
- return 'dict<string, wild>';
- }
-
- protected function execute(ConduitAPIRequest $conduit_request) {
- $revision_phids = $conduit_request->getValue('revisionPHIDs');
- $requested_commit_phids =
- $conduit_request->getValue('requestedCommitPHIDs');
- $result = array();
-
- if (!$revision_phids && !$requested_commit_phids) {
- return $result;
- }
-
- $query = new ReleephRequestQuery();
- $query->setViewer($conduit_request->getUser());
-
- if ($revision_phids) {
- $query->withRequestedObjectPHIDs($revision_phids);
- } else if ($requested_commit_phids) {
- $query->withRequestedCommitPHIDs($requested_commit_phids);
- }
-
- $releeph_requests = $query->execute();
-
- foreach ($releeph_requests as $releeph_request) {
- $branch = $releeph_request->getBranch();
-
- $request_commit_phid = $releeph_request->getRequestCommitPHID();
-
- $object = $releeph_request->getRequestedObject();
- if ($object instanceof DifferentialRevision) {
- $object_phid = $object->getPHID();
- } else {
- $object_phid = null;
- }
-
- $status = $releeph_request->getStatus();
- $status_name = ReleephRequestStatus::getStatusDescriptionFor($status);
- $url = PhabricatorEnv::getProductionURI('/RQ'.$releeph_request->getID());
-
- $result[] = array(
- 'branchBasename' => $branch->getBasename(),
- 'branchSymbolic' => $branch->getSymbolicName(),
- 'requestID' => $releeph_request->getID(),
- 'revisionPHID' => $object_phid,
- 'status' => $status,
- 'status_name' => $status_name,
- 'url' => $url,
- );
- }
-
- return $result;
- }
-
-}
diff --git a/src/applications/releeph/conduit/ReleephRequestConduitAPIMethod.php b/src/applications/releeph/conduit/ReleephRequestConduitAPIMethod.php
deleted file mode 100644
index 0ac3caf15e..0000000000
--- a/src/applications/releeph/conduit/ReleephRequestConduitAPIMethod.php
+++ /dev/null
@@ -1,169 +0,0 @@
-<?php
-
-final class ReleephRequestConduitAPIMethod extends ReleephConduitAPIMethod {
-
- public function getAPIMethodName() {
- return 'releeph.request';
- }
-
- public function getMethodDescription() {
- return pht('Request a commit or diff to be picked to a branch.');
- }
-
- protected function defineParamTypes() {
- return array(
- 'branchPHID' => 'required string',
- 'things' => 'required list<string>',
- 'fields' => 'dict<string, string>',
- );
- }
-
- protected function defineReturnType() {
- return 'dict<string, wild>';
- }
-
- protected function defineErrorTypes() {
- return array(
- 'ERR_BRANCH' => pht('Unknown Releeph branch.'),
- 'ERR_FIELD_PARSE' => pht('Unable to parse a Releeph field.'),
- );
- }
-
- protected function execute(ConduitAPIRequest $request) {
- $user = $request->getUser();
-
- $viewer_handle = id(new PhabricatorHandleQuery())
- ->setViewer($user)
- ->withPHIDs(array($user->getPHID()))
- ->executeOne();
-
- $branch_phid = $request->getValue('branchPHID');
- $releeph_branch = id(new ReleephBranchQuery())
- ->setViewer($user)
- ->withPHIDs(array($branch_phid))
- ->executeOne();
-
- if (!$releeph_branch) {
- throw id(new ConduitException('ERR_BRANCH'))->setErrorDescription(
- pht(
- 'No %s found with PHID %s!',
- 'ReleephBranch',
- $branch_phid));
- }
-
- $releeph_project = $releeph_branch->getProduct();
-
- // Find the requested commit identifiers
- $requested_commits = array();
- $requested_object_phids = array();
- $things = $request->getValue('things');
- $finder = id(new ReleephCommitFinder())
- ->setUser($user)
- ->setReleephProject($releeph_project);
- foreach ($things as $thing) {
- try {
- $requested_commits[$thing] = $finder->fromPartial($thing);
- $object_phid = $finder->getRequestedObjectPHID();
- if (!$object_phid) {
- $object_phid = $requested_commits[$thing]->getPHID();
- }
- $requested_object_phids[$thing] = $object_phid;
- } catch (ReleephCommitFinderException $ex) {
- throw id(new ConduitException('ERR_NO_MATCHES'))
- ->setErrorDescription($ex->getMessage());
- }
- }
- $requested_commit_phids = mpull($requested_commits, 'getPHID');
-
- // Find any existing requests that clash on the commit id, for this branch
- $existing_releeph_requests = id(new ReleephRequest())->loadAllWhere(
- 'requestCommitPHID IN (%Ls) AND branchID = %d',
- $requested_commit_phids,
- $releeph_branch->getID());
- $existing_releeph_requests = mpull(
- $existing_releeph_requests,
- null,
- 'getRequestCommitPHID');
-
- $selector = $releeph_project->getReleephFieldSelector();
- $fields = $selector->getFieldSpecifications();
- foreach ($fields as $field) {
- $field
- ->setReleephProject($releeph_project)
- ->setReleephBranch($releeph_branch);
- }
-
- $results = array();
- $handles = id(new PhabricatorHandleQuery())
- ->setViewer($user)
- ->withPHIDs($requested_commit_phids)
- ->execute();
- foreach ($requested_commits as $thing => $commit) {
- $phid = $commit->getPHID();
- $name = id($handles[$phid])->getName();
-
- $releeph_request = null;
-
- $existing_releeph_request = idx($existing_releeph_requests, $phid);
- if ($existing_releeph_request) {
- $releeph_request = $existing_releeph_request;
- } else {
- $releeph_request = id(new ReleephRequest())
- ->setRequestUserPHID($user->getPHID())
- ->setBranchID($releeph_branch->getID())
- ->setInBranch(0)
- ->setRequestedObjectPHID($requested_object_phids[$thing]);
-
- $xactions = array();
-
- $xactions[] = id(new ReleephRequestTransaction())
- ->setTransactionType(ReleephRequestTransaction::TYPE_REQUEST)
- ->setNewValue($commit->getPHID());
-
- $xactions[] = id(new ReleephRequestTransaction())
- ->setTransactionType(ReleephRequestTransaction::TYPE_USER_INTENT)
- ->setMetadataValue('userPHID', $user->getPHID())
- ->setMetadataValue(
- 'isAuthoritative',
- $releeph_project->isAuthoritative($user))
- ->setNewValue(ReleephRequest::INTENT_WANT);
-
- foreach ($fields as $field) {
- if (!$field->isEditable()) {
- continue;
- }
- $field->setReleephRequest($releeph_request);
- try {
- $field->setValueFromConduitAPIRequest($request);
- } catch (ReleephFieldParseException $ex) {
- throw id(new ConduitException('ERR_FIELD_PARSE'))
- ->setErrorDescription($ex->getMessage());
- }
- }
-
- $editor = id(new ReleephRequestTransactionalEditor())
- ->setActor($user)
- ->setContinueOnNoEffect(true)
- ->setContentSource($request->newContentSource());
-
- $editor->applyTransactions($releeph_request, $xactions);
- }
-
- $url = PhabricatorEnv::getProductionURI('/Y'.$releeph_request->getID());
- $results[$thing] = array(
- 'thing' => $thing,
- 'branch' => $releeph_branch->getDisplayNameWithDetail(),
- 'commitName' => $name,
- 'commitID' => $commit->getCommitIdentifier(),
- 'url' => $url,
- 'requestID' => $releeph_request->getID(),
- 'requestor' => $viewer_handle->getName(),
- 'requestTime' => $releeph_request->getDateCreated(),
- 'existing' => $existing_releeph_request !== null,
- );
- }
-
- return $results;
- }
-
-}
diff --git a/src/applications/releeph/conduit/work/ReleephWorkCanPushConduitAPIMethod.php b/src/applications/releeph/conduit/work/ReleephWorkCanPushConduitAPIMethod.php
deleted file mode 100644
index 0086209575..0000000000
--- a/src/applications/releeph/conduit/work/ReleephWorkCanPushConduitAPIMethod.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-
-final class ReleephWorkCanPushConduitAPIMethod extends ReleephConduitAPIMethod {
-
- public function getAPIMethodName() {
- return 'releephwork.canpush';
- }
-
- public function getMethodStatus() {
- return self::METHOD_STATUS_UNSTABLE;
- }
-
- public function getMethodDescription() {
- return pht('Return whether the conduit user is allowed to push.');
- }
-
- protected function defineParamTypes() {
- return array(
- 'projectPHID' => 'required string',
- );
- }
-
- protected function defineReturnType() {
- return 'bool';
- }
-
- protected function execute(ConduitAPIRequest $request) {
- $releeph_project = id(new ReleephProject())
- ->loadOneWhere('phid = %s', $request->getValue('projectPHID'));
- $user = $request->getUser();
- return $releeph_project->isAuthoritative($user);
- }
-
-}
diff --git a/src/applications/releeph/conduit/work/ReleephWorkGetAuthorInfoConduitAPIMethod.php b/src/applications/releeph/conduit/work/ReleephWorkGetAuthorInfoConduitAPIMethod.php
deleted file mode 100644
index 6db9fd76ee..0000000000
--- a/src/applications/releeph/conduit/work/ReleephWorkGetAuthorInfoConduitAPIMethod.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-final class ReleephWorkGetAuthorInfoConduitAPIMethod
- extends ReleephConduitAPIMethod {
-
- public function getAPIMethodName() {
- return 'releephwork.getauthorinfo';
- }
-
- public function getMethodStatus() {
- return self::METHOD_STATUS_UNSTABLE;
- }
-
- public function getMethodDescription() {
- return pht('Return a string to use as the VCS author.');
- }
-
- protected function defineParamTypes() {
- return array(
- 'userPHID' => 'required string',
- 'vcsType' => 'required string',
- );
- }
-
- protected function defineReturnType() {
- return 'nonempty string';
- }
-
- protected function execute(ConduitAPIRequest $request) {
- $user = id(new PhabricatorUser())
- ->loadOneWhere('phid = %s', $request->getValue('userPHID'));
-
- $email = $user->loadPrimaryEmailAddress();
- if (is_numeric($email)) {
- $email = $user->getUserName().'@fb.com';
- }
-
- return sprintf(
- '%s <%s>',
- $user->getRealName(),
- $email);
- }
-
-}
diff --git a/src/applications/releeph/conduit/work/ReleephWorkGetBranchCommitMessageConduitAPIMethod.php b/src/applications/releeph/conduit/work/ReleephWorkGetBranchCommitMessageConduitAPIMethod.php
deleted file mode 100644
index 43bdd04161..0000000000
--- a/src/applications/releeph/conduit/work/ReleephWorkGetBranchCommitMessageConduitAPIMethod.php
+++ /dev/null
@@ -1,104 +0,0 @@
-<?php
-
-final class ReleephWorkGetBranchCommitMessageConduitAPIMethod
- extends ReleephConduitAPIMethod {
-
- public function getAPIMethodName() {
- return 'releephwork.getbranchcommitmessage';
- }
-
- public function getMethodStatus() {
- return self::METHOD_STATUS_UNSTABLE;
- }
-
- public function getMethodDescription() {
- return pht('Get a commit message for committing a Releeph branch.');
- }
-
- protected function defineParamTypes() {
- return array(
- 'branchPHID' => 'required string',
- );
- }
-
- protected function defineReturnType() {
- return 'nonempty string';
- }
-
- protected function execute(ConduitAPIRequest $request) {
- $viewer = $request->getUser();
-
- $branch = id(new ReleephBranchQuery())
- ->setViewer($viewer)
- ->withPHIDs(array($request->getValue('branchPHID')))
- ->executeOne();
-
- $project = $branch->getProduct();
-
- $creator_phid = $branch->getCreatedByUserPHID();
- $cut_phid = $branch->getCutPointCommitPHID();
-
- $phids = array(
- $branch->getPHID(),
- $project->getPHID(),
- $creator_phid,
- $cut_phid,
- );
-
- $handles = id(new PhabricatorHandleQuery())
- ->setViewer($request->getUser())
- ->withPHIDs($phids)
- ->execute();
-
- $h_branch = $handles[$branch->getPHID()];
- $h_project = $handles[$project->getPHID()];
-
- // Not as customizable as a ReleephRequest's commit message. It doesn't
- // really need to be.
- // TODO: Yes it does, see FB-specific stuff below.
- $commit_message = array();
- $commit_message[] = $h_branch->getFullName();
- $commit_message[] = $h_branch->getURI();
-
- $commit_message[] = pht('Cut Point: %s', $handles[$cut_phid]->getName());
-
- $cut_point_pr_commit = id(new PhabricatorRepositoryCommit())
- ->loadOneWhere('phid = %s', $cut_phid);
- $cut_point_commit_date = strftime(
- '%Y-%m-%d %H:%M:%S%z',
- $cut_point_pr_commit->getEpoch());
- $commit_message[] = pht('Cut Point Date: %s', $cut_point_commit_date);
-
- $commit_message[] = pht(
- 'Created By: %s',
- $handles[$creator_phid]->getName());
-
- $project_uri = $project->getURI();
- $commit_message[] = pht(
- 'Project: %s',
- $h_project->getName().' '.$project_uri);
-
- /**
- * Required for 090-limit_new_branch_creations.sh in
- * admin/scripts/git/hosting/hooks/update.d (in the E repo):
- *
- * http://fburl.com/2372545
- *
- * The commit message must have a line saying:
- *
- * @new-branch: <branch-name>
- *
- */
- $repo = $project->getRepository();
- switch ($repo->getVersionControlSystem()) {
- case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
- $commit_message[] = sprintf(
- '@new-branch: %s',
- $branch->getName());
- break;
- }
-
- return implode("\n\n", $commit_message);
- }
-
-}
diff --git a/src/applications/releeph/conduit/work/ReleephWorkGetBranchConduitAPIMethod.php b/src/applications/releeph/conduit/work/ReleephWorkGetBranchConduitAPIMethod.php
deleted file mode 100644
index 7941710528..0000000000
--- a/src/applications/releeph/conduit/work/ReleephWorkGetBranchConduitAPIMethod.php
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-
-final class ReleephWorkGetBranchConduitAPIMethod
- extends ReleephConduitAPIMethod {
-
- public function getAPIMethodName() {
- return 'releephwork.getbranch';
- }
-
- public function getMethodStatus() {
- return self::METHOD_STATUS_UNSTABLE;
- }
-
- public function getMethodDescription() {
- return pht('Return information to help checkout / cut a Releeph branch.');
- }
-
- protected function defineParamTypes() {
- return array(
- 'branchPHID' => 'required string',
- );
- }
-
- protected function defineReturnType() {
- return 'dict<string, wild>';
- }
-
- protected function execute(ConduitAPIRequest $request) {
- $branch = id(new ReleephBranchQuery())
- ->setViewer($request->getUser())
- ->withPHIDs(array($request->getValue('branchPHID')))
- ->needCutPointCommits(true)
- ->executeOne();
-
- $cut_phid = $branch->getCutPointCommitPHID();
- $phids = array($cut_phid);
- $handles = id(new PhabricatorHandleQuery())
- ->setViewer($request->getUser())
- ->withPHIDs($phids)
- ->execute();
-
- $project = $branch->getProject();
- $repo = $project->getRepository();
- $commit = $branch->getCutPointCommit();
-
- return array(
- 'branchName' => $branch->getName(),
- 'branchPHID' => $branch->getPHID(),
- 'vcsType' => $repo->getVersionControlSystem(),
- 'cutCommitID' => $commit->getCommitIdentifier(),
- 'cutCommitName' => $handles[$cut_phid]->getName(),
- 'creatorPHID' => $branch->getCreatedByUserPHID(),
- 'trunk' => $project->getTrunkBranch(),
- );
- }
-
-}
diff --git a/src/applications/releeph/conduit/work/ReleephWorkGetCommitMessageConduitAPIMethod.php b/src/applications/releeph/conduit/work/ReleephWorkGetCommitMessageConduitAPIMethod.php
deleted file mode 100644
index 7f645c4be8..0000000000
--- a/src/applications/releeph/conduit/work/ReleephWorkGetCommitMessageConduitAPIMethod.php
+++ /dev/null
@@ -1,96 +0,0 @@
-<?php
-
-final class ReleephWorkGetCommitMessageConduitAPIMethod
- extends ReleephConduitAPIMethod {
-
- public function getAPIMethodName() {
- return 'releephwork.getcommitmessage';
- }
-
- public function getMethodStatus() {
- return self::METHOD_STATUS_UNSTABLE;
- }
-
- public function getMethodDescription() {
- return pht(
- 'Get commit message components for building a %s commit message.',
- 'ReleephRequest');
- }
-
- protected function defineParamTypes() {
- $action_const = $this->formatStringConstants(array('pick', 'revert'));
-
- return array(
- 'requestPHID' => 'required string',
- 'action' => 'required '.$action_const,
- );
- }
-
- protected function defineReturnType() {
- return 'dict<string, string>';
- }
-
- protected function execute(ConduitAPIRequest $request) {
- $viewer = $request->getUser();
-
- $releeph_request = id(new ReleephRequestQuery())
- ->setViewer($viewer)
- ->withPHIDs(array($request->getValue('requestPHID')))
- ->executeOne();
-
- $action = $request->getValue('action');
-
- $title = $releeph_request->getSummaryForDisplay();
-
- $commit_message = array();
-
- $branch = $releeph_request->getBranch();
- $project = $branch->getProduct();
-
- $selector = $project->getReleephFieldSelector();
- $fields = $selector->getFieldSpecifications();
- $fields = $selector->sortFieldsForCommitMessage($fields);
-
- foreach ($fields as $field) {
- $field
- ->setUser($request->getUser())
- ->setReleephProject($project)
- ->setReleephBranch($branch)
- ->setReleephRequest($releeph_request);
-
- $label = null;
- $value = null;
-
- switch ($action) {
- case 'pick':
- if ($field->shouldAppearOnCommitMessage()) {
- $label = $field->renderLabelForCommitMessage();
- $value = $field->renderValueForCommitMessage();
- }
- break;
-
- case 'revert':
- if ($field->shouldAppearOnRevertMessage()) {
- $label = $field->renderLabelForRevertMessage();
- $value = $field->renderValueForRevertMessage();
- }
- break;
- }
-
- if ($label && $value) {
- if (strpos($value, "\n") !== false ||
- substr($value, 0, 2) === ' ') {
- $commit_message[] = "{$label}:\n{$value}";
- } else {
- $commit_message[] = "{$label}: {$value}";
- }
- }
- }
-
- return array(
- 'title' => $title,
- 'body' => implode("\n\n", $commit_message),
- );
- }
-
-}
diff --git a/src/applications/releeph/conduit/work/ReleephWorkNextRequestConduitAPIMethod.php b/src/applications/releeph/conduit/work/ReleephWorkNextRequestConduitAPIMethod.php
deleted file mode 100644
index fb8a8ad0dc..0000000000
--- a/src/applications/releeph/conduit/work/ReleephWorkNextRequestConduitAPIMethod.php
+++ /dev/null
@@ -1,228 +0,0 @@
-<?php
-
-final class ReleephWorkNextRequestConduitAPIMethod
- extends ReleephConduitAPIMethod {
-
- private $project;
- private $branch;
-
- public function getAPIMethodName() {
- return 'releephwork.nextrequest';
- }
-
- public function getMethodStatus() {
- return self::METHOD_STATUS_UNSTABLE;
- }
-
- public function getMethodDescription() {
- return pht(
- 'Return info required to cut a branch, and pick and revert %s.',
- 'ReleephRequests');
- }
-
- protected function defineParamTypes() {
- return array(
- 'branchPHID' => 'required phid',
- 'seen' => 'required map<string, bool>',
- );
- }
-
- protected function defineReturnType() {
- return '';
- }
-
- protected function defineErrorTypes() {
- return array(
- 'ERR-NOT-PUSHER' => pht(
- 'You are not listed as a pusher for the Releeph project!'),
- );
- }
-
- protected function execute(ConduitAPIRequest $request) {
- $viewer = $request->getUser();
- $seen = $request->getValue('seen');
-
- $branch = id(new ReleephBranchQuery())
- ->setViewer($viewer)
- ->withPHIDs(array($request->getValue('branchPHID')))
- ->executeOne();
-
- $project = $branch->getProduct();
-
- $needs_pick = array();
- $needs_revert = array();
-
- // Load every request ever made for this branch...?!!!
- $releeph_requests = id(new ReleephRequestQuery())
- ->setViewer($viewer)
- ->withBranchIDs(array($branch->getID()))
- ->execute();
-
- foreach ($releeph_requests as $candidate) {
- $phid = $candidate->getPHID();
- if (idx($seen, $phid)) {
- continue;
- }
-
- $should = $candidate->shouldBeInBranch();
- $in = $candidate->getInBranch();
- if ($should && !$in) {
- $needs_pick[] = $candidate;
- }
- if (!$should && $in) {
- $needs_revert[] = $candidate;
- }
- }
-
- /**
- * Sort both needs_pick and needs_revert in ascending commit order, as
- * discovered by Phabricator (using the `id` column to perform that
- * ordering).
- *
- * This is easy for $needs_pick as the ordinal is stored. It is hard for
- * reverts, as we have to look that information up.
- */
- $needs_pick = $this->sortPicks($needs_pick);
- $needs_revert = $this->sortReverts($needs_revert);
-
- /**
- * Do reverts first in reverse order, then the picks in original-commit
- * order.
- *
- * This seems like the correct thing to do, but there may be a better
- * algorithm for the releephwork.nextrequest Conduit call that orders
- * things better.
- *
- * We could also button-mash our way through everything that failed (at the
- * end of the run) to try failed things again.
- */
- $releeph_request = null;
- $action = null;
- if ($needs_revert) {
- $releeph_request = last($needs_revert);
- $action = 'revert';
- $commit_id = $releeph_request->getCommitIdentifier();
- $commit_phid = $releeph_request->getCommitPHID();
- } else if ($needs_pick) {
- $releeph_request = head($needs_pick);
- $action = 'pick';
- $commit = $releeph_request->loadPhabricatorRepositoryCommit();
- $commit_id = $commit->getCommitIdentifier();
- $commit_phid = $commit->getPHID();
- } else {
- // Return early if there's nothing to do!
- return array();
- }
-
- // Build the response
- $phids = array();
- $phids[] = $commit_phid;
-
- $diff_phid = null;
- $diff_rev_id = null;
-
- $requested_object = $releeph_request->getRequestedObject();
- if ($requested_object instanceof DifferentialRevision) {
- $diff_rev = $requested_object;
- } else {
- $diff_rev = null;
- }
-
- if ($diff_rev) {
- $diff_phid = $diff_rev->getPHID();
- $phids[] = $diff_phid;
- $diff_rev_id = $diff_rev->getID();
- }
-
- $phids[] = $releeph_request->getPHID();
- $handles = id(new PhabricatorHandleQuery())
- ->setViewer($request->getUser())
- ->withPHIDs($phids)
- ->execute();
-
- $diff_name = null;
- if ($diff_rev) {
- $diff_name = $handles[$diff_phid]->getName();
- }
-
- $new_author_phid = null;
- if ($diff_rev) {
- $new_author_phid = $diff_rev->getAuthorPHID();
- } else {
- $pr_commit = $releeph_request->loadPhabricatorRepositoryCommit();
- if ($pr_commit) {
- $new_author_phid = $pr_commit->getAuthorPHID();
- }
- }
-
- return array(
- 'requestID' => $releeph_request->getID(),
- 'requestPHID' => $releeph_request->getPHID(),
- 'requestName' => $handles[$releeph_request->getPHID()]->getName(),
- 'requestorPHID' => $releeph_request->getRequestUserPHID(),
- 'action' => $action,
- 'diffRevID' => $diff_rev_id,
- 'diffName' => $diff_name,
- 'commitIdentifier' => $commit_id,
- 'commitPHID' => $commit_phid,
- 'commitName' => $handles[$commit_phid]->getName(),
- 'needsRevert' => mpull($needs_revert, 'getID'),
- 'needsPick' => mpull($needs_pick, 'getID'),
- 'newAuthorPHID' => $new_author_phid,
- );
- }
-
- private function sortPicks(array $releeph_requests) {
- $surrogate = array();
- foreach ($releeph_requests as $rq) {
- // TODO: it's likely that relying on the `id` column to provide
- // trunk-commit-order is thoroughly broken.
- $ordinal = (int)$rq->loadPhabricatorRepositoryCommit()->getID();
- $surrogate[$ordinal] = $rq;
- }
- ksort($surrogate);
- return $surrogate;
- }
-
- /**
- * Sort an array of ReleephRequests, that have been picked into a branch, in
- * the order in which they were picked to the branch.
- */
- private function sortReverts(array $releeph_requests) {
- if (!$releeph_requests) {
- return array();
- }
-
- // ReleephRequests, keyed by <branch-commit-id>
- $releeph_requests = mpull($releeph_requests, null, 'getCommitIdentifier');
-
- $commits = id(new PhabricatorRepositoryCommit())
- ->loadAllWhere(
- 'commitIdentifier IN (%Ls)',
- mpull($releeph_requests, 'getCommitIdentifier'));
-
- // A map of <branch-commit-id> => <branch-commit-ordinal>
- $surrogate = mpull($commits, 'getID', 'getCommitIdentifier');
-
- $unparsed = array();
- $result = array();
-
- foreach ($releeph_requests as $commit_id => $releeph_request) {
- $ordinal = idx($surrogate, $commit_id);
- if ($ordinal) {
- $result[$ordinal] = $releeph_request;
- } else {
- $unparsed[] = $releeph_request;
- }
- }
-
- // Sort $result in ascending order
- ksort($result);
-
- // Unparsed commits we'll just have to guess, based on time
- $unparsed = msort($unparsed, 'getDateModified');
-
- return array_merge($result, $unparsed);
- }
-
-}
diff --git a/src/applications/releeph/conduit/work/ReleephWorkRecordConduitAPIMethod.php b/src/applications/releeph/conduit/work/ReleephWorkRecordConduitAPIMethod.php
deleted file mode 100644
index 9cfbe72c99..0000000000
--- a/src/applications/releeph/conduit/work/ReleephWorkRecordConduitAPIMethod.php
+++ /dev/null
@@ -1,73 +0,0 @@
-<?php
-
-final class ReleephWorkRecordConduitAPIMethod
- extends ReleephConduitAPIMethod {
-
- public function getAPIMethodName() {
- return 'releephwork.record';
- }
-
- public function getMethodStatus() {
- return self::METHOD_STATUS_UNSTABLE;
- }
-
- /**
- * Record that a request was committed locally, and is about to be pushed to
- * the remote repository.
- *
- * This lets us mark a ReleephRequest as being in a branch in real time so
- * that no one else tries to pick it.
- *
- * When the daemons discover this commit in the repository with
- * DifferentialReleephRequestFieldSpecification, we'll be able to record the
- * commit's PHID as well. That process is slow though, and we don't want to
- * wait a whole minute before marking something as cleanly picked or
- * reverted.
- */
- public function getMethodDescription() {
- return pht(
- 'Record whether we committed a pick or revert '.
- 'to the upstream repository.');
- }
-
- protected function defineParamTypes() {
- $action_const = $this->formatStringConstants(
- array(
- 'pick',
- 'revert',
- ));
-
- return array(
- 'requestPHID' => 'required string',
- 'action' => 'required '.$action_const,
- 'commitIdentifier' => 'required string',
- );
- }
-
- protected function defineReturnType() {
- return 'void';
- }
-
- protected function execute(ConduitAPIRequest $request) {
- $action = $request->getValue('action');
- $new_commit_id = $request->getValue('commitIdentifier');
-
- $releeph_request = id(new ReleephRequest())
- ->loadOneWhere('phid = %s', $request->getValue('requestPHID'));
-
- $xactions = array();
-
- $xactions[] = id(new ReleephRequestTransaction())
- ->setTransactionType(ReleephRequestTransaction::TYPE_COMMIT)
- ->setMetadataValue('action', $action)
- ->setNewValue($new_commit_id);
-
- $editor = id(new ReleephRequestTransactionalEditor())
- ->setActor($request->getUser())
- ->setContinueOnNoEffect(true)
- ->setContentSource($request->newContentSource());
-
- $editor->applyTransactions($releeph_request, $xactions);
- }
-
-}
diff --git a/src/applications/releeph/conduit/work/ReleephWorkRecordPickStatusConduitAPIMethod.php b/src/applications/releeph/conduit/work/ReleephWorkRecordPickStatusConduitAPIMethod.php
deleted file mode 100644
index 5de0108a25..0000000000
--- a/src/applications/releeph/conduit/work/ReleephWorkRecordPickStatusConduitAPIMethod.php
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-
-final class ReleephWorkRecordPickStatusConduitAPIMethod
- extends ReleephConduitAPIMethod {
-
- public function getAPIMethodName() {
- return 'releephwork.recordpickstatus';
- }
-
- public function getMethodStatus() {
- return self::METHOD_STATUS_UNSTABLE;
- }
-
- public function getMethodDescription() {
- return pht('Record whether a pick or revert was successful or not.');
- }
-
- protected function defineParamTypes() {
- $action_const = $this->formatStringConstants(
- array(
- 'pick',
- 'revert',
- ));
-
- return array(
- 'requestPHID' => 'required string',
- 'action' => 'required '.$action_const,
- 'ok' => 'required bool',
- 'dryRun' => 'optional bool',
- 'details' => 'optional dict<string, wild>',
- );
- }
-
- protected function defineReturnType() {
- return '';
- }
-
- protected function execute(ConduitAPIRequest $request) {
- $action = $request->getValue('action');
- $ok = $request->getValue('ok');
- $dry_run = $request->getValue('dryRun');
- $details = $request->getValue('details', array());
-
- switch ($request->getValue('action')) {
- case 'pick':
- $pick_status = $ok
- ? ReleephRequest::PICK_OK
- : ReleephRequest::PICK_FAILED;
- break;
-
- case 'revert':
- $pick_status = $ok
- ? ReleephRequest::REVERT_OK
- : ReleephRequest::REVERT_FAILED;
- break;
-
- default:
- throw new Exception(pht('Unknown action %s!', $action));
- }
-
- $releeph_request = id(new ReleephRequest())
- ->loadOneWhere('phid = %s', $request->getValue('requestPHID'));
-
- $editor = id(new ReleephRequestTransactionalEditor())
- ->setActor($request->getUser())
- ->setContinueOnNoEffect(true)
- ->setContentSource($request->newContentSource());
-
- $xactions = array();
-
- $xactions[] = id(new ReleephRequestTransaction())
- ->setTransactionType(ReleephRequestTransaction::TYPE_PICK_STATUS)
- ->setMetadataValue('dryRun', $dry_run)
- ->setMetadataValue('details', $details)
- ->setNewValue($pick_status);
-
- $editor->applyTransactions($releeph_request, $xactions);
- }
-
-}
diff --git a/src/applications/releeph/config/PhabricatorReleephApplicationConfigOptions.php b/src/applications/releeph/config/PhabricatorReleephApplicationConfigOptions.php
deleted file mode 100644
index 08dace52a7..0000000000
--- a/src/applications/releeph/config/PhabricatorReleephApplicationConfigOptions.php
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-
-final class PhabricatorReleephApplicationConfigOptions
- extends PhabricatorApplicationConfigOptions {
-
- public function getName() {
- return pht('Releeph');
- }
-
- public function getDescription() {
- return pht('Options for configuring Releeph, the release branch tool.');
- }
-
- public function getIcon() {
- return 'fa-flag-checkered';
- }
-
- public function getGroup() {
- return 'apps';
- }
-
- public function getOptions() {
- $default_fields = array(
- new ReleephSummaryFieldSpecification(),
- new ReleephRequestorFieldSpecification(),
- new ReleephSeverityFieldSpecification(),
- new ReleephIntentFieldSpecification(),
- new ReleephReasonFieldSpecification(),
- new ReleephAuthorFieldSpecification(),
- new ReleephRevisionFieldSpecification(),
- new ReleephOriginalCommitFieldSpecification(),
- new ReleephBranchCommitFieldSpecification(),
- new ReleephDiffSizeFieldSpecification(),
- new ReleephDiffChurnFieldSpecification(),
- new ReleephDiffMessageFieldSpecification(),
- new ReleephCommitMessageFieldSpecification(),
- );
-
- $default = array();
- foreach ($default_fields as $default_field) {
- $default[$default_field->getFieldKey()] = true;
- }
-
- foreach ($default as $key => $enabled) {
- $default[$key] = array(
- 'disabled' => !$enabled,
- );
- }
-
- $custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType';
-
- return array(
- $this->newOption('releeph.fields', $custom_field_type, $default)
- ->setCustomData('ReleephFieldSpecification'),
- $this->newOption(
- 'releeph.default-branch-template',
- 'string',
- 'releases/%P/%p-%Y%m%d-%v')
- ->setDescription(
- pht(
- 'The default branch template for new branches in unconfigured '.
- 'Releeph projects. This is also configurable on a per-project '.
- 'basis.')),
- );
- }
-
-}
diff --git a/src/applications/releeph/constants/ReleephRequestStatus.php b/src/applications/releeph/constants/ReleephRequestStatus.php
deleted file mode 100644
index 2d29a95921..0000000000
--- a/src/applications/releeph/constants/ReleephRequestStatus.php
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-
-final class ReleephRequestStatus extends Phobject {
-
- const STATUS_REQUESTED = 1;
- const STATUS_NEEDS_PICK = 2; // aka approved
- const STATUS_REJECTED = 3;
- const STATUS_ABANDONED = 4;
- const STATUS_PICKED = 5;
- const STATUS_REVERTED = 6;
- const STATUS_NEEDS_REVERT = 7; // aka revert requested
-
- public static function getStatusDescriptionFor($status) {
- $descriptions = array(
- self::STATUS_REQUESTED => pht('Requested'),
- self::STATUS_REJECTED => pht('Rejected'),
- self::STATUS_ABANDONED => pht('Abandoned'),
- self::STATUS_PICKED => pht('Pulled'),
- self::STATUS_REVERTED => pht('Reverted'),
- self::STATUS_NEEDS_PICK => pht('Needs Pull'),
- self::STATUS_NEEDS_REVERT => pht('Needs Revert'),
- );
- return idx($descriptions, $status, '??');
- }
-
- public static function getStatusClassSuffixFor($status) {
- $description = self::getStatusDescriptionFor($status);
- $class = str_replace(' ', '-', strtolower($description));
- return $class;
- }
-
-}
diff --git a/src/applications/releeph/controller/ReleephController.php b/src/applications/releeph/controller/ReleephController.php
deleted file mode 100644
index f48d2858e8..0000000000
--- a/src/applications/releeph/controller/ReleephController.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-
-abstract class ReleephController extends PhabricatorController {
-
- public function buildSideNavView($for_app = false) {
- $user = $this->getRequest()->getUser();
-
- $nav = new AphrontSideNavFilterView();
- $nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
-
- if ($for_app) {
- $nav->addFilter('project/create/', pht('Create Product'));
- }
-
- id(new ReleephProductSearchEngine())
- ->setViewer($user)
- ->addNavigationItems($nav->getMenu());
-
- $nav->selectFilter(null);
-
- return $nav;
- }
-
- public function buildApplicationMenu() {
- return $this->buildSideNavView(true)->getMenu();
- }
-
-
- protected function getProductViewURI(ReleephProject $product) {
- return $this->getApplicationURI('project/'.$product->getID().'/');
- }
-
- protected function getBranchViewURI(ReleephBranch $branch) {
- return $this->getApplicationURI('branch/'.$branch->getID().'/');
- }
-
-}
diff --git a/src/applications/releeph/controller/branch/ReleephBranchAccessController.php b/src/applications/releeph/controller/branch/ReleephBranchAccessController.php
deleted file mode 100644
index 8b675ed2da..0000000000
--- a/src/applications/releeph/controller/branch/ReleephBranchAccessController.php
+++ /dev/null
@@ -1,73 +0,0 @@
-<?php
-
-final class ReleephBranchAccessController extends ReleephBranchController {
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $action = $request->getURIData('action');
- $id = $request->getURIData('branchID');
-
- $branch = id(new ReleephBranchQuery())
- ->setViewer($viewer)
- ->withIDs(array($id))
- ->requireCapabilities(
- array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- ))
- ->executeOne();
- if (!$branch) {
- return new Aphront404Response();
- }
- $this->setBranch($branch);
-
- switch ($action) {
- case 'close':
- case 're-open':
- break;
- default:
- return new Aphront404Response();
- }
-
- $branch_uri = $this->getBranchViewURI($branch);
- if ($request->isFormPost()) {
-
- if ($action == 're-open') {
- $is_active = 1;
- } else {
- $is_active = 0;
- }
-
- id(new ReleephBranchEditor())
- ->setActor($request->getUser())
- ->setReleephBranch($branch)
- ->changeBranchAccess($is_active);
-
- return id(new AphrontReloadResponse())->setURI($branch_uri);
- }
-
- if ($action == 'close') {
- $title_text = pht('Really Close Branch?');
- $short = pht('Close Branch');
- $body_text = pht(
- 'Really close the branch "%s"?',
- phutil_tag('strong', array(), $branch->getBasename()));
- $button_text = pht('Close Branch');
- } else {
- $title_text = pht('Really Reopen Branch?');
- $short = pht('Reopen Branch');
- $body_text = pht(
- 'Really reopen the branch "%s"?',
- phutil_tag('strong', array(), $branch->getBasename()));
- $button_text = pht('Reopen Branch');
- }
-
- return $this->newDialog()
- ->setTitle($title_text)
- ->setShortTitle($short)
- ->appendChild($body_text)
- ->addSubmitButton($button_text)
- ->addCancelButton($branch_uri);
- }
-
-}
diff --git a/src/applications/releeph/controller/branch/ReleephBranchController.php b/src/applications/releeph/controller/branch/ReleephBranchController.php
deleted file mode 100644
index 62b26b4f94..0000000000
--- a/src/applications/releeph/controller/branch/ReleephBranchController.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-
-abstract class ReleephBranchController extends ReleephController {
-
- private $branch;
-
- public function setBranch($branch) {
- $this->branch = $branch;
- return $this;
- }
-
- public function getBranch() {
- return $this->branch;
- }
-
- protected function buildApplicationCrumbs() {
- $crumbs = parent::buildApplicationCrumbs();
-
- $branch = $this->getBranch();
- if ($branch) {
- $product = $branch->getProduct();
-
- $crumbs->addTextCrumb(
- $product->getName(),
- $this->getProductViewURI($product));
-
- $crumbs->addTextCrumb(
- $branch->getName(),
- $this->getBranchViewURI($branch));
- }
-
- return $crumbs;
- }
-
-}
diff --git a/src/applications/releeph/controller/branch/ReleephBranchCreateController.php b/src/applications/releeph/controller/branch/ReleephBranchCreateController.php
deleted file mode 100644
index e03e432d1f..0000000000
--- a/src/applications/releeph/controller/branch/ReleephBranchCreateController.php
+++ /dev/null
@@ -1,133 +0,0 @@
-<?php
-
-final class ReleephBranchCreateController extends ReleephProductController {
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $id = $request->getURIData('projectID');
-
- $product = id(new ReleephProductQuery())
- ->setViewer($viewer)
- ->withIDs(array($id))
- ->requireCapabilities(
- array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- ))
- ->executeOne();
- if (!$product) {
- return new Aphront404Response();
- }
- $this->setProduct($product);
-
-
- $cut_point = $request->getStr('cutPoint');
- $symbolic_name = $request->getStr('symbolicName');
-
- if (!$cut_point) {
- $repository = $product->getRepository();
- switch ($repository->getVersionControlSystem()) {
- case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
- break;
- case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
- $cut_point = $product->getTrunkBranch();
- break;
- }
- }
-
- $e_cut = true;
- $errors = array();
-
- $branch_date_control = id(new AphrontFormDateControl())
- ->setUser($request->getUser())
- ->setName('templateDate')
- ->setLabel(pht('Date'))
- ->setCaption(pht('The date used for filling out the branch template.'))
- ->setInitialTime(AphrontFormDateControl::TIME_START_OF_DAY);
- $branch_date = $branch_date_control->readValueFromRequest($request);
-
- if ($request->isFormPost()) {
- $cut_commit = null;
- if (!$cut_point) {
- $e_cut = pht('Required');
- $errors[] = pht('You must give a branch cut point');
- } else {
- try {
- $finder = id(new ReleephCommitFinder())
- ->setUser($request->getUser())
- ->setReleephProject($product);
- $cut_commit = $finder->fromPartial($cut_point);
- } catch (Exception $e) {
- $e_cut = pht('Invalid');
- $errors[] = $e->getMessage();
- }
- }
-
- if (!$errors) {
- $branch = id(new ReleephBranchEditor())
- ->setReleephProject($product)
- ->setActor($request->getUser())
- ->newBranchFromCommit(
- $cut_commit,
- $branch_date,
- $symbolic_name);
-
- $branch_uri = $this->getApplicationURI('branch/'.$branch->getID());
-
- return id(new AphrontRedirectResponse())
- ->setURI($branch_uri);
- }
- }
-
- $product_uri = $this->getProductViewURI($product);
-
- $form = id(new AphrontFormView())
- ->setUser($request->getUser())
- ->appendChild(
- id(new AphrontFormTextControl())
- ->setLabel(pht('Symbolic Name'))
- ->setName('symbolicName')
- ->setValue($symbolic_name)
- ->setCaption(pht(
- 'Mutable alternate name, for easy reference, (e.g. "LATEST")')))
- ->appendChild(
- id(new AphrontFormTextControl())
- ->setLabel(pht('Cut point'))
- ->setName('cutPoint')
- ->setValue($cut_point)
- ->setError($e_cut)
- ->setCaption(pht(
- 'A commit ID for your repo type, or a Diffusion ID like "rE123"')))
- ->appendChild($branch_date_control)
- ->appendChild(
- id(new AphrontFormSubmitControl())
- ->setValue(pht('Cut Branch'))
- ->addCancelButton($product_uri));
-
- $box = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Branch'))
- ->setFormErrors($errors)
- ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
- ->appendChild($form);
-
- $title = pht('New Branch');
-
- $crumbs = $this->buildApplicationCrumbs();
- $crumbs->addTextCrumb($title);
- $crumbs->setBorder(true);
-
- $header = id(new PHUIHeaderView())
- ->setHeader($title)
- ->setHeaderIcon('fa-plus-square');
-
- $view = id(new PHUITwoColumnView())
- ->setHeader($header)
- ->setFooter($box);
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($view);
-
- }
-}
diff --git a/src/applications/releeph/controller/branch/ReleephBranchEditController.php b/src/applications/releeph/controller/branch/ReleephBranchEditController.php
deleted file mode 100644
index 9d34e78668..0000000000
--- a/src/applications/releeph/controller/branch/ReleephBranchEditController.php
+++ /dev/null
@@ -1,114 +0,0 @@
-<?php
-
-final class ReleephBranchEditController extends ReleephBranchController {
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $id = $request->getURIData('branchID');
-
- $branch = id(new ReleephBranchQuery())
- ->setViewer($viewer)
- ->requireCapabilities(
- array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- ))
- ->withIDs(array($id))
- ->executeOne();
- if (!$branch) {
- return new Aphront404Response();
- }
- $this->setBranch($branch);
-
- $symbolic_name = $request->getStr(
- 'symbolicName',
- $branch->getSymbolicName());
-
- if ($request->isFormPost()) {
- $existing_with_same_symbolic_name =
- id(new ReleephBranch())
- ->loadOneWhere(
- 'id != %d AND releephProjectID = %d AND symbolicName = %s',
- $branch->getID(),
- $branch->getReleephProjectID(),
- $symbolic_name);
-
- $branch->openTransaction();
- $branch->setSymbolicName($symbolic_name);
-
- if ($existing_with_same_symbolic_name) {
- $existing_with_same_symbolic_name
- ->setSymbolicName(null)
- ->save();
- }
-
- $branch->save();
- $branch->saveTransaction();
-
- return id(new AphrontRedirectResponse())
- ->setURI($this->getBranchViewURI($branch));
- }
-
- $phids = array();
-
- $phids[] = $creator_phid = $branch->getCreatedByUserPHID();
- $phids[] = $cut_commit_phid = $branch->getCutPointCommitPHID();
-
- $handles = id(new PhabricatorHandleQuery())
- ->setViewer($request->getUser())
- ->withPHIDs($phids)
- ->execute();
-
- $form = id(new AphrontFormView())
- ->setUser($request->getUser())
- ->appendChild(
- id(new AphrontFormStaticControl())
- ->setLabel(pht('Branch Name'))
- ->setValue($branch->getName()))
- ->appendChild(
- id(new AphrontFormMarkupControl())
- ->setLabel(pht('Cut Point'))
- ->setValue($handles[$cut_commit_phid]->renderLink()))
- ->appendChild(
- id(new AphrontFormMarkupControl())
- ->setLabel(pht('Created By'))
- ->setValue($handles[$creator_phid]->renderLink()))
- ->appendChild(
- id(new AphrontFormTextControl())
- ->setLabel(pht('Symbolic Name'))
- ->setName('symbolicName')
- ->setValue($symbolic_name)
- ->setCaption(pht(
- 'Mutable alternate name, for easy reference, (e.g. "LATEST")')))
- ->appendChild(
- id(new AphrontFormSubmitControl())
- ->addCancelButton($this->getBranchViewURI($branch))
- ->setValue(pht('Save Branch')));
-
- $title = pht(
- 'Edit Branch: %s',
- $branch->getDisplayNameWithDetail());
-
- $box = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Branch'))
- ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
- ->appendChild($form);
-
- $crumbs = $this->buildApplicationCrumbs();
- $crumbs->addTextCrumb(pht('Edit'));
- $crumbs->setBorder(true);
-
- $header = id(new PHUIHeaderView())
- ->setHeader(pht('Edit Branch'))
- ->setHeaderIcon('fa-pencil');
-
- $view = id(new PHUITwoColumnView())
- ->setHeader($header)
- ->setFooter($box);
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($view);
- }
-}
diff --git a/src/applications/releeph/controller/branch/ReleephBranchHistoryController.php b/src/applications/releeph/controller/branch/ReleephBranchHistoryController.php
deleted file mode 100644
index 5a07a5c879..0000000000
--- a/src/applications/releeph/controller/branch/ReleephBranchHistoryController.php
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-
-final class ReleephBranchHistoryController extends ReleephBranchController {
-
- public function shouldAllowPublic() {
- return true;
- }
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $id = $request->getURIData('branchID');
-
- $branch = id(new ReleephBranchQuery())
- ->setViewer($viewer)
- ->withIDs(array($id))
- ->executeOne();
- if (!$branch) {
- return new Aphront404Response();
- }
- $this->setBranch($branch);
-
- $timeline = $this->buildTransactionTimeline(
- $branch,
- new ReleephBranchTransactionQuery());
- $timeline
- ->setShouldTerminate(true);
-
- $crumbs = $this->buildApplicationCrumbs();
- $crumbs->addTextCrumb(pht('History'));
- $crumbs->setBorder(true);
-
- $title = pht('Branch History');
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($timeline);
-
- }
-
-}
diff --git a/src/applications/releeph/controller/branch/ReleephBranchNamePreviewController.php b/src/applications/releeph/controller/branch/ReleephBranchNamePreviewController.php
deleted file mode 100644
index f2dbd23d8d..0000000000
--- a/src/applications/releeph/controller/branch/ReleephBranchNamePreviewController.php
+++ /dev/null
@@ -1,47 +0,0 @@
-<?php
-
-final class ReleephBranchNamePreviewController
- extends ReleephController {
-
- public function handleRequest(AphrontRequest $request) {
-
- $is_symbolic = $request->getBool('isSymbolic');
- $template = $request->getStr('template');
-
- if (!$is_symbolic && !$template) {
- $template = ReleephBranchTemplate::getDefaultTemplate();
- }
-
- $repository_phid = $request->getInt('repositoryPHID');
- $fake_commit_handle =
- ReleephBranchTemplate::getFakeCommitHandleFor(
- $repository_phid,
- $request->getUser());
-
- list($name, $errors) = id(new ReleephBranchTemplate())
- ->setCommitHandle($fake_commit_handle)
- ->setReleephProjectName($request->getStr('projectName'))
- ->setSymbolic($is_symbolic)
- ->interpolate($template);
-
- $markup = '';
-
- if ($name) {
- $markup = phutil_tag(
- 'div',
- array('class' => 'name'),
- $name);
- }
-
- if ($errors) {
- $markup .= phutil_tag(
- 'div',
- array('class' => 'error'),
- head($errors));
- }
-
- return id(new AphrontAjaxResponse())
- ->setContent(array('markup' => $markup));
- }
-
-}
diff --git a/src/applications/releeph/controller/branch/ReleephBranchViewController.php b/src/applications/releeph/controller/branch/ReleephBranchViewController.php
deleted file mode 100644
index cb4c3ed297..0000000000
--- a/src/applications/releeph/controller/branch/ReleephBranchViewController.php
+++ /dev/null
@@ -1,149 +0,0 @@
-<?php
-
-final class ReleephBranchViewController extends ReleephBranchController {
-
- public function shouldAllowPublic() {
- return true;
- }
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $id = $request->getURIData('branchID');
- $querykey = $request->getURIData('queryKey');
-
- $branch = id(new ReleephBranchQuery())
- ->setViewer($viewer)
- ->withIDs(array($id))
- ->executeOne();
- if (!$branch) {
- return new Aphront404Response();
- }
- $this->setBranch($branch);
-
- $controller = id(new PhabricatorApplicationSearchController())
- ->setPreface($this->renderPreface())
- ->setQueryKey($querykey)
- ->setSearchEngine($this->getSearchEngine())
- ->setNavigation($this->buildSideNavView());
-
- return $this->delegateToController($controller);
- }
-
-
- public function buildSideNavView($for_app = false) {
- $user = $this->getRequest()->getUser();
-
- $nav = new AphrontSideNavFilterView();
- $nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
-
- $this->getSearchEngine()->addNavigationItems($nav->getMenu());
-
- $nav->selectFilter(null);
-
- return $nav;
- }
-
- private function getSearchEngine() {
- $branch = $this->getBranch();
- return id(new ReleephRequestSearchEngine())
- ->setBranch($branch)
- ->setBaseURI($this->getApplicationURI('branch/'.$branch->getID().'/'))
- ->setViewer($this->getRequest()->getUser());
- }
-
- protected function buildApplicationCrumbs() {
- $crumbs = parent::buildApplicationCrumbs();
-
- $branch = $this->getBranch();
- if ($branch) {
- $pull_uri = $this->getApplicationURI('branch/pull/'.$branch->getID().'/');
- $crumbs->addAction(
- id(new PHUIListItemView())
- ->setHref($pull_uri)
- ->setName(pht('New Pull Request'))
- ->setIcon('fa-plus-square')
- ->setDisabled(!$branch->isActive()));
- }
-
- return $crumbs;
- }
-
- private function renderPreface() {
- $viewer = $this->getRequest()->getUser();
-
- $branch = $this->getBranch();
- $id = $branch->getID();
-
- $header = id(new PHUIHeaderView())
- ->setHeader($branch->getDisplayName())
- ->setUser($viewer)
- ->setPolicyObject($branch);
-
- if ($branch->getIsActive()) {
- $header->setStatus('fa-check', 'bluegrey', pht('Active'));
- } else {
- $header->setStatus('fa-ban', 'dark', pht('Closed'));
- }
-
- $actions = id(new PhabricatorActionListView())
- ->setUser($viewer)
- ->setObject($branch);
-
- $can_edit = PhabricatorPolicyFilter::hasCapability(
- $viewer,
- $branch,
- PhabricatorPolicyCapability::CAN_EDIT);
-
- $edit_uri = $this->getApplicationURI("branch/edit/{$id}/");
- $close_uri = $this->getApplicationURI("branch/close/{$id}/");
- $reopen_uri = $this->getApplicationURI("branch/re-open/{$id}/");
- $history_uri = $this->getApplicationURI("branch/{$id}/history/");
-
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Edit Branch'))
- ->setHref($edit_uri)
- ->setIcon('fa-pencil')
- ->setDisabled(!$can_edit)
- ->setWorkflow(!$can_edit));
-
- if ($branch->getIsActive()) {
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Close Branch'))
- ->setHref($close_uri)
- ->setIcon('fa-times')
- ->setDisabled(!$can_edit)
- ->setWorkflow(true));
- } else {
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Reopen Branch'))
- ->setHref($reopen_uri)
- ->setIcon('fa-plus')
- ->setUser($viewer)
- ->setDisabled(!$can_edit)
- ->setWorkflow(true));
- }
-
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('View History'))
- ->setHref($history_uri)
- ->setIcon('fa-list'));
-
- $properties = id(new PHUIPropertyListView())
- ->setUser($viewer)
- ->setObject($branch)
- ->setActionList($actions);
-
- $properties->addProperty(
- pht('Branch'),
- $branch->getName());
-
- return id(new PHUIObjectBoxView())
- ->setHeader($header)
- ->addPropertyList($properties);
- }
-
-}
diff --git a/src/applications/releeph/controller/product/ReleephProductActionController.php b/src/applications/releeph/controller/product/ReleephProductActionController.php
deleted file mode 100644
index da82e2d6ad..0000000000
--- a/src/applications/releeph/controller/product/ReleephProductActionController.php
+++ /dev/null
@@ -1,85 +0,0 @@
-<?php
-
-final class ReleephProductActionController extends ReleephProductController {
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $id = $request->getURIData('projectID');
- $action = $request->getURIData('action');
-
- $product = id(new ReleephProductQuery())
- ->withIDs(array($id))
- ->requireCapabilities(
- array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- ))
- ->setViewer($viewer)
- ->executeOne();
- if (!$product) {
- return new Aphront404Response();
- }
-
- $this->setProduct($product);
-
- $product_id = $product->getID();
- $product_uri = $this->getProductViewURI($product);
-
- switch ($action) {
- case 'deactivate':
- case 'activate':
- break;
- default:
- throw new Aphront404Response();
- }
-
- if ($request->isFormPost()) {
- $type_active = ReleephProductTransaction::TYPE_ACTIVE;
-
- $xactions = array();
- if ($action == 'activate') {
- $xactions[] = id(new ReleephProductTransaction())
- ->setTransactionType($type_active)
- ->setNewValue(1);
- } else {
- $xactions[] = id(new ReleephProductTransaction())
- ->setTransactionType($type_active)
- ->setNewValue(0);
- }
-
- $editor = id(new ReleephProductEditor())
- ->setActor($viewer)
- ->setContentSourceFromRequest($request)
- ->setContinueOnNoEffect(true)
- ->setContinueOnMissingFields(true);
-
- $editor->applyTransactions($product, $xactions);
-
- return id(new AphrontRedirectResponse())->setURI($product_uri);
- }
-
- if ($action == 'activate') {
- $title = pht('Activate Product?');
- $body = pht(
- 'Reactivate the product %s?',
- phutil_tag('strong', array(), $product->getName()));
- $submit = pht('Reactivate Product');
- $short = pht('Deactivate');
- } else {
- $title = pht('Really Deactivate Product?');
- $body = pht(
- 'Really deactivate the product %s?',
- phutil_tag('strong', array(), $product->getName()));
- $submit = pht('Deactivate Product');
- $short = pht('Activate');
- }
-
- return $this->newDialog()
- ->setTitle($title)
- ->setShortTitle($short)
- ->appendParagraph($body)
- ->addSubmitButton($submit)
- ->addCancelButton($product_uri);
- }
-
-}
diff --git a/src/applications/releeph/controller/product/ReleephProductController.php b/src/applications/releeph/controller/product/ReleephProductController.php
deleted file mode 100644
index 0e9090ebd4..0000000000
--- a/src/applications/releeph/controller/product/ReleephProductController.php
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-
-abstract class ReleephProductController extends ReleephController {
-
- private $product;
-
- protected function setProduct(ReleephProject $product) {
- $this->product = $product;
- return $this;
- }
-
- protected function getProduct() {
- return $this->product;
- }
-
- protected function buildApplicationCrumbs() {
- $crumbs = parent::buildApplicationCrumbs();
-
- $product = $this->getProduct();
- if ($product) {
- $crumbs->addTextCrumb(
- $product->getName(),
- $this->getProductViewURI($product));
- }
-
- return $crumbs;
- }
-
-
-}
diff --git a/src/applications/releeph/controller/product/ReleephProductCreateController.php b/src/applications/releeph/controller/product/ReleephProductCreateController.php
deleted file mode 100644
index 12da2ea3f4..0000000000
--- a/src/applications/releeph/controller/product/ReleephProductCreateController.php
+++ /dev/null
@@ -1,140 +0,0 @@
-<?php
-
-final class ReleephProductCreateController extends ReleephProductController {
-
- public function handleRequest(AphrontRequest $request) {
- $name = trim($request->getStr('name'));
- $trunk_branch = trim($request->getStr('trunkBranch'));
- $repository_phid = $request->getStr('repositoryPHID');
-
- $e_name = true;
- $e_trunk_branch = true;
- $errors = array();
-
- if ($request->isFormPost()) {
- if (!$name) {
- $e_name = pht('Required');
- $errors[] = pht(
- 'Your product should have a simple, descriptive name.');
- }
-
- if (!$trunk_branch) {
- $e_trunk_branch = pht('Required');
- $errors[] = pht(
- 'You must specify which branch you will be picking from.');
- }
-
- $pr_repository = id(new PhabricatorRepositoryQuery())
- ->setViewer($request->getUser())
- ->withPHIDs(array($repository_phid))
- ->executeOne();
-
-
- if (!$errors) {
- $releeph_product = id(new ReleephProject())
- ->setName($name)
- ->setTrunkBranch($trunk_branch)
- ->setRepositoryPHID($pr_repository->getPHID())
- ->setCreatedByUserPHID($request->getUser()->getPHID())
- ->setIsActive(1);
-
- try {
- $releeph_product->save();
-
- return id(new AphrontRedirectResponse())
- ->setURI($releeph_product->getURI());
- } catch (AphrontDuplicateKeyQueryException $ex) {
- $e_name = pht('Not Unique');
- $errors[] = pht('Another product already uses this name.');
- }
- }
- }
-
- $repo_options = $this->getRepositorySelectOptions();
-
- $product_name_input = id(new AphrontFormTextControl())
- ->setLabel(pht('Name'))
- ->setDisableAutocomplete(true)
- ->setName('name')
- ->setValue($name)
- ->setError($e_name)
- ->setCaption(pht('A name like "Thrift" but not "Thrift releases".'));
-
- $repository_input = id(new AphrontFormSelectControl())
- ->setLabel(pht('Repository'))
- ->setName('repositoryPHID')
- ->setValue($repository_phid)
- ->setOptions($repo_options);
-
- $branch_name_preview = id(new ReleephBranchPreviewView())
- ->setLabel(pht('Example Branch'))
- ->addControl('projectName', $product_name_input)
- ->addControl('repositoryPHID', $repository_input)
- ->addStatic('template', '')
- ->addStatic('isSymbolic', false);
-
- $form = id(new AphrontFormView())
- ->setUser($request->getUser())
- ->appendChild($product_name_input)
- ->appendChild($repository_input)
- ->appendChild(
- id(new AphrontFormTextControl())
- ->setLabel(pht('Trunk'))
- ->setName('trunkBranch')
- ->setValue($trunk_branch)
- ->setError($e_trunk_branch)
- ->setCaption(pht(
- 'The development branch, from which requests will be picked.')))
- ->appendChild($branch_name_preview)
- ->appendChild(
- id(new AphrontFormSubmitControl())
- ->addCancelButton('/releeph/project/')
- ->setValue(pht('Create Release Product')));
-
- $box = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Product'))
- ->setFormErrors($errors)
- ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
- ->setForm($form);
-
- $title = pht('Create New Product');
-
- $crumbs = $this->buildApplicationCrumbs();
- $crumbs->addTextCrumb(pht('New Product'));
- $crumbs->setBorder(true);
-
- $header = id(new PHUIHeaderView())
- ->setHeader($title)
- ->setHeaderIcon('fa-plus-square');
-
- $view = id(new PHUITwoColumnView())
- ->setHeader($header)
- ->setFooter($box);
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($view);
- }
-
- private function getRepositorySelectOptions() {
- $repos = id(new PhabricatorRepositoryQuery())
- ->setViewer($this->getRequest()->getUser())
- ->execute();
-
- $repos = msort($repos, 'getName');
- $repos = mpull($repos, null, 'getID');
-
- $choices = array();
-
- foreach ($repos as $repo_id => $repo) {
- $repo_name = $repo->getName();
- $display = $repo->getDisplayName();
- $choices[$repo->getPHID()] = "{$display} ({$repo_name})";
- }
-
- asort($choices);
- return $choices;
- }
-
-}
diff --git a/src/applications/releeph/controller/product/ReleephProductEditController.php b/src/applications/releeph/controller/product/ReleephProductEditController.php
deleted file mode 100644
index 7938f0d930..0000000000
--- a/src/applications/releeph/controller/product/ReleephProductEditController.php
+++ /dev/null
@@ -1,275 +0,0 @@
-<?php
-
-final class ReleephProductEditController extends ReleephProductController {
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $id = $request->getURIData('projectID');
-
- $product = id(new ReleephProductQuery())
- ->setViewer($viewer)
- ->withIDs(array($id))
- ->requireCapabilities(
- array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- ))
- ->executeOne();
- if (!$product) {
- return new Aphront404Response();
- }
- $this->setProduct($product);
-
- $e_name = true;
- $e_trunk_branch = true;
- $e_branch_template = false;
- $errors = array();
-
- $product_name = $request->getStr('name', $product->getName());
-
- $trunk_branch = $request->getStr('trunkBranch', $product->getTrunkBranch());
- $branch_template = $request->getStr('branchTemplate');
- if ($branch_template === null) {
- $branch_template = $product->getDetail('branchTemplate');
- }
- $pick_failure_instructions = $request->getStr('pickFailureInstructions',
- $product->getDetail('pick_failure_instructions'));
- $test_paths = $request->getStr('testPaths');
- if ($test_paths !== null) {
- $test_paths = array_filter(explode("\n", $test_paths));
- } else {
- $test_paths = $product->getDetail('testPaths', array());
- }
-
- $repository_phid = $product->getRepositoryPHID();
-
- if ($request->isFormPost()) {
- $pusher_phids = $request->getArr('pushers');
-
- if (!$product_name) {
- $e_name = pht('Required');
- $errors[] =
- pht('Your Releeph product should have a simple descriptive name.');
- }
-
- if (!$trunk_branch) {
- $e_trunk_branch = pht('Required');
- $errors[] =
- pht('You must specify which branch you will be picking from.');
- }
-
- $other_releeph_products = id(new ReleephProject())
- ->loadAllWhere('id != %d', $product->getID());
- $other_releeph_product_names = mpull($other_releeph_products,
- 'getName', 'getID');
-
- if (in_array($product_name, $other_releeph_product_names)) {
- $errors[] = pht('Releeph product name %s is already taken',
- $product_name);
- }
-
- foreach ($test_paths as $test_path) {
- $result = @preg_match($test_path, '');
- $is_a_valid_regexp = $result !== false;
- if (!$is_a_valid_regexp) {
- $errors[] = pht('Please provide a valid regular expression: '.
- '%s is not valid', $test_path);
- }
- }
-
- $product
- ->setName($product_name)
- ->setTrunkBranch($trunk_branch)
- ->setDetail('pushers', $pusher_phids)
- ->setDetail('pick_failure_instructions', $pick_failure_instructions)
- ->setDetail('branchTemplate', $branch_template)
- ->setDetail('testPaths', $test_paths);
-
- $fake_commit_handle = ReleephBranchTemplate::getFakeCommitHandleFor(
- $repository_phid,
- $viewer);
-
- if ($branch_template) {
- list($branch_name, $template_errors) = id(new ReleephBranchTemplate())
- ->setCommitHandle($fake_commit_handle)
- ->setReleephProjectName($product_name)
- ->interpolate($branch_template);
-
- if ($template_errors) {
- $e_branch_template = pht('Whoopsies!');
- foreach ($template_errors as $template_error) {
- $errors[] = pht('Template error: %s', $template_error);
- }
- }
- }
-
- if (!$errors) {
- $product->save();
-
- return id(new AphrontRedirectResponse())->setURI($product->getURI());
- }
- }
-
- $pusher_phids = $request->getArr(
- 'pushers',
- $product->getDetail('pushers', array()));
-
- $form = id(new AphrontFormView())
- ->setUser($request->getUser())
- ->appendChild(
- id(new AphrontFormTextControl())
- ->setLabel(pht('Name'))
- ->setName('name')
- ->setValue($product_name)
- ->setError($e_name)
- ->setCaption(pht('A name like "Thrift" but not "Thrift releases".')))
- ->appendChild(
- id(new AphrontFormStaticControl())
- ->setLabel(pht('Repository'))
- ->setValue(
- $product->getRepository()->getName()))
- ->appendChild(
- id(new AphrontFormStaticControl())
- ->setLabel(pht('Repository'))
- ->setValue(
- $product->getRepository()->getName()))
- ->appendChild(
- id(new AphrontFormStaticControl())
- ->setLabel(pht('Releeph Project PHID'))
- ->setValue(
- $product->getPHID()))
- ->appendChild(
- id(new AphrontFormTextControl())
- ->setLabel(pht('Trunk'))
- ->setValue($trunk_branch)
- ->setName('trunkBranch')
- ->setError($e_trunk_branch))
- ->appendChild(
- id(new AphrontFormTextAreaControl())
- ->setLabel(pht('Pick Instructions'))
- ->setValue($pick_failure_instructions)
- ->setName('pickFailureInstructions')
- ->setCaption(
- pht('Instructions for pick failures, which will be used '.
- 'in emails generated by failed picks')))
- ->appendChild(
- id(new AphrontFormTextAreaControl())
- ->setLabel(pht('Tests paths'))
- ->setValue(implode("\n", $test_paths))
- ->setName('testPaths')
- ->setCaption(
- pht('List of strings that all test files contain in their path '.
- 'in this project. One string per line. '.
- 'Examples: \'__tests__\', \'/javatests/\'...')));
-
- $branch_template_input = id(new AphrontFormTextControl())
- ->setName('branchTemplate')
- ->setValue($branch_template)
- ->setLabel(pht('Branch Template'))
- ->setError($e_branch_template)
- ->setCaption(
- pht("Leave this blank to use your installation's default."));
-
- $branch_template_preview = id(new ReleephBranchPreviewView())
- ->setLabel(pht('Preview'))
- ->addControl('template', $branch_template_input)
- ->addStatic('repositoryPHID', $repository_phid)
- ->addStatic('isSymbolic', false)
- ->addStatic('projectName', $product->getName());
-
- $form
- ->appendControl(
- id(new AphrontFormTokenizerControl())
- ->setLabel(pht('Pushers'))
- ->setName('pushers')
- ->setDatasource(new PhabricatorPeopleDatasource())
- ->setValue($pusher_phids))
- ->appendChild($branch_template_input)
- ->appendChild($branch_template_preview)
- ->appendRemarkupInstructions($this->getBranchHelpText());
-
- $form
- ->appendChild(
- id(new AphrontFormSubmitControl())
- ->addCancelButton('/releeph/product/')
- ->setValue(pht('Save')));
-
- $box = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Product'))
- ->setFormErrors($errors)
- ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
- ->appendChild($form);
-
- $title = pht('Edit Product');
-
- $crumbs = $this->buildApplicationCrumbs();
- $crumbs->addTextCrumb(pht('Edit Product'));
- $crumbs->setBorder(true);
-
- $header = id(new PHUIHeaderView())
- ->setHeader($title)
- ->setHeaderIcon('fa-pencil');
-
- $view = id(new PHUITwoColumnView())
- ->setHeader($header)
- ->setFooter($box);
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($view);
-
- }
-
- private function getBranchHelpText() {
- return <<<EOTEXT
-
-==== Interpolations ====
-
-| Code | Meaning
-| ----- | -------
-| `%P` | The name of your product, with spaces changed to "-".
-| `%p` | Like %P, but all lowercase.
-| `%Y` | The four digit year associated with the branch date.
-| `%m` | The two digit month.
-| `%d` | The two digit day.
-| `%v` | The handle of the commit where the branch was cut ("rXYZa4b3c2d1").
-| `%V` | The abbreviated commit id where the branch was cut ("a4b3c2d1").
-| `%..` | Any other sequence interpreted by `strftime()`.
-| `%%` | A literal percent sign.
-
-
-==== Tips for Branch Templates ====
-
-Use a directory to separate your release branches from other branches:
-
- lang=none
- releases/%Y-%M-%d-%v
- => releases/2012-30-16-rHERGE32cd512a52b7
-
-Include a second hierarchy if you share your repository with other products:
-
- lang=none
- releases/%P/%p-release-%Y%m%d-%V
- => releases/Tintin/tintin-release-20121116-32cd512a52b7
-
-Keep your branch names simple, avoiding strange punctuation, most of which is
-forbidden or escaped anyway:
-
- lang=none, counterexample
- releases//..clown-releases..//`date --iso=seconds`-$(sudo halt)
-
-Include the date early in your template, in an order which sorts properly:
-
- lang=none
- releases/%Y%m%d-%v
- => releases/20121116-rHERGE32cd512a52b7 (good!)
-
- releases/%V-%m.%d.%Y
- => releases/32cd512a52b7-11.16.2012 (awful!)
-
-
-EOTEXT;
- }
-
-}
diff --git a/src/applications/releeph/controller/product/ReleephProductHistoryController.php b/src/applications/releeph/controller/product/ReleephProductHistoryController.php
deleted file mode 100644
index 12d0d0b5c1..0000000000
--- a/src/applications/releeph/controller/product/ReleephProductHistoryController.php
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-
-final class ReleephProductHistoryController extends ReleephProductController {
-
- public function shouldAllowPublic() {
- return true;
- }
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $request->getViewer();
- $id = $request->getURIData('projectID');
-
- $product = id(new ReleephProductQuery())
- ->setViewer($viewer)
- ->withIDs(array($id))
- ->executeOne();
- if (!$product) {
- return new Aphront404Response();
- }
- $this->setProduct($product);
-
- $timeline = $this->buildTransactionTimeline(
- $product,
- new ReleephProductTransactionQuery());
- $timeline->setShouldTerminate(true);
-
- $crumbs = $this->buildApplicationCrumbs();
- $crumbs->addTextCrumb(pht('History'));
- $crumbs->setBorder(true);
-
- $title = pht('Product History');
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($timeline);
- }
-
-}
diff --git a/src/applications/releeph/controller/product/ReleephProductListController.php b/src/applications/releeph/controller/product/ReleephProductListController.php
deleted file mode 100644
index 14cc964e03..0000000000
--- a/src/applications/releeph/controller/product/ReleephProductListController.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-
-final class ReleephProductListController extends ReleephController {
-
- public function shouldAllowPublic() {
- return true;
- }
-
- public function handleRequest(AphrontRequest $request) {
- $query_key = $request->getURIData('queryKey');
- $controller = id(new PhabricatorApplicationSearchController())
- ->setQueryKey($query_key)
- ->setSearchEngine(new ReleephProductSearchEngine())
- ->setNavigation($this->buildSideNavView());
-
- return $this->delegateToController($controller);
- }
-
- protected function buildApplicationCrumbs() {
- $crumbs = parent::buildApplicationCrumbs();
-
- $crumbs->addAction(
- id(new PHUIListItemView())
- ->setName(pht('Create Product'))
- ->setHref($this->getApplicationURI('product/create/'))
- ->setIcon('fa-plus-square'));
-
- return $crumbs;
- }
-
-}
diff --git a/src/applications/releeph/controller/product/ReleephProductViewController.php b/src/applications/releeph/controller/product/ReleephProductViewController.php
deleted file mode 100644
index 963710c3ec..0000000000
--- a/src/applications/releeph/controller/product/ReleephProductViewController.php
+++ /dev/null
@@ -1,153 +0,0 @@
-<?php
-
-final class ReleephProductViewController extends ReleephProductController {
-
- public function shouldAllowPublic() {
- return true;
- }
-
- public function handleRequest(AphrontRequest $request) {
- $id = $request->getURIData('projectID');
- $query_key = $request->getURIData('queryKey');
- $viewer = $request->getViewer();
-
- $product = id(new ReleephProductQuery())
- ->setViewer($viewer)
- ->withIDs(array($id))
- ->executeOne();
- if (!$product) {
- return new Aphront404Response();
- }
- $this->setProduct($product);
-
- $controller = id(new PhabricatorApplicationSearchController())
- ->setQueryKey($query_key)
- ->setPreface($this->renderPreface())
- ->setSearchEngine(
- id(new ReleephBranchSearchEngine())
- ->setProduct($product))
- ->setNavigation($this->buildSideNavView());
-
- return $this->delegateToController($controller);
- }
-
- public function buildSideNavView($for_app = false) {
- $viewer = $this->getRequest()->getUser();
- $product = $this->getProduct();
-
- $nav = new AphrontSideNavFilterView();
- $nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
-
- if ($for_app) {
- $nav->addFilter('product/create/', pht('Create Product'));
- }
-
- id(new ReleephBranchSearchEngine())
- ->setProduct($product)
- ->setViewer($viewer)
- ->addNavigationItems($nav->getMenu());
-
- $nav->selectFilter(null);
-
- return $nav;
- }
-
- protected function buildApplicationCrumbs() {
- $crumbs = parent::buildApplicationCrumbs();
-
- $product = $this->getProduct();
- if ($product) {
- $crumbs->addAction(
- id(new PHUIListItemView())
- ->setHref($product->getURI('cutbranch/'))
- ->setName(pht('Cut New Branch'))
- ->setIcon('fa-plus'));
- }
-
- return $crumbs;
- }
-
- private function renderPreface() {
- $viewer = $this->getRequest()->getUser();
- $product = $this->getProduct();
-
- $id = $product->getID();
-
- $header = id(new PHUIHeaderView())
- ->setHeader($product->getName())
- ->setUser($viewer)
- ->setPolicyObject($product);
-
- if ($product->getIsActive()) {
- $header->setStatus('fa-check', 'bluegrey', pht('Active'));
- } else {
- $header->setStatus('fa-ban', 'dark', pht('Inactive'));
- }
-
- $actions = id(new PhabricatorActionListView())
- ->setUser($viewer)
- ->setObject($product);
-
- $can_edit = PhabricatorPolicyFilter::hasCapability(
- $viewer,
- $product,
- PhabricatorPolicyCapability::CAN_EDIT);
-
- $edit_uri = $this->getApplicationURI("product/{$id}/edit/");
- $history_uri = $this->getApplicationURI("product/{$id}/history/");
-
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Edit Product'))
- ->setHref($edit_uri)
- ->setIcon('fa-pencil')
- ->setDisabled(!$can_edit)
- ->setWorkflow(!$can_edit));
-
- if ($product->getIsActive()) {
- $status_name = pht('Deactivate Product');
- $status_href = "product/{$id}/action/deactivate/";
- $status_icon = 'fa-times';
- } else {
- $status_name = pht('Reactivate Product');
- $status_href = "product/{$id}/action/activate/";
- $status_icon = 'fa-plus-circle-o';
- }
-
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName($status_name)
- ->setHref($this->getApplicationURI($status_href))
- ->setIcon($status_icon)
- ->setDisabled(!$can_edit)
- ->setWorkflow(true));
-
- $actions->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('View History'))
- ->setHref($history_uri)
- ->setIcon('fa-list'));
-
- $properties = id(new PHUIPropertyListView())
- ->setUser($viewer)
- ->setObject($product);
-
- $properties->addProperty(
- pht('Repository'),
- $product->getRepository()->getName());
-
- $properties->setActionList($actions);
-
- $pushers = $product->getPushers();
- if ($pushers) {
- $properties->addProperty(
- pht('Pushers'),
- $viewer->renderHandleList($pushers));
- }
-
- return id(new PHUIObjectBoxView())
- ->setHeader($header)
- ->addPropertyList($properties);
- }
-
-}
diff --git a/src/applications/releeph/controller/request/ReleephRequestActionController.php b/src/applications/releeph/controller/request/ReleephRequestActionController.php
deleted file mode 100644
index 1a53b08b8c..0000000000
--- a/src/applications/releeph/controller/request/ReleephRequestActionController.php
+++ /dev/null
@@ -1,122 +0,0 @@
-<?php
-
-final class ReleephRequestActionController
- extends ReleephRequestController {
-
- public function handleRequest(AphrontRequest $request) {
- $action = $request->getURIData('action');
- $id = $request->getURIData('requestID');
- $viewer = $request->getViewer();
-
- $request->validateCSRF();
-
- $pull = id(new ReleephRequestQuery())
- ->setViewer($viewer)
- ->withIDs(array($id))
- ->executeOne();
- if (!$pull) {
- return new Aphront404Response();
- }
-
- $branch = $pull->getBranch();
- $product = $branch->getProduct();
- $origin_uri = '/'.$pull->getMonogram();
-
- $editor = id(new ReleephRequestTransactionalEditor())
- ->setActor($viewer)
- ->setContinueOnNoEffect(true)
- ->setContentSourceFromRequest($request);
-
- $xactions = array();
-
- switch ($action) {
- case 'want':
- case 'pass':
- static $action_map = array(
- 'want' => ReleephRequest::INTENT_WANT,
- 'pass' => ReleephRequest::INTENT_PASS,
- );
- $intent = $action_map[$action];
- $xactions[] = id(new ReleephRequestTransaction())
- ->setTransactionType(ReleephRequestTransaction::TYPE_USER_INTENT)
- ->setMetadataValue(
- 'isAuthoritative',
- $product->isAuthoritative($viewer))
- ->setNewValue($intent);
- break;
-
- case 'mark-manually-picked':
- case 'mark-manually-reverted':
- if (
- $pull->getRequestUserPHID() === $viewer->getPHID() ||
- $product->isAuthoritative($viewer)) {
-
- // We're all good!
- } else {
- throw new Exception(
- pht(
- "Bug! Only pushers or the requestor can manually change a ".
- "request's in-branch status!"));
- }
-
- if ($action === 'mark-manually-picked') {
- $in_branch = 1;
- $intent = ReleephRequest::INTENT_WANT;
- } else {
- $in_branch = 0;
- $intent = ReleephRequest::INTENT_PASS;
- }
-
- $xactions[] = id(new ReleephRequestTransaction())
- ->setTransactionType(ReleephRequestTransaction::TYPE_USER_INTENT)
- ->setMetadataValue('isManual', true)
- ->setMetadataValue('isAuthoritative', true)
- ->setNewValue($intent);
-
- $xactions[] = id(new ReleephRequestTransaction())
- ->setTransactionType(ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH)
- ->setNewValue($in_branch);
-
- break;
-
- default:
- throw new Exception(
- pht('Unknown or unimplemented action %s.', $action));
- }
-
- $editor->applyTransactions($pull, $xactions);
-
- if ($request->getBool('render')) {
- $field_list = PhabricatorCustomField::getObjectFields(
- $pull,
- PhabricatorCustomField::ROLE_VIEW);
-
- $field_list
- ->setViewer($viewer)
- ->readFieldsFromStorage($pull);
-
- // TODO: This should be more modern and general.
- $engine = id(new PhabricatorMarkupEngine())
- ->setViewer($viewer);
- foreach ($field_list->getFields() as $field) {
- if ($field->shouldMarkup()) {
- $field->setMarkupEngine($engine);
- }
- }
- $engine->process();
-
- $pull_box = id(new ReleephRequestView())
- ->setUser($viewer)
- ->setCustomFields($field_list)
- ->setPullRequest($pull)
- ->setIsListView(true);
-
- return id(new AphrontAjaxResponse())->setContent(
- array(
- 'markup' => hsprintf('%s', $pull_box),
- ));
- }
-
- return id(new AphrontRedirectResponse())->setURI($origin_uri);
- }
-}
diff --git a/src/applications/releeph/controller/request/ReleephRequestCommentController.php b/src/applications/releeph/controller/request/ReleephRequestCommentController.php
deleted file mode 100644
index 96500e8bc5..0000000000
--- a/src/applications/releeph/controller/request/ReleephRequestCommentController.php
+++ /dev/null
@@ -1,64 +0,0 @@
-<?php
-
-final class ReleephRequestCommentController
- extends ReleephRequestController {
-
- public function handleRequest(AphrontRequest $request) {
- $id = $request->getURIData('requestID');
- $viewer = $request->getViewer();
-
- if (!$request->isFormPost()) {
- return new Aphront400Response();
- }
-
- $pull = id(new ReleephRequestQuery())
- ->setViewer($viewer)
- ->withIDs(array($id))
- ->executeOne();
- if (!$pull) {
- return new Aphront404Response();
- }
-
- $is_preview = $request->isPreviewRequest();
- $draft = PhabricatorDraft::buildFromRequest($request);
-
- $view_uri = $this->getApplicationURI('/'.$pull->getMonogram());
-
- $xactions = array();
- $xactions[] = id(new ReleephRequestTransaction())
- ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
- ->attachComment(
- id(new ReleephRequestTransactionComment())
- ->setContent($request->getStr('comment')));
-
- $editor = id(new ReleephRequestTransactionalEditor())
- ->setActor($viewer)
- ->setContinueOnNoEffect($request->isContinueRequest())
- ->setContentSourceFromRequest($request)
- ->setIsPreview($is_preview);
-
- try {
- $xactions = $editor->applyTransactions($pull, $xactions);
- } catch (PhabricatorApplicationTransactionNoEffectException $ex) {
- return id(new PhabricatorApplicationTransactionNoEffectResponse())
- ->setCancelURI($view_uri)
- ->setException($ex);
- }
-
- if ($draft) {
- $draft->replaceOrDelete();
- }
-
- if ($request->isAjax() && $is_preview) {
- return id(new PhabricatorApplicationTransactionResponse())
- ->setObject($pull)
- ->setViewer($viewer)
- ->setTransactions($xactions)
- ->setIsPreview($is_preview);
- } else {
- return id(new AphrontRedirectResponse())
- ->setURI($view_uri);
- }
- }
-
-}
diff --git a/src/applications/releeph/controller/request/ReleephRequestController.php b/src/applications/releeph/controller/request/ReleephRequestController.php
deleted file mode 100644
index 98b0bbab60..0000000000
--- a/src/applications/releeph/controller/request/ReleephRequestController.php
+++ /dev/null
@@ -1,3 +0,0 @@
-<?php
-
-abstract class ReleephRequestController extends ReleephController {}
diff --git a/src/applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php b/src/applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php
deleted file mode 100644
index 1835e6f7f9..0000000000
--- a/src/applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php
+++ /dev/null
@@ -1,102 +0,0 @@
-<?php
-
-// TODO: After T2222, this is likely unreachable?
-
-final class ReleephRequestDifferentialCreateController
- extends ReleephController {
-
- private $revision;
-
- public function handleRequest(AphrontRequest $request) {
- $revision_id = $request->getURIData('diffRevID');
- $viewer = $request->getViewer();
-
- $diff_rev = id(new DifferentialRevisionQuery())
- ->setViewer($viewer)
- ->withIDs(array($revision_id))
- ->executeOne();
- if (!$diff_rev) {
- return new Aphront404Response();
- }
- $this->revision = $diff_rev;
-
- $repository = $this->revision->getRepository();
-
- $projects = id(new ReleephProject())->loadAllWhere(
- 'repositoryPHID = %s AND isActive = 1',
- $repository->getPHID());
- if (!$projects) {
- throw new Exception(
- pht(
- "%s belongs to the '%s' repository, ".
- "which is not part of any Releeph project!",
- 'D'.$this->revision->getID(),
- $repository->getMonogram()));
- }
-
- $branches = id(new ReleephBranch())->loadAllWhere(
- 'releephProjectID IN (%Ld) AND isActive = 1',
- mpull($projects, 'getID'));
- if (!$branches) {
- throw new Exception(pht(
- '%s could be in the Releeph project(s) %s, '.
- 'but this project / none of these projects have open branches.',
- 'D'.$this->revision->getID(),
- implode(', ', mpull($projects, 'getName'))));
- }
-
- if (count($branches) === 1) {
- return id(new AphrontRedirectResponse())
- ->setURI($this->buildReleephRequestURI(head($branches)));
- }
-
- $projects = msort(
- mpull($projects, null, 'getID'),
- 'getName');
-
- $branch_groups = mgroup($branches, 'getReleephProjectID');
-
- require_celerity_resource('releeph-request-differential-create-dialog');
- $dialog = id(new AphrontDialogView())
- ->setUser($viewer)
- ->setTitle(pht('Choose Releeph Branch'))
- ->setClass('releeph-request-differential-create-dialog')
- ->addCancelButton('/D'.$request->getStr('D'));
-
- $dialog->appendChild(
- pht(
- 'This differential revision changes code that is associated '.
- 'with multiple Releeph branches. Please select the branch '.
- 'where you would like this code to be picked.'));
-
- foreach ($branch_groups as $project_id => $branches) {
- $project = idx($projects, $project_id);
- $dialog->appendChild(
- phutil_tag(
- 'h1',
- array(),
- $project->getName()));
- $branches = msort($branches, 'getBasename');
- foreach ($branches as $branch) {
- $uri = $this->buildReleephRequestURI($branch);
- $dialog->appendChild(
- phutil_tag(
- 'a',
- array(
- 'href' => $uri,
- ),
- $branch->getDisplayNameWithDetail()));
- }
- }
-
- return id(new AphrontDialogResponse())
- ->setDialog($dialog);
- }
-
- private function buildReleephRequestURI(ReleephBranch $branch) {
- $uri = $branch->getURI('request/');
- return id(new PhutilURI($uri))
- ->replaceQueryParam('D', $this->revision->getID());
- }
-
-}
diff --git a/src/applications/releeph/controller/request/ReleephRequestEditController.php b/src/applications/releeph/controller/request/ReleephRequestEditController.php
deleted file mode 100644
index af7adc2c83..0000000000
--- a/src/applications/releeph/controller/request/ReleephRequestEditController.php
+++ /dev/null
@@ -1,320 +0,0 @@
-<?php
-
-final class ReleephRequestEditController extends ReleephBranchController {
-
- public function handleRequest(AphrontRequest $request) {
- $action = $request->getURIData('action');
- $request_id = $request->getURIData('requestID');
- $branch_id = $request->getURIData('branchID');
- $viewer = $request->getViewer();
-
- if ($request_id) {
- $pull = id(new ReleephRequestQuery())
- ->setViewer($viewer)
- ->withIDs(array($request_id))
- ->requireCapabilities(
- array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- ))
- ->executeOne();
- if (!$pull) {
- return new Aphront404Response();
- }
-
- $branch = $pull->getBranch();
-
- $is_edit = true;
- } else {
- $branch = id(new ReleephBranchQuery())
- ->setViewer($viewer)
- ->withIDs(array($branch_id))
- ->executeOne();
- if (!$branch) {
- return new Aphront404Response();
- }
-
- $pull = id(new ReleephRequest())
- ->setRequestUserPHID($viewer->getPHID())
- ->setBranchID($branch->getID())
- ->setInBranch(0)
- ->attachBranch($branch);
-
- $is_edit = false;
- }
- $this->setBranch($branch);
-
- $product = $branch->getProduct();
-
- $request_identifier = $request->getStr('requestIdentifierRaw');
- $e_request_identifier = true;
-
- // Load all the ReleephFieldSpecifications
- $selector = $branch->getProduct()->getReleephFieldSelector();
- $fields = $selector->getFieldSpecifications();
- foreach ($fields as $field) {
- $field
- ->setReleephProject($product)
- ->setReleephBranch($branch)
- ->setReleephRequest($pull);
- }
-
- $field_list = PhabricatorCustomField::getObjectFields(
- $pull,
- PhabricatorCustomField::ROLE_EDIT);
- foreach ($field_list->getFields() as $field) {
- $field
- ->setReleephProject($product)
- ->setReleephBranch($branch)
- ->setReleephRequest($pull);
- }
- $field_list->readFieldsFromStorage($pull);
-
-
- if ($branch_id) {
- $cancel_uri = $this->getApplicationURI('branch/'.$branch_id.'/');
- } else {
- $cancel_uri = '/'.$pull->getMonogram();
- }
-
- // Make edits
- $errors = array();
- if ($request->isFormPost()) {
- $xactions = array();
-
- // The commit-identifier being requested...
- if (!$is_edit) {
- if ($request_identifier ===
- ReleephRequestTypeaheadControl::PLACEHOLDER) {
-
- $errors[] = pht('No commit ID was provided.');
- $e_request_identifier = pht('Required');
- } else {
- $pr_commit = null;
- $finder = id(new ReleephCommitFinder())
- ->setUser($viewer)
- ->setReleephProject($product);
- try {
- $pr_commit = $finder->fromPartial($request_identifier);
- } catch (Exception $e) {
- $e_request_identifier = pht('Invalid');
- $errors[] = pht(
- 'Request %s is probably not a valid commit.',
- $request_identifier);
- $errors[] = $e->getMessage();
- }
-
- if (!$errors) {
- $object_phid = $finder->getRequestedObjectPHID();
- if (!$object_phid) {
- $object_phid = $pr_commit->getPHID();
- }
-
- $pull->setRequestedObjectPHID($object_phid);
- }
- }
-
- if (!$errors) {
- $existing = id(new ReleephRequest())
- ->loadOneWhere('requestCommitPHID = %s AND branchID = %d',
- $pr_commit->getPHID(), $branch->getID());
- if ($existing) {
- return id(new AphrontRedirectResponse())
- ->setURI('/releeph/request/edit/'.$existing->getID().
- '?existing=1');
- }
-
- $xactions[] = id(new ReleephRequestTransaction())
- ->setTransactionType(ReleephRequestTransaction::TYPE_REQUEST)
- ->setNewValue($pr_commit->getPHID());
-
- $xactions[] = id(new ReleephRequestTransaction())
- ->setTransactionType(ReleephRequestTransaction::TYPE_USER_INTENT)
- // To help hide these implicit intents...
- ->setMetadataValue('isRQCreate', true)
- ->setMetadataValue('userPHID', $viewer->getPHID())
- ->setMetadataValue(
- 'isAuthoritative',
- $product->isAuthoritative($viewer))
- ->setNewValue(ReleephRequest::INTENT_WANT);
- }
- }
-
- // TODO: This should happen implicitly while building transactions
- // instead.
- foreach ($field_list->getFields() as $field) {
- $field->readValueFromRequest($request);
- }
-
- if (!$errors) {
- foreach ($fields as $field) {
- if ($field->isEditable()) {
- try {
- $data = $request->getRequestData();
- $value = idx($data, $field->getRequiredStorageKey());
- $field->validate($value);
- $xactions[] = id(new ReleephRequestTransaction())
- ->setTransactionType(ReleephRequestTransaction::TYPE_EDIT_FIELD)
- ->setMetadataValue('fieldClass', get_class($field))
- ->setNewValue($value);
- } catch (ReleephFieldParseException $ex) {
- $errors[] = $ex->getMessage();
- }
- }
- }
- }
-
- if (!$errors) {
- $editor = id(new ReleephRequestTransactionalEditor())
- ->setActor($viewer)
- ->setContinueOnNoEffect(true)
- ->setContentSourceFromRequest($request);
- $editor->applyTransactions($pull, $xactions);
- return id(new AphrontRedirectResponse())->setURI($cancel_uri);
- }
- }
-
- $handle_phids = array(
- $pull->getRequestUserPHID(),
- $pull->getRequestCommitPHID(),
- );
- $handle_phids = array_filter($handle_phids);
- if ($handle_phids) {
- $handles = id(new PhabricatorHandleQuery())
- ->setViewer($viewer)
- ->withPHIDs($handle_phids)
- ->execute();
- } else {
- $handles = array();
- }
-
- $age_string = '';
- if ($is_edit) {
- $age_string = phutil_format_relative_time(
- time() - $pull->getDateCreated()).' ago';
- }
-
- // Warn the user if we've been redirected here because we tried to
- // re-request something.
- $notice_view = null;
- if ($request->getInt('existing')) {
- $notice_messages = array(
- pht('You are editing an existing pick request!'),
- pht(
- 'Requested %s by %s',
- $age_string,
- $handles[$pull->getRequestUserPHID()]->renderLink()),
- );
- $notice_view = id(new PHUIInfoView())
- ->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
- ->setErrors($notice_messages);
- }
-
- $form = id(new AphrontFormView())
- ->setUser($viewer);
-
- if ($is_edit) {
- $form
- ->appendChild(
- id(new AphrontFormMarkupControl())
- ->setLabel(pht('Original Commit'))
- ->setValue(
- $handles[$pull->getRequestCommitPHID()]->renderLink()))
- ->appendChild(
- id(new AphrontFormMarkupControl())
- ->setLabel(pht('Requestor'))
- ->setValue(hsprintf(
- '%s %s',
- $handles[$pull->getRequestUserPHID()]->renderLink(),
- $age_string)));
- } else {
- $origin = null;
- $diff_rev_id = $request->getStr('D');
- if ($diff_rev_id) {
- $diff_rev = id(new DifferentialRevisionQuery())
- ->setViewer($viewer)
- ->withIDs(array($diff_rev_id))
- ->executeOne();
- $origin = '/D'.$diff_rev->getID();
- $title = sprintf(
- 'D%d: %s',
- $diff_rev_id,
- $diff_rev->getTitle());
- $form
- ->addHiddenInput('requestIdentifierRaw', 'D'.$diff_rev_id)
- ->appendChild(
- id(new AphrontFormStaticControl())
- ->setLabel(pht('Diff'))
- ->setValue($title));
- } else {
- $origin = $branch->getURI();
- $repo = $product->getRepository();
- $branch_cut_point = id(new PhabricatorRepositoryCommit())
- ->loadOneWhere(
- 'phid = %s',
- $branch->getCutPointCommitPHID());
- $form->appendChild(
- id(new ReleephRequestTypeaheadControl())
- ->setName('requestIdentifierRaw')
- ->setLabel(pht('Commit ID'))
- ->setRepo($repo)
- ->setValue($request_identifier)
- ->setError($e_request_identifier)
- ->setStartTime($branch_cut_point->getEpoch())
- ->setCaption(
- pht(
- 'Start typing to autocomplete on commit title, '.
- 'or give a Phabricator commit identifier like rFOO1234.')));
- }
- }
-
- $field_list->appendFieldsToForm($form);
-
- $crumbs = $this->buildApplicationCrumbs();
-
- if ($is_edit) {
- $title = pht('Edit Pull Request');
- $submit_name = pht('Save');
- $header_icon = 'fa-pencil';
-
- $crumbs->addTextCrumb($pull->getMonogram(), '/'.$pull->getMonogram());
- $crumbs->addTextCrumb(pht('Edit'));
- } else {
- $title = pht('Create Pull Request');
- $submit_name = pht('Create Pull Request');
- $header_icon = 'fa-plus-square';
-
- $crumbs->addTextCrumb(pht('New Pull Request'));
- }
-
- $form->appendChild(
- id(new AphrontFormSubmitControl())
- ->addCancelButton($cancel_uri, pht('Cancel'))
- ->setValue($submit_name));
-
- $box = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Request'))
- ->setFormErrors($errors)
- ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
- ->appendChild($form);
-
- $crumbs->setBorder(true);
-
- $header = id(new PHUIHeaderView())
- ->setHeader($title)
- ->setHeaderIcon($header_icon);
-
- $view = id(new PHUITwoColumnView())
- ->setHeader($header)
- ->setFooter(array(
- $notice_view,
- $box,
- ));
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($view);
-
- }
-}
diff --git a/src/applications/releeph/controller/request/ReleephRequestTypeaheadController.php b/src/applications/releeph/controller/request/ReleephRequestTypeaheadController.php
deleted file mode 100644
index 3a93de7453..0000000000
--- a/src/applications/releeph/controller/request/ReleephRequestTypeaheadController.php
+++ /dev/null
@@ -1,90 +0,0 @@
-<?php
-
-final class ReleephRequestTypeaheadController
- extends PhabricatorTypeaheadDatasourceController {
-
- public function handleRequest(AphrontRequest $request) {
- $query = $request->getStr('q');
- $repo_id = $request->getInt('repo');
- $since = $request->getInt('since');
- $limit = $request->getInt('limit');
-
- $now = time();
- $data = array();
-
- // Dummy instances used for getting connections, table names, etc.
- $pr_commit = new PhabricatorRepositoryCommit();
- $pr_commit_data = new PhabricatorRepositoryCommitData();
-
- $conn = $pr_commit->establishConnection('r');
-
- $rows = queryfx_all(
- $conn,
- 'SELECT
- rc.phid as commitPHID,
- rc.authorPHID,
- rcd.authorName,
- SUBSTRING(rcd.commitMessage, 1, 100) AS shortMessage,
- rc.commitIdentifier,
- rc.epoch
- FROM %T rc
- INNER JOIN %T rcd ON rcd.commitID = rc.id
- WHERE repositoryID = %d
- AND rc.epoch >= %d
- AND (
- rcd.commitMessage LIKE %~
- OR
- rc.commitIdentifier LIKE %~
- )
- ORDER BY rc.epoch DESC
- LIMIT %d',
- $pr_commit->getTableName(),
- $pr_commit_data->getTableName(),
- $repo_id,
- $since,
- $query,
- $query,
- $limit);
-
- foreach ($rows as $row) {
- $full_commit_id = $row['commitIdentifier'];
- $short_commit_id = substr($full_commit_id, 0, 12);
- $first_line = $this->getFirstLine($row['shortMessage']);
- $data[] = array(
- $full_commit_id,
- $short_commit_id,
- $row['authorName'],
- phutil_format_relative_time($now - $row['epoch']),
- $first_line,
- );
- }
-
- return id(new AphrontAjaxResponse())
- ->setContent($data);
- }
-
- /**
- * Split either at the first new line, or a bunch of dashes.
- *
- * Really just a legacy from old Releeph Daemon commit messages where I used
- * to say:
- *
- * Commit of FOO for BAR
- * ------------
- * This does X Y Z
- *
- */
- private function getFirstLine($commit_message_fragment) {
- static $separators = array('-------', "\n");
- $string = ltrim($commit_message_fragment);
- $first_line = $string;
- foreach ($separators as $separator) {
- if ($pos = strpos($string, $separator)) {
- $first_line = substr($string, 0, $pos);
- break;
- }
- }
- return $first_line;
- }
-
-}
diff --git a/src/applications/releeph/controller/request/ReleephRequestViewController.php b/src/applications/releeph/controller/request/ReleephRequestViewController.php
deleted file mode 100644
index c404e31579..0000000000
--- a/src/applications/releeph/controller/request/ReleephRequestViewController.php
+++ /dev/null
@@ -1,101 +0,0 @@
-<?php
-
-final class ReleephRequestViewController
- extends ReleephBranchController {
-
- public function handleRequest(AphrontRequest $request) {
- $id = $request->getURIData('requestID');
- $viewer = $request->getViewer();
-
- $pull = id(new ReleephRequestQuery())
- ->setViewer($viewer)
- ->withIDs(array($id))
- ->executeOne();
- if (!$pull) {
- return new Aphront404Response();
- }
- $this->setBranch($pull->getBranch());
-
- // Redirect older URIs to new "Y" URIs.
- // TODO: Get rid of this eventually.
- $actual_path = $request->getRequestURI()->getPath();
- $expect_path = '/'.$pull->getMonogram();
- if ($actual_path != $expect_path) {
- return id(new AphrontRedirectResponse())->setURI($expect_path);
- }
-
- // TODO: Break this 1:1 stuff?
- $branch = $pull->getBranch();
-
- $field_list = PhabricatorCustomField::getObjectFields(
- $pull,
- PhabricatorCustomField::ROLE_VIEW);
-
- $field_list
- ->setViewer($viewer)
- ->readFieldsFromStorage($pull);
-
- // TODO: This should be more modern and general.
- $engine = id(new PhabricatorMarkupEngine())
- ->setViewer($viewer);
- foreach ($field_list->getFields() as $field) {
- if ($field->shouldMarkup()) {
- $field->setMarkupEngine($engine);
- }
- }
- $engine->process();
-
- $pull_box = id(new ReleephRequestView())
- ->setUser($viewer)
- ->setCustomFields($field_list)
- ->setPullRequest($pull);
-
- $timeline = $this->buildTransactionTimeline(
- $pull,
- new ReleephRequestTransactionQuery());
-
- $add_comment_header = pht('Plea or Yield');
-
- $draft = PhabricatorDraft::newFromUserAndKey(
- $viewer,
- $pull->getPHID());
-
- $title = hsprintf(
- '%s %s',
- $pull->getMonogram(),
- $pull->getSummaryForDisplay());
-
- $add_comment_form = id(new PhabricatorApplicationTransactionCommentView())
- ->setUser($viewer)
- ->setObjectPHID($pull->getPHID())
- ->setDraft($draft)
- ->setHeaderText($add_comment_header)
- ->setAction($this->getApplicationURI(
- '/request/comment/'.$pull->getID().'/'))
- ->setSubmitButtonName(pht('Comment'));
-
- $crumbs = $this->buildApplicationCrumbs();
- $crumbs->addTextCrumb($pull->getMonogram(), '/'.$pull->getMonogram());
- $crumbs->setBorder(true);
-
- $header = id(new PHUIHeaderView())
- ->setHeader($title)
- ->setHeaderIcon('fa-flag-checkered');
-
- $view = id(new PHUITwoColumnView())
- ->setHeader($header)
- ->setFooter(array(
- $pull_box,
- $timeline,
- $add_comment_form,
- ));
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($view);
-
- }
-
-
-}
diff --git a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php
deleted file mode 100644
index 56d371f1d6..0000000000
--- a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php
+++ /dev/null
@@ -1,384 +0,0 @@
-<?php
-
-/**
- * This DifferentialFieldSpecification exists for two reason:
- *
- * 1: To parse "Releeph: picks RQ<nn>" headers in commits created by
- * arc-releeph so that RQs committed by arc-releeph have real
- * PhabricatorRepositoryCommits associated with them (instead of just the SHA
- * of the commit, as seen by the pusher).
- *
- * 2: If requestors want to commit directly to their release branch, they can
- * use this header to (i) indicate on a differential revision that this
- * differential revision is for the release branch, and (ii) when they land
- * their diff on to the release branch manually, the ReleephRequest is
- * automatically updated (instead of having to use the "Mark Manually Picked"
- * button.)
- *
- */
-final class DifferentialReleephRequestFieldSpecification extends Phobject {
-
- // TODO: This class is essentially dead right now, see T2222.
-
- const ACTION_PICKS = 'picks';
- const ACTION_REVERTS = 'reverts';
-
- private $releephAction;
- private $releephPHIDs = array();
-
- public function getStorageKey() {
- return 'releeph:actions';
- }
-
- public function getValueForStorage() {
- return json_encode(array(
- 'releephAction' => $this->releephAction,
- 'releephPHIDs' => $this->releephPHIDs,
- ));
- }
-
- public function setValueFromStorage($json) {
- if ($json) {
- $dict = phutil_json_decode($json);
- $this->releephAction = idx($dict, 'releephAction');
- $this->releephPHIDs = idx($dict, 'releephPHIDs');
- }
- return $this;
- }
-
- public function shouldAppearOnRevisionView() {
- return true;
- }
-
- public function renderLabelForRevisionView() {
- return pht('Releeph');
- }
-
- public function getRequiredHandlePHIDs() {
- return mpull($this->loadReleephRequests(), 'getPHID');
- }
-
- public function renderValueForRevisionView() {
- static $tense;
-
- if ($tense === null) {
- $tense = array(
- self::ACTION_PICKS => array(
- 'future' => pht('Will pick'),
- 'past' => pht('Picked'),
- ),
- self::ACTION_REVERTS => array(
- 'future' => pht('Will revert'),
- 'past' => pht('Reverted'),
- ),
- );
- }
-
- $releeph_requests = $this->loadReleephRequests();
- if (!$releeph_requests) {
- return null;
- }
-
- if ($this->getRevision()->isClosed()) {
- $verb = $tense[$this->releephAction]['past'];
- } else {
- $verb = $tense[$this->releephAction]['future'];
- }
-
- $parts = hsprintf('%s...', $verb);
- foreach ($releeph_requests as $releeph_request) {
- $parts->appendHTML(phutil_tag('br'));
- $parts->appendHTML(
- $this->getHandle($releeph_request->getPHID())->renderLink());
- }
-
- return $parts;
- }
-
- public function shouldAppearOnCommitMessage() {
- return true;
- }
-
- public function getCommitMessageKey() {
- return 'releephActions';
- }
-
- public function setValueFromParsedCommitMessage($dict) {
- $this->releephAction = $dict['releephAction'];
- $this->releephPHIDs = $dict['releephPHIDs'];
- return $this;
- }
-
- public function renderValueForCommitMessage($is_edit) {
- $releeph_requests = $this->loadReleephRequests();
- if (!$releeph_requests) {
- return null;
- }
-
- $parts = array($this->releephAction);
- foreach ($releeph_requests as $releeph_request) {
- $parts[] = 'RQ'.$releeph_request->getID();
- }
-
- return implode(' ', $parts);
- }
-
- /**
- * Releeph fields should look like:
- *
- * Releeph: picks RQ1 RQ2, RQ3
- * Releeph: reverts RQ1
- */
- public function parseValueFromCommitMessage($value) {
- /**
- * Releeph commit messages look like this (but with more blank lines,
- * omitted here):
- *
- * Make CaptainHaddock more reasonable
- * Releeph: picks RQ1
- * Requested By: edward
- * Approved By: edward (requestor)
- * Request Reason: x
- * Summary: Make the Haddock implementation more reasonable.
- * Test Plan: none
- * Reviewers: user1
- *
- * Some of these fields are recognized by Differential (e.g. "Requested
- * By"). They are folded up into the "Releeph" field, parsed by this
- * class. As such $value includes more than just the first-line:
- *
- * "picks RQ1\n\nRequested By: edward\n\nApproved By: edward (requestor)"
- *
- * To hack around this, just consider the first line of $value when
- * determining what Releeph actions the parsed commit is performing.
- */
- $first_line = head(array_filter(explode("\n", $value)));
-
- $tokens = preg_split('/\s*,?\s+/', $first_line);
- $raw_action = array_shift($tokens);
- $action = strtolower($raw_action);
-
- if (!$action) {
- return null;
- }
-
- switch ($action) {
- case self::ACTION_REVERTS:
- case self::ACTION_PICKS:
- break;
-
- default:
- throw new DifferentialFieldParseException(
- pht(
- "Commit message contains unknown Releeph action '%s'!",
- $raw_action));
- break;
- }
-
- $releeph_requests = array();
- foreach ($tokens as $token) {
- $match = array();
- if (!preg_match('/^(?:RQ)?(\d+)$/i', $token, $match)) {
- $label = $this->renderLabelForCommitMessage();
- throw new DifferentialFieldParseException(
- pht(
- "Commit message contains unparseable ".
- "Releeph request token '%s'!",
- $token));
- }
-
- $id = (int)$match[1];
- $releeph_request = id(new ReleephRequest())->load($id);
-
- if (!$releeph_request) {
- throw new DifferentialFieldParseException(
- pht(
- 'Commit message references non existent Releeph request: %s!',
- $value));
- }
-
- $releeph_requests[] = $releeph_request;
- }
-
- if (count($releeph_requests) > 1) {
- $rqs_seen = array();
- $groups = array();
- foreach ($releeph_requests as $releeph_request) {
- $releeph_branch = $releeph_request->getBranch();
- $branch_name = $releeph_branch->getName();
- $rq_id = 'RQ'.$releeph_request->getID();
-
- if (idx($rqs_seen, $rq_id)) {
- throw new DifferentialFieldParseException(
- pht(
- 'Commit message refers to %s multiple times!',
- $rq_id));
- }
- $rqs_seen[$rq_id] = true;
-
- if (!isset($groups[$branch_name])) {
- $groups[$branch_name] = array();
- }
- $groups[$branch_name][] = $rq_id;
- }
-
- if (count($groups) > 1) {
- $lists = array();
- foreach ($groups as $branch_name => $rq_ids) {
- $lists[] = implode(', ', $rq_ids).' in '.$branch_name;
- }
- throw new DifferentialFieldParseException(
- pht(
- 'Commit message references multiple Releeph requests, '.
- 'but the requests are in different branches: %s',
- implode('; ', $lists)));
- }
- }
-
- $phids = mpull($releeph_requests, 'getPHID');
-
- $data = array(
- 'releephAction' => $action,
- 'releephPHIDs' => $phids,
- );
- return $data;
- }
-
- public function renderLabelForCommitMessage() {
- return pht('Releeph');
- }
-
- public function shouldAppearOnCommitMessageTemplate() {
- return false;
- }
-
- public function didParseCommit(
- PhabricatorRepository $repo,
- PhabricatorRepositoryCommit $commit,
- PhabricatorRepositoryCommitData $data) {
-
- // NOTE: This is currently dead code. See T2222.
-
- $releeph_requests = $this->loadReleephRequests();
-
- if (!$releeph_requests) {
- return;
- }
-
- $releeph_branch = head($releeph_requests)->getBranch();
- if (!$this->isCommitOnBranch($repo, $commit, $releeph_branch)) {
- return;
- }
-
- foreach ($releeph_requests as $releeph_request) {
- if ($this->releephAction === self::ACTION_PICKS) {
- $action = 'pick';
- } else {
- $action = 'revert';
- }
-
- $actor_phid = coalesce(
- $data->getCommitDetail('committerPHID'),
- $data->getCommitDetail('authorPHID'));
-
- $actor = id(new PhabricatorUser())
- ->loadOneWhere('phid = %s', $actor_phid);
-
- $xactions = array();
-
- $xactions[] = id(new ReleephRequestTransaction())
- ->setTransactionType(ReleephRequestTransaction::TYPE_DISCOVERY)
- ->setMetadataValue('action', $action)
- ->setMetadataValue('authorPHID',
- $data->getCommitDetail('authorPHID'))
- ->setMetadataValue('committerPHID',
- $data->getCommitDetail('committerPHID'))
- ->setNewValue($commit->getPHID());
-
- $editor = id(new ReleephRequestTransactionalEditor())
- ->setActor($actor)
- ->setContinueOnNoEffect(true)
- ->setContentSource(
- PhabricatorContentSource::newForSource(
- PhabricatorUnknownContentSource::SOURCECONST));
-
- $editor->applyTransactions($releeph_request, $xactions);
- }
- }
-
- private function loadReleephRequests() {
- if (!$this->releephPHIDs) {
- return array();
- }
-
- return id(new ReleephRequestQuery())
- ->setViewer($this->getViewer())
- ->withPHIDs($this->releephPHIDs)
- ->execute();
- }
-
- private function isCommitOnBranch(
- PhabricatorRepository $repo,
- PhabricatorRepositoryCommit $commit,
- ReleephBranch $releeph_branch) {
-
- switch ($repo->getVersionControlSystem()) {
- case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
- list($output) = $repo->execxLocalCommand(
- 'branch --all --no-color --contains %s',
- $commit->getCommitIdentifier());
-
- $remote_prefix = 'remotes/origin/';
- $branches = array();
- foreach (array_filter(explode("\n", $output)) as $line) {
- $tokens = explode(' ', $line);
- $ref = last($tokens);
- if (strncmp($ref, $remote_prefix, strlen($remote_prefix)) === 0) {
- $branch = substr($ref, strlen($remote_prefix));
- $branches[$branch] = $branch;
- }
- }
-
- return idx($branches, $releeph_branch->getName());
- break;
-
- case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
- $change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
- DiffusionRequest::newFromDictionary(array(
- 'user' => $this->getUser(),
- 'repository' => $repo,
- 'commit' => $commit->getCommitIdentifier(),
- )));
- $path_changes = $change_query->loadChanges();
- $commit_paths = mpull($path_changes, 'getPath');
-
- $branch_path = $releeph_branch->getName();
-
- $in_branch = array();
- $ex_branch = array();
- foreach ($commit_paths as $path) {
- if (strncmp($path, $branch_path, strlen($branch_path)) === 0) {
- $in_branch[] = $path;
- } else {
- $ex_branch[] = $path;
- }
- }
-
- if ($in_branch && $ex_branch) {
- $error = pht(
- 'CONFUSION: commit %s in %s contains %d path change(s) that were '.
- 'part of a Releeph branch, but also has %d path change(s) not '.
- 'part of a Releeph branch!',
- $commit->getCommitIdentifier(),
- $repo->getDisplayName(),
- count($in_branch),
- count($ex_branch));
- phlog($error);
- }
-
- return !empty($in_branch);
- break;
- }
- }
-
-}
diff --git a/src/applications/releeph/editor/ReleephBranchEditor.php b/src/applications/releeph/editor/ReleephBranchEditor.php
deleted file mode 100644
index 0bc084223b..0000000000
--- a/src/applications/releeph/editor/ReleephBranchEditor.php
+++ /dev/null
@@ -1,86 +0,0 @@
-<?php
-
-final class ReleephBranchEditor extends PhabricatorEditor {
-
- private $releephProject;
- private $releephBranch;
-
- public function setReleephProject(ReleephProject $rp) {
- $this->releephProject = $rp;
- return $this;
- }
-
- public function setReleephBranch(ReleephBranch $branch) {
- $this->releephBranch = $branch;
- return $this;
- }
-
- public function newBranchFromCommit(
- PhabricatorRepositoryCommit $cut_point,
- $branch_date,
- $symbolic_name = null) {
-
- $template = $this->releephProject->getDetail('branchTemplate');
- if (!$template) {
- $template = ReleephBranchTemplate::getRequiredDefaultTemplate();
- }
-
- $cut_point_handle = id(new PhabricatorHandleQuery())
- ->setViewer($this->requireActor())
- ->withPHIDs(array($cut_point->getPHID()))
- ->executeOne();
-
- list($name, $errors) = id(new ReleephBranchTemplate())
- ->setCommitHandle($cut_point_handle)
- ->setBranchDate($branch_date)
- ->setReleephProjectName($this->releephProject->getName())
- ->interpolate($template);
-
- $basename = last(explode('/', $name));
-
- $table = id(new ReleephBranch());
- $transaction = $table->openTransaction();
- $branch = id(new ReleephBranch())
- ->setName($name)
- ->setBasename($basename)
- ->setReleephProjectID($this->releephProject->getID())
- ->setCreatedByUserPHID($this->requireActor()->getPHID())
- ->setCutPointCommitPHID($cut_point->getPHID())
- ->setIsActive(1)
- ->setDetail('branchDate', $branch_date)
- ->save();
-
- /**
- * Steal the symbolic name from any other branch that has it (in this
- * project).
- */
- if ($symbolic_name) {
- $others = id(new ReleephBranch())->loadAllWhere(
- 'releephProjectID = %d',
- $this->releephProject->getID());
- foreach ($others as $other) {
- if ($other->getSymbolicName() == $symbolic_name) {
- $other
- ->setSymbolicName(null)
- ->save();
- }
- }
- $branch
- ->setSymbolicName($symbolic_name)
- ->save();
- }
-
- $table->saveTransaction();
- return $branch;
- }
-
- // aka "close" and "reopen"
- public function changeBranchAccess($is_active) {
- $branch = $this->releephBranch;
-
- $branch
- ->setIsActive((int)$is_active)
- ->save();
- }
-
-}
diff --git a/src/applications/releeph/editor/ReleephProductEditor.php b/src/applications/releeph/editor/ReleephProductEditor.php
deleted file mode 100644
index 4c3d69c97b..0000000000
--- a/src/applications/releeph/editor/ReleephProductEditor.php
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php
-
-final class ReleephProductEditor
- extends PhabricatorApplicationTransactionEditor {
-
- public function getEditorApplicationClass() {
- return 'PhabricatorReleephApplication';
- }
-
- public function getEditorObjectsDescription() {
- return pht('Releeph Products');
- }
-
- public function getTransactionTypes() {
- $types = parent::getTransactionTypes();
-
- $types[] = ReleephProductTransaction::TYPE_ACTIVE;
-
- return $types;
- }
-
- protected function getCustomTransactionOldValue(
- PhabricatorLiskDAO $object,
- PhabricatorApplicationTransaction $xaction) {
-
- switch ($xaction->getTransactionType()) {
- case ReleephProductTransaction::TYPE_ACTIVE:
- return (int)$object->getIsActive();
- }
- }
-
- protected function getCustomTransactionNewValue(
- PhabricatorLiskDAO $object,
- PhabricatorApplicationTransaction $xaction) {
-
- switch ($xaction->getTransactionType()) {
- case ReleephProductTransaction::TYPE_ACTIVE:
- return (int)$xaction->getNewValue();
- }
- }
-
- protected function applyCustomInternalTransaction(
- PhabricatorLiskDAO $object,
- PhabricatorApplicationTransaction $xaction) {
- $new = $xaction->getNewValue();
-
- switch ($xaction->getTransactionType()) {
- case ReleephProductTransaction::TYPE_ACTIVE:
- $object->setIsActive($new);
- break;
- }
- }
-
- protected function applyCustomExternalTransaction(
- PhabricatorLiskDAO $object,
- PhabricatorApplicationTransaction $xaction) {
-
- return;
- }
-
-}
diff --git a/src/applications/releeph/editor/ReleephRequestTransactionalEditor.php b/src/applications/releeph/editor/ReleephRequestTransactionalEditor.php
deleted file mode 100644
index da488d9c72..0000000000
--- a/src/applications/releeph/editor/ReleephRequestTransactionalEditor.php
+++ /dev/null
@@ -1,307 +0,0 @@
-<?php
-
-final class ReleephRequestTransactionalEditor
- extends PhabricatorApplicationTransactionEditor {
-
- public function getEditorApplicationClass() {
- return 'PhabricatorReleephApplication';
- }
-
- public function getEditorObjectsDescription() {
- return pht('Releeph Requests');
- }
-
- public function getTransactionTypes() {
- $types = parent::getTransactionTypes();
-
- $types[] = PhabricatorTransactions::TYPE_COMMENT;
- $types[] = ReleephRequestTransaction::TYPE_COMMIT;
- $types[] = ReleephRequestTransaction::TYPE_DISCOVERY;
- $types[] = ReleephRequestTransaction::TYPE_EDIT_FIELD;
- $types[] = ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH;
- $types[] = ReleephRequestTransaction::TYPE_PICK_STATUS;
- $types[] = ReleephRequestTransaction::TYPE_REQUEST;
- $types[] = ReleephRequestTransaction::TYPE_USER_INTENT;
-
- return $types;
- }
-
- protected function getCustomTransactionOldValue(
- PhabricatorLiskDAO $object,
- PhabricatorApplicationTransaction $xaction) {
-
- switch ($xaction->getTransactionType()) {
- case ReleephRequestTransaction::TYPE_REQUEST:
- return $object->getRequestCommitPHID();
-
- case ReleephRequestTransaction::TYPE_EDIT_FIELD:
- $field = newv($xaction->getMetadataValue('fieldClass'), array());
- $value = $field->setReleephRequest($object)->getValue();
- return $value;
-
- case ReleephRequestTransaction::TYPE_USER_INTENT:
- $user_phid = $xaction->getAuthorPHID();
- return idx($object->getUserIntents(), $user_phid);
-
- case ReleephRequestTransaction::TYPE_PICK_STATUS:
- return (int)$object->getPickStatus();
- break;
-
- case ReleephRequestTransaction::TYPE_COMMIT:
- return $object->getCommitIdentifier();
-
- case ReleephRequestTransaction::TYPE_DISCOVERY:
- return $object->getCommitPHID();
-
- case ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH:
- return $object->getInBranch();
- }
- }
-
- protected function getCustomTransactionNewValue(
- PhabricatorLiskDAO $object,
- PhabricatorApplicationTransaction $xaction) {
-
- switch ($xaction->getTransactionType()) {
- case ReleephRequestTransaction::TYPE_REQUEST:
- case ReleephRequestTransaction::TYPE_USER_INTENT:
- case ReleephRequestTransaction::TYPE_EDIT_FIELD:
- case ReleephRequestTransaction::TYPE_PICK_STATUS:
- case ReleephRequestTransaction::TYPE_COMMIT:
- case ReleephRequestTransaction::TYPE_DISCOVERY:
- case ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH:
- return $xaction->getNewValue();
- }
- }
-
- protected function applyCustomInternalTransaction(
- PhabricatorLiskDAO $object,
- PhabricatorApplicationTransaction $xaction) {
-
- $new = $xaction->getNewValue();
-
- switch ($xaction->getTransactionType()) {
- case ReleephRequestTransaction::TYPE_REQUEST:
- $object->setRequestCommitPHID($new);
- break;
-
- case ReleephRequestTransaction::TYPE_USER_INTENT:
- $user_phid = $xaction->getAuthorPHID();
- $intents = $object->getUserIntents();
- $intents[$user_phid] = $new;
- $object->setUserIntents($intents);
- break;
-
- case ReleephRequestTransaction::TYPE_EDIT_FIELD:
- $field = newv($xaction->getMetadataValue('fieldClass'), array());
- $field
- ->setReleephRequest($object)
- ->setValue($new);
- break;
-
- case ReleephRequestTransaction::TYPE_PICK_STATUS:
- $object->setPickStatus($new);
- break;
-
- case ReleephRequestTransaction::TYPE_COMMIT:
- $this->setInBranchFromAction($object, $xaction);
- $object->setCommitIdentifier($new);
- break;
-
- case ReleephRequestTransaction::TYPE_DISCOVERY:
- $this->setInBranchFromAction($object, $xaction);
- $object->setCommitPHID($new);
- break;
-
- case ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH:
- $object->setInBranch((int)$new);
- break;
- }
- }
-
- protected function applyCustomExternalTransaction(
- PhabricatorLiskDAO $object,
- PhabricatorApplicationTransaction $xaction) {
-
- return;
- }
-
- protected function filterTransactions(
- PhabricatorLiskDAO $object,
- array $xactions) {
-
- // Remove TYPE_DISCOVERY xactions that are the result of a reparse.
- $previously_discovered_commits = array();
- $discovery_xactions = idx(
- mgroup($xactions, 'getTransactionType'),
- ReleephRequestTransaction::TYPE_DISCOVERY);
- if ($discovery_xactions) {
- $previous_xactions = id(new ReleephRequestTransactionQuery())
- ->setViewer(PhabricatorUser::getOmnipotentUser())
- ->withObjectPHIDs(array($object->getPHID()))
- ->execute();
-
- foreach ($previous_xactions as $xaction) {
- if ($xaction->getTransactionType() ===
- ReleephRequestTransaction::TYPE_DISCOVERY) {
-
- $commit_phid = $xaction->getNewValue();
- $previously_discovered_commits[$commit_phid] = true;
- }
- }
- }
-
- foreach ($xactions as $key => $xaction) {
- if ($xaction->getTransactionType() ===
- ReleephRequestTransaction::TYPE_DISCOVERY &&
- idx($previously_discovered_commits, $xaction->getNewValue())) {
-
- unset($xactions[$key]);
- }
- }
-
- return parent::filterTransactions($object, $xactions);
- }
-
- protected function shouldSendMail(
- PhabricatorLiskDAO $object,
- array $xactions) {
-
- // Avoid sending emails that only talk about commit discovery.
- $types = array_unique(mpull($xactions, 'getTransactionType'));
- if ($types === array(ReleephRequestTransaction::TYPE_DISCOVERY)) {
- return false;
- }
-
- // Don't email people when we discover that something picks or reverts OK.
- if ($types === array(ReleephRequestTransaction::TYPE_PICK_STATUS)) {
- if (!mfilter($xactions, 'isBoringPickStatus', true /* negate */)) {
- // If we effectively call "isInterestingPickStatus" and get nothing...
- return false;
- }
- }
-
- return true;
- }
-
- protected function buildReplyHandler(PhabricatorLiskDAO $object) {
- return id(new ReleephRequestReplyHandler())
- ->setActor($this->getActor())
- ->setMailReceiver($object);
- }
-
- protected function getMailSubjectPrefix() {
- return '[Releeph]';
- }
-
- protected function buildMailTemplate(PhabricatorLiskDAO $object) {
- $id = $object->getID();
- $title = $object->getSummaryForDisplay();
- return id(new PhabricatorMetaMTAMail())
- ->setSubject("RQ{$id}: {$title}");
- }
-
- protected function getMailTo(PhabricatorLiskDAO $object) {
- $to_phids = array();
-
- $product = $object->getBranch()->getProduct();
- foreach ($product->getPushers() as $phid) {
- $to_phids[] = $phid;
- }
-
- foreach ($object->getUserIntents() as $phid => $intent) {
- $to_phids[] = $phid;
- }
-
- return $to_phids;
- }
-
- protected function getMailCC(PhabricatorLiskDAO $object) {
- return array();
- }
-
- protected function buildMailBody(
- PhabricatorLiskDAO $object,
- array $xactions) {
-
- $body = parent::buildMailBody($object, $xactions);
-
- $rq = $object;
- $releeph_branch = $rq->getBranch();
- $releeph_project = $releeph_branch->getProduct();
-
- /**
- * If any of the events we are emailing about were about a pick failure
- * (and/or a revert failure?), include pick failure instructions.
- */
- $has_pick_failure = false;
- foreach ($xactions as $xaction) {
- if ($xaction->getTransactionType() ===
- ReleephRequestTransaction::TYPE_PICK_STATUS &&
- $xaction->getNewValue() === ReleephRequest::PICK_FAILED) {
-
- $has_pick_failure = true;
- break;
- }
- }
- if ($has_pick_failure) {
- $instructions = $releeph_project->getDetail('pick_failure_instructions');
- if ($instructions) {
- $body->addRemarkupSection(
- pht('PICK FAILURE INSTRUCTIONS'),
- $instructions);
- }
- }
-
- $name = sprintf('RQ%s: %s', $rq->getID(), $rq->getSummaryForDisplay());
- $body->addTextSection(
- pht('RELEEPH REQUEST'),
- $name."\n".
- PhabricatorEnv::getProductionURI('/RQ'.$rq->getID()));
-
- $project_and_branch = sprintf(
- '%s - %s',
- $releeph_project->getName(),
- $releeph_branch->getDisplayNameWithDetail());
-
- $body->addTextSection(
- pht('RELEEPH BRANCH'),
- $project_and_branch."\n".
- PhabricatorEnv::getProductionURI($releeph_branch->getURI()));
-
- return $body;
- }
-
- private function setInBranchFromAction(
- ReleephRequest $rq,
- ReleephRequestTransaction $xaction) {
-
- $action = $xaction->getMetadataValue('action');
- switch ($action) {
- case 'pick':
- $rq->setInBranch(1);
- break;
-
- case 'revert':
- $rq->setInBranch(0);
- break;
-
- default:
- $id = $rq->getID();
- $type = $xaction->getTransactionType();
- $new = $xaction->getNewValue();
- phlog(
- pht(
- "Unknown discovery action '%s' for xaction of type %s ".
- "with new value %s mentioning %s!",
- $action,
- $type,
- $new,
- 'RQ'.$id));
- break;
- }
-
- return $this;
- }
-
-}
diff --git a/src/applications/releeph/field/exception/ReleephFieldParseException.php b/src/applications/releeph/field/exception/ReleephFieldParseException.php
deleted file mode 100644
index fcb513df13..0000000000
--- a/src/applications/releeph/field/exception/ReleephFieldParseException.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-final class ReleephFieldParseException extends Exception {
-
- public function __construct(ReleephFieldSpecification $field,
- $message) {
-
- $name = $field->getName();
- parent::__construct("{$name}: {$message}");
- }
-
-}
diff --git a/src/applications/releeph/field/selector/ReleephDefaultFieldSelector.php b/src/applications/releeph/field/selector/ReleephDefaultFieldSelector.php
deleted file mode 100644
index af18bfd3d6..0000000000
--- a/src/applications/releeph/field/selector/ReleephDefaultFieldSelector.php
+++ /dev/null
@@ -1,71 +0,0 @@
-<?php
-
-final class ReleephDefaultFieldSelector extends ReleephFieldSelector {
-
- /**
- * Determine if this install is Facebook.
- *
- * TODO: This is a giant hacky mess because I am dumb and moved forward on
- * Releeph changes with partial information. Recover from this as gracefully
- * as possible. This obviously is an abomination. -epriestley
- */
- public static function isFacebook() {
- return class_exists('ReleephFacebookKarmaFieldSpecification');
- }
-
- /**
- * @phutil-external-symbol class ReleephFacebookKarmaFieldSpecification
- * @phutil-external-symbol class ReleephFacebookSeverityFieldSpecification
- * @phutil-external-symbol class ReleephFacebookTagFieldSpecification
- * @phutil-external-symbol class ReleephFacebookTasksFieldSpecification
- */
- public function getFieldSpecifications() {
- if (self::isFacebook()) {
- return array(
- new ReleephCommitMessageFieldSpecification(),
- new ReleephSummaryFieldSpecification(),
- new ReleephReasonFieldSpecification(),
- new ReleephAuthorFieldSpecification(),
- new ReleephRevisionFieldSpecification(),
- new ReleephRequestorFieldSpecification(),
- new ReleephFacebookKarmaFieldSpecification(),
- new ReleephFacebookSeverityFieldSpecification(),
- new ReleephOriginalCommitFieldSpecification(),
- new ReleephDiffMessageFieldSpecification(),
- new ReleephIntentFieldSpecification(),
- new ReleephBranchCommitFieldSpecification(),
- new ReleephDiffSizeFieldSpecification(),
- new ReleephDiffChurnFieldSpecification(),
- new ReleephDependsOnFieldSpecification(),
- new ReleephFacebookTagFieldSpecification(),
- new ReleephFacebookTasksFieldSpecification(),
- );
- } else {
- return array(
- new ReleephCommitMessageFieldSpecification(),
- new ReleephSummaryFieldSpecification(),
- new ReleephReasonFieldSpecification(),
- new ReleephAuthorFieldSpecification(),
- new ReleephRevisionFieldSpecification(),
- new ReleephRequestorFieldSpecification(),
- new ReleephSeverityFieldSpecification(),
- new ReleephOriginalCommitFieldSpecification(),
- new ReleephDiffMessageFieldSpecification(),
- new ReleephIntentFieldSpecification(),
- new ReleephBranchCommitFieldSpecification(),
- new ReleephDiffSizeFieldSpecification(),
- new ReleephDiffChurnFieldSpecification(),
- );
- }
- }
-
- public function sortFieldsForCommitMessage(array $fields) {
- return self::selectFields($fields, array(
- 'ReleephCommitMessageFieldSpecification',
- 'ReleephRequestorFieldSpecification',
- 'ReleephIntentFieldSpecification',
- 'ReleephReasonFieldSpecification',
- ));
- }
-
-}
diff --git a/src/applications/releeph/field/selector/ReleephFieldSelector.php b/src/applications/releeph/field/selector/ReleephFieldSelector.php
deleted file mode 100644
index 3d554f5d6f..0000000000
--- a/src/applications/releeph/field/selector/ReleephFieldSelector.php
+++ /dev/null
@@ -1,48 +0,0 @@
-<?php
-
-abstract class ReleephFieldSelector extends Phobject {
-
- final public function __construct() {
- // <empty>
- }
-
- abstract public function getFieldSpecifications();
-
- public function sortFieldsForCommitMessage(array $fields) {
- assert_instances_of($fields, 'ReleephFieldSpecification');
- return $fields;
- }
-
- protected static function selectFields(array $fields, array $classes) {
- assert_instances_of($fields, 'ReleephFieldSpecification');
-
- $map = array();
- foreach ($fields as $field) {
- $map[get_class($field)] = $field;
- }
-
- $result = array();
- foreach ($classes as $class) {
- $field = idx($map, $class);
- if (!$field) {
- throw new Exception(
- pht(
- "Tried to select a in instance of '%s' but that field ".
- "is not configured for this project!",
- $class));
- }
-
- if (idx($result, $class)) {
- throw new Exception(
- pht(
- "You have asked to select the field '%s' more than once!",
- $class));
- }
-
- $result[$class] = $field;
- }
-
- return $result;
- }
-
-}
diff --git a/src/applications/releeph/field/specification/ReleephAuthorFieldSpecification.php b/src/applications/releeph/field/specification/ReleephAuthorFieldSpecification.php
deleted file mode 100644
index 577280fddb..0000000000
--- a/src/applications/releeph/field/specification/ReleephAuthorFieldSpecification.php
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-final class ReleephAuthorFieldSpecification
- extends ReleephFieldSpecification {
-
- public function getFieldKey() {
- return 'author';
- }
-
- public function getName() {
- return pht('Author');
- }
-
- public function getRequiredHandlePHIDsForPropertyView() {
- $pull = $this->getReleephRequest();
- $commit = $pull->loadPhabricatorRepositoryCommit();
- if (!$commit) {
- return array();
- }
-
- $author_phid = $commit->getAuthorPHID();
- if (!$author_phid) {
- return array();
- }
-
- return array($author_phid);
- }
-
- public function renderPropertyViewValue(array $handles) {
- return $this->renderHandleList($handles);
- }
-
-}
diff --git a/src/applications/releeph/field/specification/ReleephBranchCommitFieldSpecification.php b/src/applications/releeph/field/specification/ReleephBranchCommitFieldSpecification.php
deleted file mode 100644
index 74f747238d..0000000000
--- a/src/applications/releeph/field/specification/ReleephBranchCommitFieldSpecification.php
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-
-final class ReleephBranchCommitFieldSpecification
- extends ReleephFieldSpecification {
-
- public function getFieldKey() {
- return 'commit';
- }
-
- public function getName() {
- return pht('Commit');
- }
-
- public function getRequiredHandlePHIDsForPropertyView() {
- $pull = $this->getReleephRequest();
-
- if ($pull->getCommitPHID()) {
- return array($pull->getCommitPHID());
- }
-
- return array();
- }
-
- public function renderPropertyViewValue(array $handles) {
- return $this->renderHandleList($handles);
- }
-
-}
diff --git a/src/applications/releeph/field/specification/ReleephCommitMessageFieldSpecification.php b/src/applications/releeph/field/specification/ReleephCommitMessageFieldSpecification.php
deleted file mode 100644
index 5122d1e7e1..0000000000
--- a/src/applications/releeph/field/specification/ReleephCommitMessageFieldSpecification.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-
-final class ReleephCommitMessageFieldSpecification
- extends ReleephFieldSpecification {
-
- public function getFieldKey() {
- return 'commit:apply';
- }
-
- public function getName() {
- return '__only_for_commit_message!';
- }
-
- public function shouldAppearInPropertyView() {
- return false;
- }
-
- public function shouldAppearOnCommitMessage() {
- return true;
- }
-
- public function renderLabelForCommitMessage() {
- return $this->renderCommonLabel();
- }
-
- public function renderValueForCommitMessage() {
- return $this->renderCommonValue(
- DifferentialReleephRequestFieldSpecification::ACTION_PICKS);
- }
-
- public function shouldAppearOnRevertMessage() {
- return true;
- }
-
- public function renderLabelForRevertMessage() {
- return $this->renderCommonLabel();
- }
-
- public function renderValueForRevertMessage() {
- return $this->renderCommonValue(
- DifferentialReleephRequestFieldSpecification::ACTION_REVERTS);
- }
-
- private function renderCommonLabel() {
- return id(new DifferentialReleephRequestFieldSpecification())
- ->renderLabelForCommitMessage();
- }
-
- private function renderCommonValue($action) {
- $rq = 'RQ'.$this->getReleephRequest()->getID();
- return "{$action} {$rq}";
- }
-
-}
diff --git a/src/applications/releeph/field/specification/ReleephDependsOnFieldSpecification.php b/src/applications/releeph/field/specification/ReleephDependsOnFieldSpecification.php
deleted file mode 100644
index acb5d7d11d..0000000000
--- a/src/applications/releeph/field/specification/ReleephDependsOnFieldSpecification.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-
-final class ReleephDependsOnFieldSpecification
- extends ReleephFieldSpecification {
-
- public function getFieldKey() {
- return 'dependsOn';
- }
-
- public function getName() {
- return pht('Depends On');
- }
-
- public function getRequiredHandlePHIDsForPropertyView() {
- return $this->getDependentRevisionPHIDs();
- }
-
- public function renderPropertyViewValue(array $handles) {
- return $this->renderHandleList($handles);
- }
-
- private function getDependentRevisionPHIDs() {
- $requested_object = $this->getObject()->getRequestedObjectPHID();
- if (!($requested_object instanceof DifferentialRevision)) {
- return array();
- }
-
- $revision = $requested_object;
-
- return PhabricatorEdgeQuery::loadDestinationPHIDs(
- $revision->getPHID(),
- DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST);
- }
-}
diff --git a/src/applications/releeph/field/specification/ReleephDiffChurnFieldSpecification.php b/src/applications/releeph/field/specification/ReleephDiffChurnFieldSpecification.php
deleted file mode 100644
index 3b472bbae7..0000000000
--- a/src/applications/releeph/field/specification/ReleephDiffChurnFieldSpecification.php
+++ /dev/null
@@ -1,90 +0,0 @@
-<?php
-
-final class ReleephDiffChurnFieldSpecification
- extends ReleephFieldSpecification {
-
- const REJECTIONS_WEIGHT = 30;
- const COMMENTS_WEIGHT = 7;
- const UPDATES_WEIGHT = 10;
- const MAX_POINTS = 100;
-
- public function getFieldKey() {
- return 'churn';
- }
-
- public function getName() {
- return pht('Churn');
- }
-
- public function renderPropertyViewValue(array $handles) {
- $requested_object = $this->getObject()->getRequestedObject();
- if (!($requested_object instanceof DifferentialRevision)) {
- return null;
- }
- $diff_rev = $requested_object;
-
- $xactions = id(new DifferentialTransactionQuery())
- ->setViewer($this->getViewer())
- ->withObjectPHIDs(array($diff_rev->getPHID()))
- ->execute();
-
- $rejections = 0;
- $comments = 0;
- $updates = 0;
-
- foreach ($xactions as $xaction) {
- switch ($xaction->getTransactionType()) {
- case PhabricatorTransactions::TYPE_COMMENT:
- $comments++;
- break;
- case DifferentialRevisionUpdateTransaction::TRANSACTIONTYPE:
- $updates++;
- break;
- case DifferentialTransaction::TYPE_ACTION:
- switch ($xaction->getNewValue()) {
- case DifferentialAction::ACTION_REJECT:
- $rejections++;
- break;
- }
- break;
- }
- }
-
- $points =
- self::REJECTIONS_WEIGHT * $rejections +
- self::COMMENTS_WEIGHT * $comments +
- self::UPDATES_WEIGHT * $updates;
-
- if ($points === 0) {
- $points = 0.15 * self::MAX_POINTS;
- $blurb = pht('Silent diff');
- } else {
- $parts = array();
- if ($rejections) {
- $parts[] = pht('%s rejection(s)', new PhutilNumber($rejections));
- }
- if ($comments) {
- $parts[] = pht('%s comment(s)', new PhutilNumber($comments));
- }
- if ($updates) {
- $parts[] = pht('%s update(s)', new PhutilNumber($updates));
- }
-
- if (count($parts) === 0) {
- $blurb = '';
- } else if (count($parts) === 1) {
- $blurb = head($parts);
- } else {
- $last = array_pop($parts);
- $blurb = pht('%s and %s', implode(', ', $parts), $last);
- }
- }
-
- return id(new AphrontProgressBarView())
- ->setValue($points)
- ->setMax(self::MAX_POINTS)
- ->setCaption($blurb)
- ->render();
- }
-
-}
diff --git a/src/applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php b/src/applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php
deleted file mode 100644
index cde4ab95dd..0000000000
--- a/src/applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-
-final class ReleephDiffMessageFieldSpecification
- extends ReleephFieldSpecification {
-
- public function getFieldKey() {
- return 'commit:message';
- }
-
- public function getName() {
- return pht('Message');
- }
-
- public function getStyleForPropertyView() {
- return 'block';
- }
-
- public function renderPropertyViewValue(array $handles) {
- return phutil_tag(
- 'div',
- array(
- 'class' => 'phabricator-remarkup',
- ),
- $this->getMarkupEngineOutput());
- }
-
- public function shouldMarkup() {
- return true;
- }
-
- public function getMarkupText($field) {
- $commit_data = $this
- ->getReleephRequest()
- ->loadPhabricatorRepositoryCommitData();
- if ($commit_data) {
- return $commit_data->getCommitMessage();
- } else {
- return '';
- }
- }
-
-}
diff --git a/src/applications/releeph/field/specification/ReleephDiffSizeFieldSpecification.php b/src/applications/releeph/field/specification/ReleephDiffSizeFieldSpecification.php
deleted file mode 100644
index 20c8d5e226..0000000000
--- a/src/applications/releeph/field/specification/ReleephDiffSizeFieldSpecification.php
+++ /dev/null
@@ -1,118 +0,0 @@
-<?php
-
-final class ReleephDiffSizeFieldSpecification
- extends ReleephFieldSpecification {
-
- const LINES_WEIGHT = 1;
- const PATHS_WEIGHT = 30;
- const MAX_POINTS = 1000;
-
- public function getFieldKey() {
- return 'commit:size';
- }
-
- public function getName() {
- return pht('Size');
- }
-
- public function renderPropertyViewValue(array $handles) {
- $requested_object = $this->getObject()->getRequestedObject();
- if (!($requested_object instanceof DifferentialRevision)) {
- return null;
- }
- $diff_rev = $requested_object;
-
- $diffs = id(new DifferentialDiff())->loadAllWhere(
- 'revisionID = %d AND creationMethod != %s',
- $diff_rev->getID(),
- 'commit');
-
- $all_changesets = array();
- $most_recent_changesets = null;
- foreach ($diffs as $diff) {
- $changesets = id(new DifferentialChangeset())->loadAllWhere(
- 'diffID = %d',
- $diff->getID());
- $all_changesets += $changesets;
- $most_recent_changesets = $changesets;
- }
-
- // The score is based on all changesets for all versions of this diff
- $all_changes = $this->countLinesAndPaths($all_changesets);
- $points =
- self::LINES_WEIGHT * $all_changes['code']['lines'] +
- self::PATHS_WEIGHT * count($all_changes['code']['paths']);
-
- // The blurb is just based on the most recent version of the diff
- $mr_changes = $this->countLinesAndPaths($most_recent_changesets);
-
- $test_tag = '';
- if ($mr_changes['tests']['paths']) {
- Javelin::initBehavior('phabricator-tooltips');
- require_celerity_resource('aphront-tooltip-css');
-
- $test_blurb = pht(
- "%d line(s) and %d path(s) contain changes to test code:\n",
- $mr_changes['tests']['lines'],
- count($mr_changes['tests']['paths']));
- foreach ($mr_changes['tests']['paths'] as $mr_test_path) {
- $test_blurb .= sprintf("%s\n", $mr_test_path);
- }
-
- $test_tag = javelin_tag(
- 'span',
- array(
- 'sigil' => 'has-tooltip',
- 'meta' => array(
- 'tip' => $test_blurb,
- 'align' => 'E',
- 'size' => 'auto',
- ),
- 'style' => '',
- ),
- ' + tests');
- }
-
- $blurb = hsprintf('%s%s.',
- pht(
- '%d line(s) and %d path(s) over %d diff(s)',
- $mr_changes['code']['lines'],
- $mr_changes['code']['paths'],
- count($diffs)),
- $test_tag);
-
- return id(new AphrontProgressBarView())
- ->setValue($points)
- ->setMax(self::MAX_POINTS)
- ->setCaption($blurb)
- ->render();
- }
-
- private function countLinesAndPaths(array $changesets) {
- assert_instances_of($changesets, 'DifferentialChangeset');
- $lines = 0;
- $paths_touched = array();
- $test_lines = 0;
- $test_paths_touched = array();
-
- foreach ($changesets as $ch) {
- if ($this->getReleephProject()->isTestFile($ch->getFilename())) {
- $test_lines += $ch->getAddLines() + $ch->getDelLines();
- $test_paths_touched[] = $ch->getFilename();
- } else {
- $lines += $ch->getAddLines() + $ch->getDelLines();
- $paths_touched[] = $ch->getFilename();
- }
- }
- return array(
- 'code' => array(
- 'lines' => $lines,
- 'paths' => array_unique($paths_touched),
- ),
- 'tests' => array(
- 'lines' => $test_lines,
- 'paths' => array_unique($test_paths_touched),
- ),
- );
- }
-}
diff --git a/src/applications/releeph/field/specification/ReleephFieldSpecification.php b/src/applications/releeph/field/specification/ReleephFieldSpecification.php
deleted file mode 100644
index ff95ed6514..0000000000
--- a/src/applications/releeph/field/specification/ReleephFieldSpecification.php
+++ /dev/null
@@ -1,265 +0,0 @@
-<?php
-
-abstract class ReleephFieldSpecification
- extends PhabricatorCustomField
- implements PhabricatorMarkupInterface {
-
- // TODO: This is temporary, until ReleephFieldSpecification is more conformant
- // to PhabricatorCustomField.
- private $requestValue;
-
- public function readValueFromRequest(AphrontRequest $request) {
- $this->requestValue = $request->getStr($this->getRequiredStorageKey());
- return $this;
- }
-
- public function shouldAppearInPropertyView() {
- return true;
- }
-
- public function renderPropertyViewLabel() {
- return $this->getName();
- }
-
- public function renderPropertyViewValue(array $handles) {
- $key = $this->getRequiredStorageKey();
- $value = $this->getReleephRequest()->getDetail($key);
- if ($value === '') {
- return null;
- }
- return $value;
- }
-
- abstract public function getName();
-
-/* -( Storage )------------------------------------------------------------ */
-
- public function getStorageKey() {
- return null;
- }
-
- public function getRequiredStorageKey() {
- $key = $this->getStorageKey();
- if ($key === null) {
- throw new PhabricatorCustomFieldImplementationIncompleteException($this);
- }
- if (strpos($key, '.') !== false) {
- /**
- * Storage keys are reused for form controls, and periods in form control
- * names break HTML forms.
- */
- throw new Exception(pht("You can't use '%s' in storage keys!", '.'));
- }
- return $key;
- }
-
- public function shouldAppearInEditView() {
- return $this->isEditable();
- }
-
- final public function isEditable() {
- return $this->getStorageKey() !== null;
- }
-
- final public function getValue() {
- if ($this->requestValue !== null) {
- return $this->requestValue;
- }
-
- $key = $this->getRequiredStorageKey();
- return $this->getReleephRequest()->getDetail($key);
- }
-
- final public function setValue($value) {
- $key = $this->getRequiredStorageKey();
- return $this->getReleephRequest()->setDetail($key, $value);
- }
-
- /**
- * @throws ReleephFieldParseException, to show an error.
- */
- public function validate($value) {
- return;
- }
-
- /**
- * Turn values as they are stored in a ReleephRequest into a text that can be
- * rendered as a transactions old/new values.
- */
- public function normalizeForTransactionView(
- PhabricatorApplicationTransaction $xaction,
- $value) {
-
- return $value;
- }
-
-
-/* -( Conduit )------------------------------------------------------------ */
-
- public function getKeyForConduit() {
- return $this->getRequiredStorageKey();
- }
-
- public function getValueForConduit() {
- return $this->getValue();
- }
-
- public function setValueFromConduitAPIRequest(ConduitAPIRequest $request) {
- $value = idx(
- $request->getValue('fields', array()),
- $this->getRequiredStorageKey());
- $this->validate($value);
- $this->setValue($value);
- return $this;
- }
-
-
-/* -( Arcanist )----------------------------------------------------------- */
-
- public function renderHelpForArcanist() {
- return '';
- }
-
-
-/* -( Context )------------------------------------------------------------ */
-
- private $releephProject;
- private $releephBranch;
- private $releephRequest;
- private $user;
-
- final public function setReleephProject(ReleephProject $rp) {
- $this->releephProject = $rp;
- return $this;
- }
-
- final public function setReleephBranch(ReleephBranch $rb) {
- $this->releephRequest = $rb;
- return $this;
- }
-
- final public function setReleephRequest(ReleephRequest $rr) {
- $this->releephRequest = $rr;
- return $this;
- }
-
- final public function setUser(PhabricatorUser $user) {
- $this->user = $user;
- return $this;
- }
-
- final public function getReleephProject() {
- if (!$this->releephProject) {
- return $this->getReleephBranch()->getProduct();
- }
- return $this->releephProject;
- }
-
- final public function getReleephBranch() {
- if (!$this->releephBranch) {
- return $this->getReleephRequest()->getBranch();
- }
- return $this->releephBranch;
- }
-
- final public function getReleephRequest() {
- if (!$this->releephRequest) {
- return $this->getObject();
- }
- return $this->releephRequest;
- }
-
- final public function getUser() {
- if (!$this->user) {
- return $this->getViewer();
- }
- return $this->user;
- }
-
-/* -( Commit Messages )---------------------------------------------------- */
-
- public function shouldAppearOnCommitMessage() {
- return false;
- }
-
- public function renderLabelForCommitMessage() {
- throw new PhabricatorCustomFieldImplementationIncompleteException($this);
- }
-
- public function renderValueForCommitMessage() {
- throw new PhabricatorCustomFieldImplementationIncompleteException($this);
- }
-
- public function shouldAppearOnRevertMessage() {
- return false;
- }
-
- public function renderLabelForRevertMessage() {
- return $this->renderLabelForCommitMessage();
- }
-
- public function renderValueForRevertMessage() {
- return $this->renderValueForCommitMessage();
- }
-
-
-/* -( Markup Interface )--------------------------------------------------- */
-
- const MARKUP_FIELD_GENERIC = 'releeph:generic-markup-field';
-
- private $engine;
-
- /**
- * @{class:ReleephFieldSpecification} implements much of
- * @{interface:PhabricatorMarkupInterface} for you. If you return true from
- * `shouldMarkup()`, and implement `getMarkupText()` then your text will be
- * rendered through the Phabricator markup pipeline.
- *
- * Output is retrievable with `getMarkupEngineOutput()`.
- */
- public function shouldMarkup() {
- return false;
- }
-
- public function getMarkupText($field) {
- throw new PhabricatorCustomFieldImplementationIncompleteException($this);
- }
-
- final public function getMarkupEngineOutput() {
- return $this->engine->getOutput($this, self::MARKUP_FIELD_GENERIC);
- }
-
- final public function setMarkupEngine(PhabricatorMarkupEngine $engine) {
- $this->engine = $engine;
- $engine->addObject($this, self::MARKUP_FIELD_GENERIC);
- return $this;
- }
-
- final public function getMarkupFieldKey($field) {
- $content = sprintf(
- '%s:%s:%s:%s',
- $this->getReleephRequest()->getPHID(),
- $this->getStorageKey(),
- $field,
- $this->getMarkupText($field));
-
- return PhabricatorMarkupEngine::digestRemarkupContent($this, $content);
- }
-
- final public function newMarkupEngine($field) {
- return PhabricatorMarkupEngine::newDifferentialMarkupEngine();
- }
-
- final public function didMarkupText(
- $field,
- $output,
- PhutilMarkupEngine $engine) {
-
- return $output;
- }
-
- final public function shouldUseMarkupCache($field) {
- return true;
- }
-
-}
diff --git a/src/applications/releeph/field/specification/ReleephIntentFieldSpecification.php b/src/applications/releeph/field/specification/ReleephIntentFieldSpecification.php
deleted file mode 100644
index 833dce0b39..0000000000
--- a/src/applications/releeph/field/specification/ReleephIntentFieldSpecification.php
+++ /dev/null
@@ -1,142 +0,0 @@
-<?php
-
-final class ReleephIntentFieldSpecification
- extends ReleephFieldSpecification {
-
- public function getFieldKey() {
- return 'intent';
- }
-
- public function getName() {
- return pht('Intent');
- }
-
- public function getRequiredHandlePHIDsForPropertyView() {
- $pull = $this->getReleephRequest();
- $intents = $pull->getUserIntents();
- return array_keys($intents);
- }
-
- public function renderPropertyViewValue(array $handles) {
- $pull = $this->getReleephRequest();
-
- $intents = $pull->getUserIntents();
- $product = $this->getReleephProject();
-
- if (!$intents) {
- return null;
- }
-
- $pushers = array();
- $others = array();
-
- foreach ($intents as $phid => $intent) {
- if ($product->isAuthoritativePHID($phid)) {
- $pushers[$phid] = $intent;
- } else {
- $others[$phid] = $intent;
- }
- }
-
- $intents = $pushers + $others;
-
- $view = id(new PHUIStatusListView());
- foreach ($intents as $phid => $intent) {
- switch ($intent) {
- case ReleephRequest::INTENT_WANT:
- $icon = PHUIStatusItemView::ICON_ACCEPT;
- $color = 'green';
- $label = pht('Want');
- break;
- case ReleephRequest::INTENT_PASS:
- $icon = PHUIStatusItemView::ICON_REJECT;
- $color = 'red';
- $label = pht('Pass');
- break;
- default:
- $icon = PHUIStatusItemView::ICON_QUESTION;
- $color = 'bluegrey';
- $label = pht('Unknown Intent (%s)', $intent);
- break;
- }
-
- $target = $handles[$phid]->renderLink();
- if ($product->isAuthoritativePHID($phid)) {
- $target = phutil_tag('strong', array(), $target);
- }
-
- $view->addItem(
- id(new PHUIStatusItemView())
- ->setIcon($icon, $color, $label)
- ->setTarget($target));
- }
-
- return $view;
- }
-
- public function shouldAppearOnCommitMessage() {
- return true;
- }
-
- public function shouldAppearOnRevertMessage() {
- return true;
- }
-
- public function renderLabelForCommitMessage() {
- return pht('Approved By');
- }
-
- public function renderLabelForRevertMessage() {
- return pht('Rejected By');
- }
-
- public function renderValueForCommitMessage() {
- return $this->renderIntentsForCommitMessage(ReleephRequest::INTENT_WANT);
- }
-
- public function renderValueForRevertMessage() {
- return $this->renderIntentsForCommitMessage(ReleephRequest::INTENT_PASS);
- }
-
- private function renderIntentsForCommitMessage($print_intent) {
- $intents = $this->getReleephRequest()->getUserIntents();
-
- $requestor = $this->getReleephRequest()->getRequestUserPHID();
- $pusher_phids = $this->getReleephProject()->getPushers();
-
- $phids = array_unique($pusher_phids + array_keys($intents));
- $handles = id(new PhabricatorHandleQuery())
- ->setViewer($this->getUser())
- ->withPHIDs($phids)
- ->execute();
-
- $tokens = array();
- foreach ($phids as $phid) {
- $intent = idx($intents, $phid);
- if ($intent == $print_intent) {
- $name = $handles[$phid]->getName();
- $is_pusher = in_array($phid, $pusher_phids);
- $is_requestor = $phid == $requestor;
-
- if ($is_pusher) {
- if ($is_requestor) {
- $token = pht('%s (pusher and requestor)', $name);
- } else {
- $token = "{$name} (pusher)";
- }
- } else {
- if ($is_requestor) {
- $token = pht('%s (requestor)', $name);
- } else {
- $token = $name;
- }
- }
-
- $tokens[] = $token;
- }
- }
-
- return implode(', ', $tokens);
- }
-
-}
diff --git a/src/applications/releeph/field/specification/ReleephLevelFieldSpecification.php b/src/applications/releeph/field/specification/ReleephLevelFieldSpecification.php
deleted file mode 100644
index 395636c983..0000000000
--- a/src/applications/releeph/field/specification/ReleephLevelFieldSpecification.php
+++ /dev/null
@@ -1,137 +0,0 @@
-<?php
-
-/**
- * Provides a convenient field for storing a set of levels that you can use to
- * filter requests on.
- *
- * Levels are rendered with names and descriptions in the edit UI, and are
- * automatically documented via the "arc request" interface.
- *
- * See ReleephSeverityFieldSpecification for an example.
- */
-abstract class ReleephLevelFieldSpecification
- extends ReleephFieldSpecification {
-
- private $error;
-
- abstract public function getLevels();
- abstract public function getDefaultLevel();
- abstract public function getNameForLevel($level);
- abstract public function getDescriptionForLevel($level);
-
- public function getStorageKey() {
- throw new PhabricatorCustomFieldImplementationIncompleteException($this);
- }
-
- public function renderPropertyViewValue(array $handles) {
- return $this->getNameForLevel($this->getValue());
- }
-
- public function renderEditControl(array $handles) {
- $control_name = $this->getRequiredStorageKey();
- $all_levels = $this->getLevels();
-
- $level = $this->getValue();
- if (!$level) {
- $level = $this->getDefaultLevel();
- }
-
- $control = id(new AphrontFormRadioButtonControl())
- ->setLabel(pht('Level'))
- ->setName($control_name)
- ->setValue($level);
-
- if ($this->error) {
- $control->setError($this->error);
- } else if ($this->getDefaultLevel()) {
- $control->setError(true);
- }
-
- foreach ($all_levels as $level) {
- $name = $this->getNameForLevel($level);
- $description = $this->getDescriptionForLevel($level);
- $control->addButton($level, $name, $description);
- }
-
- return $control;
- }
-
- public function renderHelpForArcanist() {
- $text = '';
- $levels = $this->getLevels();
- $default = $this->getDefaultLevel();
- foreach ($levels as $level) {
- $name = $this->getNameForLevel($level);
- $description = $this->getDescriptionForLevel($level);
- $default_marker = ' ';
- if ($level === $default) {
- $default_marker = '*';
- }
- $text .= " {$default_marker} **{$name}**\n";
- $text .= phutil_console_wrap($description."\n", 8);
- }
- return $text;
- }
-
- public function validate($value) {
- if ($value === null) {
- $this->error = pht('Required');
- $label = $this->getName();
- throw new ReleephFieldParseException(
- $this,
- pht('You must provide a %s level.', $label));
- }
-
- $levels = $this->getLevels();
- if (!in_array($value, $levels)) {
- $label = $this->getName();
- throw new ReleephFieldParseException(
- $this,
- pht(
- "Level '%s' is not a valid %s level in this project.",
- $value,
- $label));
- }
- }
-
- public function setValueFromConduitAPIRequest(ConduitAPIRequest $request) {
- $key = $this->getRequiredStorageKey();
- $label = $this->getName();
- $name = idx($request->getValue('fields', array()), $key);
-
- if (!$name) {
- $level = $this->getDefaultLevel();
- if (!$level) {
- throw new ReleephFieldParseException(
- $this,
- pht(
- 'No value given for %s, and no default is given for this level!',
- $label));
- }
- } else {
- $level = $this->getLevelByName($name);
- }
-
- if (!$level) {
- throw new ReleephFieldParseException(
- $this,
- pht("Unknown %s level name '%s'", $label, $name));
- }
- $this->setValue($level);
- return $this;
- }
-
- private $nameMap = array();
-
- public function getLevelByName($name) {
- // Build this once
- if (!$this->nameMap) {
- foreach ($this->getLevels() as $level) {
- $level_name = $this->getNameForLevel($level);
- $this->nameMap[$level_name] = $level;
- }
- }
- return idx($this->nameMap, $name);
- }
-
-}
diff --git a/src/applications/releeph/field/specification/ReleephOriginalCommitFieldSpecification.php b/src/applications/releeph/field/specification/ReleephOriginalCommitFieldSpecification.php
deleted file mode 100644
index 57c4209ca7..0000000000
--- a/src/applications/releeph/field/specification/ReleephOriginalCommitFieldSpecification.php
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php
-
-final class ReleephOriginalCommitFieldSpecification
- extends ReleephFieldSpecification {
-
- public function getFieldKey() {
- return 'commit:name';
- }
-
- public function getName() {
- return pht('Commit');
- }
-
- public function getRequiredHandlePHIDsForPropertyView() {
- return array(
- $this->getReleephRequest()->getRequestCommitPHID(),
- );
- }
-
-
- public function renderPropertyViewValue(array $handles) {
- return $this->renderHandleList($handles);
- }
-
-}
diff --git a/src/applications/releeph/field/specification/ReleephReasonFieldSpecification.php b/src/applications/releeph/field/specification/ReleephReasonFieldSpecification.php
deleted file mode 100644
index 023ea081a1..0000000000
--- a/src/applications/releeph/field/specification/ReleephReasonFieldSpecification.php
+++ /dev/null
@@ -1,86 +0,0 @@
-<?php
-
-final class ReleephReasonFieldSpecification
- extends ReleephFieldSpecification {
-
- public function getFieldKey() {
- return 'reason';
- }
-
- public function getName() {
- return pht('Reason');
- }
-
- public function getStorageKey() {
- return 'reason';
- }
-
- public function getStyleForPropertyView() {
- return 'block';
- }
-
- public function getIconForPropertyView() {
- return PHUIPropertyListView::ICON_SUMMARY;
- }
-
- public function renderPropertyViewValue(array $handles) {
- return phutil_tag(
- 'div',
- array(
- 'class' => 'phabricator-remarkup',
- ),
- $this->getMarkupEngineOutput());
- }
-
- private $error = true;
-
- public function renderEditControl(array $handles) {
- return id(new AphrontFormTextAreaControl())
- ->setLabel(pht('Reason'))
- ->setName('reason')
- ->setError($this->error)
- ->setValue($this->getValue());
- }
-
- public function validate($reason) {
- if (!$reason) {
- $this->error = pht('Required');
- throw new ReleephFieldParseException(
- $this,
- pht('You must give a reason for your request.'));
- }
- }
-
- public function renderHelpForArcanist() {
- $text = pht(
- 'Fully explain why you are requesting this code be included '.
- 'in the next release.')."\n";
- return phutil_console_wrap($text, 8);
- }
-
- public function shouldAppearOnCommitMessage() {
- return true;
- }
-
- public function renderLabelForCommitMessage() {
- return pht('Request Reason');
- }
-
- public function renderValueForCommitMessage() {
- return $this->getValue();
- }
-
- public function shouldMarkup() {
- return true;
- }
-
- public function getMarkupText($field) {
- $reason = $this->getValue();
- if ($reason) {
- return $reason;
- } else {
- return '';
- }
- }
-
-}
diff --git a/src/applications/releeph/field/specification/ReleephRequestorFieldSpecification.php b/src/applications/releeph/field/specification/ReleephRequestorFieldSpecification.php
deleted file mode 100644
index d479bb8020..0000000000
--- a/src/applications/releeph/field/specification/ReleephRequestorFieldSpecification.php
+++ /dev/null
@@ -1,50 +0,0 @@
-<?php
-
-final class ReleephRequestorFieldSpecification
- extends ReleephFieldSpecification {
-
- public function getFieldKey() {
- return 'requestor';
- }
-
- public function getName() {
- return pht('Requestor');
- }
-
- public function getRequiredHandlePHIDsForPropertyView() {
- $phids = array();
-
- $phid = $this->getReleephRequest()->getRequestUserPHID();
- if ($phid) {
- $phids[] = $phid;
- }
-
- return $phids;
- }
-
- public function renderPropertyViewValue(array $handles) {
- return $this->renderHandleList($handles);
- }
-
- public function shouldAppearOnCommitMessage() {
- return true;
- }
-
- public function shouldAppearOnRevertMessage() {
- return true;
- }
-
- public function renderLabelForCommitMessage() {
- return pht('Requested By');
- }
-
- public function renderValueForCommitMessage() {
- $phid = $this->getReleephRequest()->getRequestUserPHID();
- $handle = id(new PhabricatorHandleQuery())
- ->setViewer($this->getUser())
- ->withPHIDs(array($phid))
- ->executeOne();
- return $handle->getName();
- }
-
-}
diff --git a/src/applications/releeph/field/specification/ReleephRevisionFieldSpecification.php b/src/applications/releeph/field/specification/ReleephRevisionFieldSpecification.php
deleted file mode 100644
index 45f6bdd7e1..0000000000
--- a/src/applications/releeph/field/specification/ReleephRevisionFieldSpecification.php
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-
-final class ReleephRevisionFieldSpecification
- extends ReleephFieldSpecification {
-
- public function getFieldKey() {
- return 'revision';
- }
-
- public function getName() {
- return pht('Revision');
- }
-
- public function getRequiredHandlePHIDsForPropertyView() {
- $requested_object = $this->getObject()->getRequestedObjectPHID();
- if (!($requested_object instanceof DifferentialRevision)) {
- return array();
- }
-
- return array(
- $requested_object->getPHID(),
- );
- }
-
- public function renderPropertyViewValue(array $handles) {
- return $this->renderHandleList($handles);
- }
-
-}
diff --git a/src/applications/releeph/field/specification/ReleephSeverityFieldSpecification.php b/src/applications/releeph/field/specification/ReleephSeverityFieldSpecification.php
deleted file mode 100644
index c73a17e149..0000000000
--- a/src/applications/releeph/field/specification/ReleephSeverityFieldSpecification.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-
-final class ReleephSeverityFieldSpecification
- extends ReleephLevelFieldSpecification {
-
- const HOTFIX = 'HOTFIX';
- const RELEASE = 'RELEASE';
-
- public function getFieldKey() {
- return 'severity';
- }
-
- public function getName() {
- return pht('Severity');
- }
-
- public function getStorageKey() {
- return 'releeph:severity';
- }
-
- public function getLevels() {
- return array(
- self::HOTFIX,
- self::RELEASE,
- );
- }
-
- public function getDefaultLevel() {
- return self::RELEASE;
- }
-
- public function getNameForLevel($level) {
- static $names = array(
- self::HOTFIX => 'HOTFIX',
- self::RELEASE => 'RELEASE',
- );
- return idx($names, $level, $level);
- }
-
- public function getDescriptionForLevel($level) {
- static $descriptions;
-
- if ($descriptions === null) {
- $descriptions = array(
- self::HOTFIX => pht('Needs merging and fixing right now.'),
- self::RELEASE => pht('Required for the currently rolling release.'),
- );
- }
-
- return idx($descriptions, $level);
- }
-
-}
diff --git a/src/applications/releeph/field/specification/ReleephSummaryFieldSpecification.php b/src/applications/releeph/field/specification/ReleephSummaryFieldSpecification.php
deleted file mode 100644
index 2341b0d2b5..0000000000
--- a/src/applications/releeph/field/specification/ReleephSummaryFieldSpecification.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-
-final class ReleephSummaryFieldSpecification
- extends ReleephFieldSpecification {
-
- const MAX_SUMMARY_LENGTH = 60;
-
- public function shouldAppearInPropertyView() {
- return false;
- }
-
- public function getFieldKey() {
- return 'summary';
- }
-
- public function getName() {
- return pht('Summary');
- }
-
- public function getStorageKey() {
- return 'summary';
- }
-
- private $error = false;
-
- public function renderEditControl(array $handles) {
- return id(new AphrontFormTextControl())
- ->setLabel(pht('Summary'))
- ->setName('summary')
- ->setError($this->error)
- ->setValue($this->getValue())
- ->setCaption(pht('Leave this blank to use the original commit title'));
- }
-
- public function renderHelpForArcanist() {
- $text = pht(
- 'A one-line title summarizing this request. '.
- 'Leave blank to use the original commit title.')."\n";
- return phutil_console_wrap($text, 8);
- }
-
- public function validate($summary) {
- if ($summary && strlen($summary) > self::MAX_SUMMARY_LENGTH) {
- $this->error = pht('Too long!');
- throw new ReleephFieldParseException(
- $this,
- pht(
- 'Please keep your summary to under %d characters.',
- self::MAX_SUMMARY_LENGTH));
- }
- }
-
-}
diff --git a/src/applications/releeph/mail/ReleephRequestMailReceiver.php b/src/applications/releeph/mail/ReleephRequestMailReceiver.php
deleted file mode 100644
index a64c7f483f..0000000000
--- a/src/applications/releeph/mail/ReleephRequestMailReceiver.php
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-
-final class ReleephRequestMailReceiver extends PhabricatorObjectMailReceiver {
-
- public function isEnabled() {
- $app_class = 'PhabricatorReleephApplication';
- return PhabricatorApplication::isClassInstalled($app_class);
- }
-
- protected function getObjectPattern() {
- return 'Y[1-9]\d*';
- }
-
- protected function loadObject($pattern, PhabricatorUser $viewer) {
- $id = (int)substr($pattern, 1);
-
- return id(new ReleephRequestQuery())
- ->setViewer($viewer)
- ->withIDs(array($id))
- ->executeOne();
- }
-
- protected function getTransactionReplyHandler() {
- return new ReleephRequestReplyHandler();
- }
-
-}
diff --git a/src/applications/releeph/mail/ReleephRequestReplyHandler.php b/src/applications/releeph/mail/ReleephRequestReplyHandler.php
deleted file mode 100644
index 9648941941..0000000000
--- a/src/applications/releeph/mail/ReleephRequestReplyHandler.php
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php
-
-final class ReleephRequestReplyHandler
- extends PhabricatorApplicationTransactionReplyHandler {
-
- public function validateMailReceiver($mail_receiver) {
- if (!($mail_receiver instanceof ReleephRequest)) {
- throw new Exception(pht('Mail receiver is not a %s!', 'ReleephRequest'));
- }
- }
-
- public function getObjectPrefix() {
- return 'Y';
- }
-
-}
diff --git a/src/applications/releeph/phid/ReleephBranchPHIDType.php b/src/applications/releeph/phid/ReleephBranchPHIDType.php
deleted file mode 100644
index d3a545d0df..0000000000
--- a/src/applications/releeph/phid/ReleephBranchPHIDType.php
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-
-final class ReleephBranchPHIDType extends PhabricatorPHIDType {
-
- const TYPECONST = 'REBR';
-
- public function getTypeName() {
- return pht('Releeph Branch');
- }
-
- public function newObject() {
- return new ReleephBranch();
- }
-
- public function getPHIDTypeApplicationClass() {
- return 'PhabricatorReleephApplication';
- }
-
- protected function buildQueryForObjects(
- PhabricatorObjectQuery $query,
- array $phids) {
-
- return id(new ReleephBranchQuery())
- ->withPHIDs($phids);
- }
-
- public function loadHandles(
- PhabricatorHandleQuery $query,
- array $handles,
- array $objects) {
-
- foreach ($handles as $phid => $handle) {
- $branch = $objects[$phid];
-
- $handle->setURI($branch->getURI());
- $handle->setName($branch->getBasename());
- $handle->setFullName($branch->getName());
- }
- }
-
-}
diff --git a/src/applications/releeph/phid/ReleephProductPHIDType.php b/src/applications/releeph/phid/ReleephProductPHIDType.php
deleted file mode 100644
index c7979cfba3..0000000000
--- a/src/applications/releeph/phid/ReleephProductPHIDType.php
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-
-final class ReleephProductPHIDType extends PhabricatorPHIDType {
-
- const TYPECONST = 'REPR';
-
- public function getTypeName() {
- return pht('Releeph Product');
- }
-
- public function newObject() {
- return new ReleephProject();
- }
-
- public function getPHIDTypeApplicationClass() {
- return 'PhabricatorReleephApplication';
- }
-
- protected function buildQueryForObjects(
- PhabricatorObjectQuery $query,
- array $phids) {
-
- return id(new ReleephProductQuery())
- ->withPHIDs($phids);
- }
-
- public function loadHandles(
- PhabricatorHandleQuery $query,
- array $handles,
- array $objects) {
-
- foreach ($handles as $phid => $handle) {
- $product = $objects[$phid];
-
- $handle->setName($product->getName());
- $handle->setURI($product->getURI());
- }
- }
-
-}
diff --git a/src/applications/releeph/phid/ReleephRequestPHIDType.php b/src/applications/releeph/phid/ReleephRequestPHIDType.php
deleted file mode 100644
index 7bd853f984..0000000000
--- a/src/applications/releeph/phid/ReleephRequestPHIDType.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-final class ReleephRequestPHIDType extends PhabricatorPHIDType {
-
- const TYPECONST = 'RERQ';
-
- public function getTypeName() {
- return pht('Releeph Request');
- }
-
- public function newObject() {
- return new ReleephRequest();
- }
-
- public function getPHIDTypeApplicationClass() {
- return 'PhabricatorReleephApplication';
- }
-
- protected function buildQueryForObjects(
- PhabricatorObjectQuery $query,
- array $phids) {
-
- return id(new ReleephRequestQuery())
- ->withPHIDs($phids);
- }
-
- public function loadHandles(
- PhabricatorHandleQuery $query,
- array $handles,
- array $objects) {
-
- foreach ($handles as $phid => $handle) {
- $request = $objects[$phid];
-
- $id = $request->getID();
- $title = $request->getSummaryForDisplay();
-
- $handle->setURI("/RQ{$id}");
- $handle->setName($title);
- $handle->setFullName("RQ{$id}: {$title}");
- }
- }
-
-}
diff --git a/src/applications/releeph/query/ReleephBranchQuery.php b/src/applications/releeph/query/ReleephBranchQuery.php
deleted file mode 100644
index 97e47bdcaf..0000000000
--- a/src/applications/releeph/query/ReleephBranchQuery.php
+++ /dev/null
@@ -1,152 +0,0 @@
-<?php
-
-final class ReleephBranchQuery
- extends PhabricatorCursorPagedPolicyAwareQuery {
-
- private $ids;
- private $phids;
- private $productPHIDs;
- private $productIDs;
-
- const STATUS_ALL = 'status-all';
- const STATUS_OPEN = 'status-open';
- private $status = self::STATUS_ALL;
-
- private $needCutPointCommits;
-
- public function withIDs(array $ids) {
- $this->ids = $ids;
- return $this;
- }
-
- public function withPHIDs(array $phids) {
- $this->phids = $phids;
- return $this;
- }
-
- public function needCutPointCommits($need_commits) {
- $this->needCutPointCommits = $need_commits;
- return $this;
- }
-
- public function withStatus($status) {
- $this->status = $status;
- return $this;
- }
-
- public function withProductPHIDs($product_phids) {
- $this->productPHIDs = $product_phids;
- return $this;
- }
-
- protected function loadPage() {
- $table = new ReleephBranch();
- $conn_r = $table->establishConnection('r');
-
- $data = queryfx_all(
- $conn_r,
- 'SELECT * FROM %T %Q %Q %Q',
- $table->getTableName(),
- $this->buildWhereClause($conn_r),
- $this->buildOrderClause($conn_r),
- $this->buildLimitClause($conn_r));
-
- return $table->loadAllFromArray($data);
- }
-
- protected function willExecute() {
- if ($this->productPHIDs !== null) {
- $products = id(new ReleephProductQuery())
- ->setViewer($this->getViewer())
- ->withPHIDs($this->productPHIDs)
- ->execute();
-
- if (!$products) {
- throw new PhabricatorEmptyQueryException();
- }
-
- $this->productIDs = mpull($products, 'getID');
- }
- }
-
- protected function willFilterPage(array $branches) {
- $project_ids = mpull($branches, 'getReleephProjectID');
-
- $projects = id(new ReleephProductQuery())
- ->withIDs($project_ids)
- ->setViewer($this->getViewer())
- ->execute();
-
- foreach ($branches as $key => $branch) {
- $project_id = $project_ids[$key];
- if (isset($projects[$project_id])) {
- $branch->attachProject($projects[$project_id]);
- } else {
- unset($branches[$key]);
- }
- }
-
- if ($this->needCutPointCommits) {
- $commit_phids = mpull($branches, 'getCutPointCommitPHID');
- $commits = id(new DiffusionCommitQuery())
- ->setViewer($this->getViewer())
- ->withPHIDs($commit_phids)
- ->execute();
- $commits = mpull($commits, null, 'getPHID');
-
- foreach ($branches as $branch) {
- $commit = idx($commits, $branch->getCutPointCommitPHID());
- $branch->attachCutPointCommit($commit);
- }
- }
-
- return $branches;
- }
-
- protected function buildWhereClause(AphrontDatabaseConnection $conn) {
- $where = array();
-
- if ($this->ids !== null) {
- $where[] = qsprintf(
- $conn,
- 'id IN (%Ld)',
- $this->ids);
- }
-
- if ($this->phids !== null) {
- $where[] = qsprintf(
- $conn,
- 'phid IN (%Ls)',
- $this->phids);
- }
-
- if ($this->productIDs !== null) {
- $where[] = qsprintf(
- $conn,
- 'releephProjectID IN (%Ld)',
- $this->productIDs);
- }
-
- $status = $this->status;
- switch ($status) {
- case self::STATUS_ALL:
- break;
- case self::STATUS_OPEN:
- $where[] = qsprintf(
- $conn,
- 'isActive = 1');
- break;
- default:
- throw new Exception(pht("Unknown status constant '%s'!", $status));
- }
-
- $where[] = $this->buildPagingClause($conn);
-
- return $this->formatWhereClause($conn, $where);
- }
-
- public function getQueryApplicationClass() {
- return 'PhabricatorReleephApplication';
- }
-
-}
diff --git a/src/applications/releeph/query/ReleephBranchSearchEngine.php b/src/applications/releeph/query/ReleephBranchSearchEngine.php
deleted file mode 100644
index 441f70e992..0000000000
--- a/src/applications/releeph/query/ReleephBranchSearchEngine.php
+++ /dev/null
@@ -1,200 +0,0 @@
-<?php
-
-final class ReleephBranchSearchEngine
- extends PhabricatorApplicationSearchEngine {
-
- private $product;
-
- public function getResultTypeDescription() {
- return pht('Releeph Branches');
- }
-
- public function canUseInPanelContext() {
- return false;
- }
-
- public function getApplicationClassName() {
- return 'PhabricatorReleephApplication';
- }
-
- public function setProduct(ReleephProject $product) {
- $this->product = $product;
- return $this;
- }
-
- public function getProduct() {
- return $this->product;
- }
-
- public function buildSavedQueryFromRequest(AphrontRequest $request) {
- $saved = new PhabricatorSavedQuery();
-
- $saved->setParameter('active', $request->getStr('active'));
-
- return $saved;
- }
-
- public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
- $query = id(new ReleephBranchQuery())
- ->needCutPointCommits(true)
- ->withProductPHIDs(array($this->getProduct()->getPHID()));
-
- $active = $saved->getParameter('active');
- $value = idx($this->getActiveValues(), $active);
- if ($value !== null) {
- $query->withStatus($value);
- }
-
- return $query;
- }
-
- public function buildSearchForm(
- AphrontFormView $form,
- PhabricatorSavedQuery $saved_query) {
-
- $form->appendChild(
- id(new AphrontFormSelectControl())
- ->setName('active')
- ->setLabel(pht('Show Branches'))
- ->setValue($saved_query->getParameter('active'))
- ->setOptions($this->getActiveOptions()));
- }
-
- protected function getURI($path) {
- return '/releeph/product/'.$this->getProduct()->getID().'/'.$path;
- }
-
- protected function getBuiltinQueryNames() {
- $names = array(
- 'open' => pht('Open'),
- 'all' => pht('All'),
- );
-
- return $names;
- }
-
- public function buildSavedQueryFromBuiltin($query_key) {
-
- $query = $this->newSavedQuery();
- $query->setQueryKey($query_key);
-
- switch ($query_key) {
- case 'open':
- return $query
- ->setParameter('active', 'open');
- case 'all':
- return $query;
- }
-
- return parent::buildSavedQueryFromBuiltin($query_key);
- }
-
- private function getActiveOptions() {
- return array(
- 'open' => pht('Open Branches'),
- 'all' => pht('Open and Closed Branches'),
- );
- }
-
- private function getActiveValues() {
- return array(
- 'open' => ReleephBranchQuery::STATUS_OPEN,
- 'all' => ReleephBranchQuery::STATUS_ALL,
- );
- }
-
- protected function renderResultList(
- array $branches,
- PhabricatorSavedQuery $query,
- array $handles) {
-
-
- assert_instances_of($branches, 'ReleephBranch');
-
- $viewer = $this->getRequest()->getUser();
-
- $products = mpull($branches, 'getProduct');
- $repo_phids = mpull($products, 'getRepositoryPHID');
-
- if ($repo_phids) {
- $repos = id(new PhabricatorRepositoryQuery())
- ->setViewer($viewer)
- ->withPHIDs($repo_phids)
- ->execute();
- $repos = mpull($repos, null, 'getPHID');
- } else {
- $repos = array();
- }
-
- $requests = array();
- if ($branches) {
- $requests = id(new ReleephRequestQuery())
- ->setViewer($viewer)
- ->withBranchIDs(mpull($branches, 'getID'))
- ->withStatus(ReleephRequestQuery::STATUS_OPEN)
- ->execute();
- $requests = mgroup($requests, 'getBranchID');
- }
-
- $list = id(new PHUIObjectItemListView())
- ->setUser($viewer);
- foreach ($branches as $branch) {
- $diffusion_href = null;
- $repo = idx($repos, $branch->getProduct()->getRepositoryPHID());
- if ($repo) {
- $drequest = DiffusionRequest::newFromDictionary(
- array(
- 'user' => $viewer,
- 'repository' => $repo,
- ));
-
- $diffusion_href = $drequest->generateURI(
- array(
- 'action' => 'branch',
- 'branch' => $branch->getName(),
- ));
- }
-
- $branch_link = $branch->getName();
- if ($diffusion_href) {
- $branch_link = phutil_tag(
- 'a',
- array(
- 'href' => $diffusion_href,
- ),
- $branch_link);
- }
-
- $item = id(new PHUIObjectItemView())
- ->setHeader($branch->getDisplayName())
- ->setHref($this->getApplicationURI('branch/'.$branch->getID().'/'))
- ->addAttribute($branch_link);
-
- if (!$branch->getIsActive()) {
- $item->setDisabled(true);
- }
-
- $commit = $branch->getCutPointCommit();
- if ($commit) {
- $item->addIcon(
- 'none',
- phabricator_datetime($commit->getEpoch(), $viewer));
- }
-
- $open_count = count(idx($requests, $branch->getID(), array()));
- if ($open_count) {
- $item->setStatusIcon('fa-code-fork orange');
- $item->addIcon(
- 'fa-code-fork',
- pht(
- '%s Open Pull Request(s)',
- new PhutilNumber($open_count)));
- }
-
- $list->addItem($item);
- }
-
- return id(new PhabricatorApplicationSearchResultView())
- ->setObjectList($list);
- }
-}
diff --git a/src/applications/releeph/query/ReleephBranchTransactionQuery.php b/src/applications/releeph/query/ReleephBranchTransactionQuery.php
deleted file mode 100644
index 3e88259b25..0000000000
--- a/src/applications/releeph/query/ReleephBranchTransactionQuery.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-final class ReleephBranchTransactionQuery
- extends PhabricatorApplicationTransactionQuery {
-
- public function getTemplateApplicationTransaction() {
- return new ReleephBranchTransaction();
- }
-
-}
diff --git a/src/applications/releeph/query/ReleephProductQuery.php b/src/applications/releeph/query/ReleephProductQuery.php
deleted file mode 100644
index 118b9919a8..0000000000
--- a/src/applications/releeph/query/ReleephProductQuery.php
+++ /dev/null
@@ -1,144 +0,0 @@
-<?php
-
-final class ReleephProductQuery
- extends PhabricatorCursorPagedPolicyAwareQuery {
-
- private $active;
- private $ids;
- private $phids;
- private $repositoryPHIDs;
-
- const ORDER_ID = 'order-id';
- const ORDER_NAME = 'order-name';
-
- public function withActive($active) {
- $this->active = $active;
- return $this;
- }
-
- public function setOrder($order) {
- switch ($order) {
- case self::ORDER_ID:
- $this->setOrderVector(array('id'));
- break;
- case self::ORDER_NAME:
- $this->setOrderVector(array('name'));
- break;
- default:
- throw new Exception(pht('Order "%s" not supported.', $order));
- }
- return $this;
- }
-
- public function withIDs(array $ids) {
- $this->ids = $ids;
- return $this;
- }
-
- public function withPHIDs(array $phids) {
- $this->phids = $phids;
- return $this;
- }
-
- public function withRepositoryPHIDs(array $repository_phids) {
- $this->repositoryPHIDs = $repository_phids;
- return $this;
- }
-
- protected function loadPage() {
- $table = new ReleephProject();
- $conn_r = $table->establishConnection('r');
-
- $rows = queryfx_all(
- $conn_r,
- 'SELECT * FROM %T %Q %Q %Q',
- $table->getTableName(),
- $this->buildWhereClause($conn_r),
- $this->buildOrderClause($conn_r),
- $this->buildLimitClause($conn_r));
-
- return $table->loadAllFromArray($rows);
- }
-
- protected function willFilterPage(array $projects) {
- assert_instances_of($projects, 'ReleephProject');
-
- $repository_phids = mpull($projects, 'getRepositoryPHID');
-
- $repositories = id(new PhabricatorRepositoryQuery())
- ->setViewer($this->getViewer())
- ->withPHIDs($repository_phids)
- ->execute();
- $repositories = mpull($repositories, null, 'getPHID');
-
- foreach ($projects as $key => $project) {
- $repo = idx($repositories, $project->getRepositoryPHID());
- if (!$repo) {
- unset($projects[$key]);
- continue;
- }
- $project->attachRepository($repo);
- }
-
- return $projects;
- }
-
- protected function buildWhereClause(AphrontDatabaseConnection $conn) {
- $where = array();
-
- if ($this->active !== null) {
- $where[] = qsprintf(
- $conn,
- 'isActive = %d',
- (int)$this->active);
- }
-
- if ($this->ids !== null) {
- $where[] = qsprintf(
- $conn,
- 'id IN (%Ls)',
- $this->ids);
- }
-
- if ($this->phids !== null) {
- $where[] = qsprintf(
- $conn,
- 'phid IN (%Ls)',
- $this->phids);
- }
-
- if ($this->repositoryPHIDs !== null) {
- $where[] = qsprintf(
- $conn,
- 'repositoryPHID IN (%Ls)',
- $this->repositoryPHIDs);
- }
-
- $where[] = $this->buildPagingClause($conn);
-
- return $this->formatWhereClause($conn, $where);
- }
-
- public function getOrderableColumns() {
- return parent::getOrderableColumns() + array(
- 'name' => array(
- 'column' => 'name',
- 'unique' => true,
- 'reverse' => true,
- 'type' => 'string',
- ),
- );
- }
-
- protected function newPagingMapFromPartialObject($object) {
- return array(
- 'id' => (int)$object->getID(),
- 'name' => $object->getName(),
- );
- }
-
- public function getQueryApplicationClass() {
- return 'PhabricatorReleephApplication';
- }
-
-}
diff --git a/src/applications/releeph/query/ReleephProductSearchEngine.php b/src/applications/releeph/query/ReleephProductSearchEngine.php
deleted file mode 100644
index d58ee735f1..0000000000
--- a/src/applications/releeph/query/ReleephProductSearchEngine.php
+++ /dev/null
@@ -1,134 +0,0 @@
-<?php
-
-final class ReleephProductSearchEngine
- extends PhabricatorApplicationSearchEngine {
-
- public function getResultTypeDescription() {
- return pht('Releeph Products');
- }
-
- public function getApplicationClassName() {
- return 'PhabricatorReleephApplication';
- }
-
- public function canUseInPanelContext() {
- return false;
- }
-
- public function buildSavedQueryFromRequest(AphrontRequest $request) {
- $saved = new PhabricatorSavedQuery();
-
- $saved->setParameter('active', $request->getStr('active'));
-
- return $saved;
- }
-
- public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
- $query = id(new ReleephProductQuery())
- ->setOrder(ReleephProductQuery::ORDER_NAME);
-
- $active = $saved->getParameter('active');
- $value = idx($this->getActiveValues(), $active);
- if ($value !== null) {
- $query->withActive($value);
- }
-
- return $query;
- }
-
- public function buildSearchForm(
- AphrontFormView $form,
- PhabricatorSavedQuery $saved_query) {
-
- $form->appendChild(
- id(new AphrontFormSelectControl())
- ->setName('active')
- ->setLabel(pht('Show Products'))
- ->setValue($saved_query->getParameter('active'))
- ->setOptions($this->getActiveOptions()));
- }
-
- protected function getURI($path) {
- return '/releeph/project/'.$path;
- }
-
- protected function getBuiltinQueryNames() {
- return array(
- 'active' => pht('Active'),
- 'all' => pht('All'),
- );
- }
-
- public function buildSavedQueryFromBuiltin($query_key) {
- $query = $this->newSavedQuery();
- $query->setQueryKey($query_key);
-
- switch ($query_key) {
- case 'active':
- return $query
- ->setParameter('active', 'active');
- case 'all':
- return $query;
- }
-
- return parent::buildSavedQueryFromBuiltin($query_key);
- }
-
- private function getActiveOptions() {
- return array(
- 'all' => pht('Active and Inactive Products'),
- 'active' => pht('Active Products'),
- 'inactive' => pht('Inactive Products'),
- );
- }
-
- private function getActiveValues() {
- return array(
- 'all' => null,
- 'active' => 1,
- 'inactive' => 0,
- );
- }
-
- protected function renderResultList(
- array $products,
- PhabricatorSavedQuery $query,
- array $handles) {
-
- assert_instances_of($products, 'ReleephProject');
- $viewer = $this->requireViewer();
-
- $list = id(new PHUIObjectItemListView())
- ->setUser($viewer);
-
- foreach ($products as $product) {
- $id = $product->getID();
-
- $item = id(new PHUIObjectItemView())
- ->setHeader($product->getName())
- ->setHref($this->getApplicationURI("product/{$id}/"));
-
- if (!$product->getIsActive()) {
- $item->setDisabled(true);
- $item->addIcon('none', pht('Inactive'));
- }
-
- $repo = $product->getRepository();
- $item->addAttribute(
- phutil_tag(
- 'a',
- array(
- 'href' => $repo->getURI(),
- ),
- $repo->getMonogram()));
-
- $list->addItem($item);
- }
-
- $result = new PhabricatorApplicationSearchResultView();
- $result->setObjectList($list);
-
- return $result;
- }
-
-}
diff --git a/src/applications/releeph/query/ReleephProductTransactionQuery.php b/src/applications/releeph/query/ReleephProductTransactionQuery.php
deleted file mode 100644
index 2a595da443..0000000000
--- a/src/applications/releeph/query/ReleephProductTransactionQuery.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-final class ReleephProductTransactionQuery
- extends PhabricatorApplicationTransactionQuery {
-
- public function getTemplateApplicationTransaction() {
- return new ReleephProductTransaction();
- }
-
-}
diff --git a/src/applications/releeph/query/ReleephRequestQuery.php b/src/applications/releeph/query/ReleephRequestQuery.php
deleted file mode 100644
index 3042260387..0000000000
--- a/src/applications/releeph/query/ReleephRequestQuery.php
+++ /dev/null
@@ -1,247 +0,0 @@
-<?php
-
-final class ReleephRequestQuery
- extends PhabricatorCursorPagedPolicyAwareQuery {
-
- private $requestedCommitPHIDs;
- private $ids;
- private $phids;
- private $severities;
- private $requestorPHIDs;
- private $branchIDs;
- private $requestedObjectPHIDs;
-
- const STATUS_ALL = 'status-all';
- const STATUS_OPEN = 'status-open';
- const STATUS_REQUESTED = 'status-requested';
- const STATUS_NEEDS_PULL = 'status-needs-pull';
- const STATUS_REJECTED = 'status-rejected';
- const STATUS_ABANDONED = 'status-abandoned';
- const STATUS_PULLED = 'status-pulled';
- const STATUS_NEEDS_REVERT = 'status-needs-revert';
- const STATUS_REVERTED = 'status-reverted';
-
- private $status = self::STATUS_ALL;
-
- public function withIDs(array $ids) {
- $this->ids = $ids;
- return $this;
- }
-
- public function withPHIDs(array $phids) {
- $this->phids = $phids;
- return $this;
- }
-
- public function withBranchIDs(array $branch_ids) {
- $this->branchIDs = $branch_ids;
- return $this;
- }
-
- public function withStatus($status) {
- $this->status = $status;
- return $this;
- }
-
- public function withRequestedCommitPHIDs(array $requested_commit_phids) {
- $this->requestedCommitPHIDs = $requested_commit_phids;
- return $this;
- }
-
- public function withRequestorPHIDs(array $phids) {
- $this->requestorPHIDs = $phids;
- return $this;
- }
-
- public function withSeverities(array $severities) {
- $this->severities = $severities;
- return $this;
- }
-
- public function withRequestedObjectPHIDs(array $phids) {
- $this->requestedObjectPHIDs = $phids;
- return $this;
- }
-
- protected function loadPage() {
- $table = new ReleephRequest();
- $conn_r = $table->establishConnection('r');
-
- $data = queryfx_all(
- $conn_r,
- 'SELECT * FROM %T %Q %Q %Q',
- $table->getTableName(),
- $this->buildWhereClause($conn_r),
- $this->buildOrderClause($conn_r),
- $this->buildLimitClause($conn_r));
-
- return $table->loadAllFromArray($data);
- }
-
- protected function willFilterPage(array $requests) {
- // Load requested objects: you must be able to see an object to see
- // requests for it.
- $object_phids = mpull($requests, 'getRequestedObjectPHID');
- $objects = id(new PhabricatorObjectQuery())
- ->setViewer($this->getViewer())
- ->setParentQuery($this)
- ->withPHIDs($object_phids)
- ->execute();
-
- foreach ($requests as $key => $request) {
- $object_phid = $request->getRequestedObjectPHID();
- $object = idx($objects, $object_phid);
- if (!$object) {
- unset($requests[$key]);
- continue;
- }
- $request->attachRequestedObject($object);
- }
-
- if ($this->severities) {
- $severities = array_fuse($this->severities);
- foreach ($requests as $key => $request) {
-
- // NOTE: Facebook uses a custom field here.
- if (ReleephDefaultFieldSelector::isFacebook()) {
- $severity = $request->getDetail('severity');
- } else {
- $severity = $request->getDetail('releeph:severity');
- }
-
- if (empty($severities[$severity])) {
- unset($requests[$key]);
- }
- }
- }
-
- $branch_ids = array_unique(mpull($requests, 'getBranchID'));
- $branches = id(new ReleephBranchQuery())
- ->withIDs($branch_ids)
- ->setViewer($this->getViewer())
- ->execute();
- $branches = mpull($branches, null, 'getID');
- foreach ($requests as $key => $request) {
- $branch = idx($branches, $request->getBranchID());
- if (!$branch) {
- unset($requests[$key]);
- continue;
- }
- $request->attachBranch($branch);
- }
-
- // TODO: These should be serviced by the query, but are not currently
- // denormalized anywhere. For now, filter them here instead. Note that
- // we must perform this filtering *after* querying and attaching branches,
- // because request status depends on the product.
-
- $keep_status = array_fuse($this->getKeepStatusConstants());
- if ($keep_status) {
- foreach ($requests as $key => $request) {
- if (empty($keep_status[$request->getStatus()])) {
- unset($requests[$key]);
- }
- }
- }
-
- return $requests;
- }
-
- protected function buildWhereClause(AphrontDatabaseConnection $conn) {
- $where = array();
-
- if ($this->ids !== null) {
- $where[] = qsprintf(
- $conn,
- 'id IN (%Ld)',
- $this->ids);
- }
-
- if ($this->phids !== null) {
- $where[] = qsprintf(
- $conn,
- 'phid IN (%Ls)',
- $this->phids);
- }
-
- if ($this->branchIDs !== null) {
- $where[] = qsprintf(
- $conn,
- 'branchID IN (%Ld)',
- $this->branchIDs);
- }
-
- if ($this->requestedCommitPHIDs !== null) {
- $where[] = qsprintf(
- $conn,
- 'requestCommitPHID IN (%Ls)',
- $this->requestedCommitPHIDs);
- }
-
- if ($this->requestorPHIDs !== null) {
- $where[] = qsprintf(
- $conn,
- 'requestUserPHID IN (%Ls)',
- $this->requestorPHIDs);
- }
-
- if ($this->requestedObjectPHIDs !== null) {
- $where[] = qsprintf(
- $conn,
- 'requestedObjectPHID IN (%Ls)',
- $this->requestedObjectPHIDs);
- }
-
- $where[] = $this->buildPagingClause($conn);
-
- return $this->formatWhereClause($conn, $where);
- }
-
- private function getKeepStatusConstants() {
- switch ($this->status) {
- case self::STATUS_ALL:
- return array();
- case self::STATUS_OPEN:
- return array(
- ReleephRequestStatus::STATUS_REQUESTED,
- ReleephRequestStatus::STATUS_NEEDS_PICK,
- ReleephRequestStatus::STATUS_NEEDS_REVERT,
- );
- case self::STATUS_REQUESTED:
- return array(
- ReleephRequestStatus::STATUS_REQUESTED,
- );
- case self::STATUS_NEEDS_PULL:
- return array(
- ReleephRequestStatus::STATUS_NEEDS_PICK,
- );
- case self::STATUS_REJECTED:
- return array(
- ReleephRequestStatus::STATUS_REJECTED,
- );
- case self::STATUS_ABANDONED:
- return array(
- ReleephRequestStatus::STATUS_ABANDONED,
- );
- case self::STATUS_PULLED:
- return array(
- ReleephRequestStatus::STATUS_PICKED,
- );
- case self::STATUS_NEEDS_REVERT:
- return array(
- ReleephRequestStatus::STATUS_NEEDS_REVERT,
- );
- case self::STATUS_REVERTED:
- return array(
- ReleephRequestStatus::STATUS_REVERTED,
- );
- default:
- throw new Exception(pht("Unknown status '%s'!", $this->status));
- }
- }
-
- public function getQueryApplicationClass() {
- return 'PhabricatorReleephApplication';
- }
-
-}
diff --git a/src/applications/releeph/query/ReleephRequestSearchEngine.php b/src/applications/releeph/query/ReleephRequestSearchEngine.php
deleted file mode 100644
index 7f866405c1..0000000000
--- a/src/applications/releeph/query/ReleephRequestSearchEngine.php
+++ /dev/null
@@ -1,225 +0,0 @@
-<?php
-
-final class ReleephRequestSearchEngine
- extends PhabricatorApplicationSearchEngine {
-
- private $branch;
- private $baseURI;
-
- public function getResultTypeDescription() {
- return pht('Releeph Pull Requests');
- }
-
- public function getApplicationClassName() {
- return 'PhabricatorReleephApplication';
- }
-
- public function canUseInPanelContext() {
- return false;
- }
-
- public function setBranch(ReleephBranch $branch) {
- $this->branch = $branch;
- return $this;
- }
-
- public function getBranch() {
- return $this->branch;
- }
-
- public function setBaseURI($base_uri) {
- $this->baseURI = $base_uri;
- return $this;
- }
-
- public function buildSavedQueryFromRequest(AphrontRequest $request) {
- $saved = new PhabricatorSavedQuery();
-
- $saved->setParameter('status', $request->getStr('status'));
- $saved->setParameter('severity', $request->getStr('severity'));
- $saved->setParameter(
- 'requestorPHIDs',
- $this->readUsersFromRequest($request, 'requestors'));
-
- return $saved;
- }
-
- public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
- $query = id(new ReleephRequestQuery())
- ->withBranchIDs(array($this->getBranch()->getID()));
-
- $status = $saved->getParameter('status');
- $status = idx($this->getStatusValues(), $status);
- if ($status) {
- $query->withStatus($status);
- }
-
- $severity = $saved->getParameter('severity');
- if ($severity) {
- $query->withSeverities(array($severity));
- }
-
- $requestor_phids = $saved->getParameter('requestorPHIDs');
- if ($requestor_phids) {
- $query->withRequestorPHIDs($requestor_phids);
- }
-
- return $query;
- }
-
- public function buildSearchForm(
- AphrontFormView $form,
- PhabricatorSavedQuery $saved_query) {
-
- $requestor_phids = $saved_query->getParameter('requestorPHIDs', array());
-
- $form
- ->appendChild(
- id(new AphrontFormSelectControl())
- ->setName('status')
- ->setLabel(pht('Status'))
- ->setValue($saved_query->getParameter('status'))
- ->setOptions($this->getStatusOptions()))
- ->appendChild(
- id(new AphrontFormSelectControl())
- ->setName('severity')
- ->setLabel(pht('Severity'))
- ->setValue($saved_query->getParameter('severity'))
- ->setOptions($this->getSeverityOptions()))
- ->appendControl(
- id(new AphrontFormTokenizerControl())
- ->setDatasource(new PhabricatorPeopleDatasource())
- ->setName('requestors')
- ->setLabel(pht('Requestors'))
- ->setValue($requestor_phids));
- }
-
- protected function getURI($path) {
- return $this->baseURI.$path;
- }
-
- protected function getBuiltinQueryNames() {
- $names = array(
- 'open' => pht('Open Requests'),
- 'all' => pht('All Requests'),
- );
-
- if ($this->requireViewer()->isLoggedIn()) {
- $names['requested'] = pht('Requested');
- }
-
- return $names;
- }
-
- public function buildSavedQueryFromBuiltin($query_key) {
-
- $query = $this->newSavedQuery();
- $query->setQueryKey($query_key);
-
- switch ($query_key) {
- case 'open':
- return $query->setParameter('status', 'open');
- case 'all':
- return $query;
- case 'requested':
- return $query->setParameter(
- 'requestorPHIDs',
- array($this->requireViewer()->getPHID()));
- }
-
- return parent::buildSavedQueryFromBuiltin($query_key);
- }
-
- private function getStatusOptions() {
- return array(
- '' => pht('(All Requests)'),
- 'open' => pht('Open Requests'),
- 'requested' => pht('Pull Requested'),
- 'needs-pull' => pht('Needs Pull'),
- 'rejected' => pht('Rejected'),
- 'abandoned' => pht('Abandoned'),
- 'pulled' => pht('Pulled'),
- 'needs-revert' => pht('Needs Revert'),
- 'reverted' => pht('Reverted'),
- );
- }
-
- private function getStatusValues() {
- return array(
- 'open' => ReleephRequestQuery::STATUS_OPEN,
- 'requested' => ReleephRequestQuery::STATUS_REQUESTED,
- 'needs-pull' => ReleephRequestQuery::STATUS_NEEDS_PULL,
- 'rejected' => ReleephRequestQuery::STATUS_REJECTED,
- 'abandoned' => ReleephRequestQuery::STATUS_ABANDONED,
- 'pulled' => ReleephRequestQuery::STATUS_PULLED,
- 'needs-revert' => ReleephRequestQuery::STATUS_NEEDS_REVERT,
- 'reverted' => ReleephRequestQuery::STATUS_REVERTED,
- );
- }
-
- private function getSeverityOptions() {
- if (ReleephDefaultFieldSelector::isFacebook()) {
- return array(
- '' => pht('(All Severities)'),
- 11 => pht('HOTFIX'),
- 12 => pht('PIGGYBACK'),
- 13 => pht('RELEASE'),
- 14 => pht('DAILY'),
- 15 => pht('PARKING'),
- );
- } else {
- return array(
- '' => pht('(All Severities)'),
- ReleephSeverityFieldSpecification::HOTFIX => pht('Hotfix'),
- ReleephSeverityFieldSpecification::RELEASE => pht('Release'),
- );
- }
- }
-
- protected function renderResultList(
- array $requests,
- PhabricatorSavedQuery $query,
- array $handles) {
-
- assert_instances_of($requests, 'ReleephRequest');
- $viewer = $this->requireViewer();
-
- // TODO: This is generally a bit sketchy, but we don't do this kind of
- // thing elsewhere at the moment. For the moment it shouldn't be hugely
- // costly, and we can batch things later. Generally, this commits fewer
- // sins than the old code did.
-
- $engine = id(new PhabricatorMarkupEngine())
- ->setViewer($viewer);
-
- $list = array();
- foreach ($requests as $pull) {
- $field_list = PhabricatorCustomField::getObjectFields(
- $pull,
- PhabricatorCustomField::ROLE_VIEW);
-
- $field_list
- ->setViewer($viewer)
- ->readFieldsFromStorage($pull);
-
- foreach ($field_list->getFields() as $field) {
- if ($field->shouldMarkup()) {
- $field->setMarkupEngine($engine);
- }
- }
-
- $list[] = id(new ReleephRequestView())
- ->setUser($viewer)
- ->setCustomFields($field_list)
- ->setPullRequest($pull)
- ->setIsListView(true);
- }
-
- // This is quite sketchy, but the list has not actually rendered yet, so
- // this still allows us to batch the markup rendering.
- $engine->process();
-
- return id(new PhabricatorApplicationSearchResultView())
- ->setContent($list);
- }
-}
diff --git a/src/applications/releeph/query/ReleephRequestTransactionQuery.php b/src/applications/releeph/query/ReleephRequestTransactionQuery.php
deleted file mode 100644
index 80c4f694f7..0000000000
--- a/src/applications/releeph/query/ReleephRequestTransactionQuery.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-final class ReleephRequestTransactionQuery
- extends PhabricatorApplicationTransactionQuery {
-
- public function getTemplateApplicationTransaction() {
- return new ReleephRequestTransaction();
- }
-
-}
diff --git a/src/applications/releeph/storage/ReleephBranch.php b/src/applications/releeph/storage/ReleephBranch.php
deleted file mode 100644
index 28323a9981..0000000000
--- a/src/applications/releeph/storage/ReleephBranch.php
+++ /dev/null
@@ -1,188 +0,0 @@
-<?php
-
-final class ReleephBranch extends ReleephDAO
- implements
- PhabricatorApplicationTransactionInterface,
- PhabricatorPolicyInterface {
-
- protected $releephProjectID;
- protected $isActive;
- protected $createdByUserPHID;
-
- // The immutable name of this branch ('releases/foo-2013.01.24')
- protected $name;
- protected $basename;
-
- // The symbolic name of this branch (LATEST, PRODUCTION, RC, ...)
- // See SYMBOLIC_NAME_NOTE below
- protected $symbolicName;
-
- // Where to cut the branch
- protected $cutPointCommitPHID;
-
- protected $details = array();
-
- private $project = self::ATTACHABLE;
- private $cutPointCommit = self::ATTACHABLE;
-
- protected function getConfiguration() {
- return array(
- self::CONFIG_AUX_PHID => true,
- self::CONFIG_SERIALIZATION => array(
- 'details' => self::SERIALIZATION_JSON,
- ),
- self::CONFIG_COLUMN_SCHEMA => array(
- 'basename' => 'text64',
- 'isActive' => 'bool',
- 'symbolicName' => 'text64?',
- 'name' => 'text128',
- ),
- self::CONFIG_KEY_SCHEMA => array(
- 'releephProjectID' => array(
- 'columns' => array('releephProjectID', 'symbolicName'),
- 'unique' => true,
- ),
- 'releephProjectID_2' => array(
- 'columns' => array('releephProjectID', 'basename'),
- 'unique' => true,
- ),
- 'releephProjectID_name' => array(
- 'columns' => array('releephProjectID', 'name'),
- 'unique' => true,
- ),
- ),
- ) + parent::getConfiguration();
- }
-
- public function generatePHID() {
- return PhabricatorPHID::generateNewPHID(ReleephBranchPHIDType::TYPECONST);
- }
-
- public function getDetail($key, $default = null) {
- return idx($this->getDetails(), $key, $default);
- }
-
- public function setDetail($key, $value) {
- $this->details[$key] = $value;
- return $this;
- }
-
- protected function willWriteData(array &$data) {
- // If symbolicName is omitted, set it to the basename.
- //
- // This means that we can enforce symbolicName as a UNIQUE column in the
- // DB. We'll interpret symbolicName === basename as meaning "no symbolic
- // name".
- //
- // SYMBOLIC_NAME_NOTE
- if (!$data['symbolicName']) {
- $data['symbolicName'] = $data['basename'];
- }
- parent::willWriteData($data);
- }
-
- public function getSymbolicName() {
- // See SYMBOLIC_NAME_NOTE above for why this is needed
- if ($this->symbolicName == $this->getBasename()) {
- return '';
- }
- return $this->symbolicName;
- }
-
- public function setSymbolicName($name) {
- if ($name) {
- parent::setSymbolicName($name);
- } else {
- parent::setSymbolicName($this->getBasename());
- }
- return $this;
- }
-
- public function getDisplayName() {
- if ($sn = $this->getSymbolicName()) {
- return $sn;
- }
- return $this->getBasename();
- }
-
- public function getDisplayNameWithDetail() {
- $n = $this->getBasename();
- if ($sn = $this->getSymbolicName()) {
- return "{$sn} ({$n})";
- } else {
- return $n;
- }
- }
-
- public function getURI($path = null) {
- $components = array(
- '/releeph/branch',
- $this->getID(),
- $path,
- );
- return implode('/', $components);
- }
-
- public function isActive() {
- return $this->getIsActive();
- }
-
- public function attachProject(ReleephProject $project) {
- $this->project = $project;
- return $this;
- }
-
- public function getProject() {
- return $this->assertAttached($this->project);
- }
-
- public function getProduct() {
- return $this->getProject();
- }
-
- public function attachCutPointCommit(
- PhabricatorRepositoryCommit $commit = null) {
- $this->cutPointCommit = $commit;
- return $this;
- }
-
- public function getCutPointCommit() {
- return $this->assertAttached($this->cutPointCommit);
- }
-
-
-/* -( PhabricatorApplicationTransactionInterface )------------------------- */
-
-
- public function getApplicationTransactionEditor() {
- return new ReleephBranchEditor();
- }
-
- public function getApplicationTransactionTemplate() {
- return new ReleephBranchTransaction();
- }
-
-
-/* -( PhabricatorPolicyInterface )----------------------------------------- */
-
-
- public function getCapabilities() {
- return $this->getProduct()->getCapabilities();
- }
-
- public function getPolicy($capability) {
- return $this->getProduct()->getPolicy($capability);
- }
-
- public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
- return $this->getProduct()->hasAutomaticCapability($capability, $viewer);
- }
-
- public function describeAutomaticCapability($capability) {
- return pht(
- 'Release branches have the same policies as the product they are a '.
- 'part of.');
- }
-
-
-}
diff --git a/src/applications/releeph/storage/ReleephBranchTransaction.php b/src/applications/releeph/storage/ReleephBranchTransaction.php
deleted file mode 100644
index 5dc2811c86..0000000000
--- a/src/applications/releeph/storage/ReleephBranchTransaction.php
+++ /dev/null
@@ -1,14 +0,0 @@
-<?php
-
-final class ReleephBranchTransaction
- extends PhabricatorApplicationTransaction {
-
- public function getApplicationName() {
- return 'releeph';
- }
-
- public function getApplicationTransactionType() {
- return ReleephBranchPHIDType::TYPECONST;
- }
-
-}
diff --git a/src/applications/releeph/storage/ReleephDAO.php b/src/applications/releeph/storage/ReleephDAO.php
deleted file mode 100644
index 638e16a73d..0000000000
--- a/src/applications/releeph/storage/ReleephDAO.php
+++ /dev/null
@@ -1,9 +0,0 @@
-<?php
-
-abstract class ReleephDAO extends PhabricatorLiskDAO {
-
- public function getApplicationName() {
- return 'releeph';
- }
-
-}
diff --git a/src/applications/releeph/storage/ReleephProductTransaction.php b/src/applications/releeph/storage/ReleephProductTransaction.php
deleted file mode 100644
index 6907ae9fbe..0000000000
--- a/src/applications/releeph/storage/ReleephProductTransaction.php
+++ /dev/null
@@ -1,108 +0,0 @@
-<?php
-
-final class ReleephProductTransaction
- extends PhabricatorApplicationTransaction {
-
- const TYPE_ACTIVE = 'releeph:product:active';
-
- public function getApplicationName() {
- return 'releeph';
- }
-
- public function getApplicationTransactionType() {
- return ReleephProductPHIDType::TYPECONST;
- }
-
- public function getColor() {
- $old = $this->getOldValue();
- $new = $this->getNewValue();
-
- switch ($this->getTransactionType()) {
- case self::TYPE_ACTIVE:
- if ($new) {
- return 'green';
- } else {
- return 'black';
- }
- break;
- }
-
- return parent::getColor();
- }
-
- public function getIcon() {
- $old = $this->getOldValue();
- $new = $this->getNewValue();
-
- switch ($this->getTransactionType()) {
- case self::TYPE_ACTIVE:
- if ($new) {
- return 'fa-pencil';
- } else {
- return 'fa-times';
- }
- break;
- }
-
- return parent::getIcon();
- }
-
- public function getTitle() {
- $author_phid = $this->getAuthorPHID();
-
- $old = $this->getOldValue();
- $new = $this->getNewValue();
-
- switch ($this->getTransactionType()) {
- case self::TYPE_ACTIVE:
- if ($new) {
- return pht(
- '%s activated this product.',
- $this->renderHandleLink($author_phid));
- } else {
- return pht(
- '%s deactivated this product.',
- $this->renderHandleLink($author_phid));
- }
- break;
- }
-
- return parent::getTitle();
- }
-
- public function getTitleForFeed() {
- $author_phid = $this->getAuthorPHID();
- $object_phid = $this->getObjectPHID();
-
- $old = $this->getOldValue();
- $new = $this->getNewValue();
-
- switch ($this->getTransactionType()) {
- case self::TYPE_ACTIVE:
- if ($new) {
- return pht(
- '%s activated release product %s.',
- $this->renderHandleLink($author_phid),
- $this->renderHandleLink($object_phid));
- } else {
- return pht(
- '%s deactivated release product %s.',
- $this->renderHandleLink($author_phid),
- $this->renderHandleLink($object_phid));
- }
- break;
- }
-
- return parent::getTitleForFeed();
- }
-
- public function getNoEffectDescription() {
- switch ($this->getTransactionType()) {
- case self::TYPE_ACTIVE:
- return pht('The product is already in that state.');
- }
-
- return parent::getNoEffectDescription();
- }
-
-}
diff --git a/src/applications/releeph/storage/ReleephProject.php b/src/applications/releeph/storage/ReleephProject.php
deleted file mode 100644
index db47ac5d39..0000000000
--- a/src/applications/releeph/storage/ReleephProject.php
+++ /dev/null
@@ -1,145 +0,0 @@
-<?php
-
-final class ReleephProject extends ReleephDAO
- implements
- PhabricatorApplicationTransactionInterface,
- PhabricatorPolicyInterface {
-
- const DEFAULT_BRANCH_NAMESPACE = 'releeph-releases';
- const SYSTEM_AGENT_USERNAME_PREFIX = 'releeph-agent-';
-
- protected $name;
-
- // Specifying the place to pick from is a requirement for svn, though not
- // for git. It's always useful though for reasoning about what revs have
- // been picked and which haven't.
- protected $trunkBranch;
-
- protected $repositoryPHID;
- protected $isActive;
- protected $createdByUserPHID;
-
- protected $details = array();
-
- private $repository = self::ATTACHABLE;
-
- protected function getConfiguration() {
- return array(
- self::CONFIG_AUX_PHID => true,
- self::CONFIG_SERIALIZATION => array(
- 'details' => self::SERIALIZATION_JSON,
- ),
- self::CONFIG_COLUMN_SCHEMA => array(
- 'name' => 'text128',
- 'trunkBranch' => 'text255',
- 'isActive' => 'bool',
- ),
- self::CONFIG_KEY_SCHEMA => array(
- 'projectName' => array(
- 'columns' => array('name'),
- 'unique' => true,
- ),
- ),
- ) + parent::getConfiguration();
- }
-
- public function generatePHID() {
- return PhabricatorPHID::generateNewPHID(ReleephProductPHIDType::TYPECONST);
- }
-
- public function getDetail($key, $default = null) {
- return idx($this->details, $key, $default);
- }
-
- public function getURI($path = null) {
- $components = array(
- '/releeph/product',
- $this->getID(),
- $path,
- );
- return implode('/', $components);
- }
-
- public function setDetail($key, $value) {
- $this->details[$key] = $value;
- return $this;
- }
-
- public function getPushers() {
- return $this->getDetail('pushers', array());
- }
-
- public function isPusher(PhabricatorUser $user) {
- // TODO Deprecate this once `isPusher` is out of the Facebook codebase.
- return $this->isAuthoritative($user);
- }
-
- public function isAuthoritative(PhabricatorUser $user) {
- return $this->isAuthoritativePHID($user->getPHID());
- }
-
- public function isAuthoritativePHID($phid) {
- $pushers = $this->getPushers();
- if (!$pushers) {
- return true;
- } else {
- return in_array($phid, $pushers);
- }
- }
-
- public function attachRepository(PhabricatorRepository $repository) {
- $this->repository = $repository;
- return $this;
- }
-
- public function getRepository() {
- return $this->assertAttached($this->repository);
- }
-
- public function getReleephFieldSelector() {
- return new ReleephDefaultFieldSelector();
- }
-
- public function isTestFile($filename) {
- $test_paths = $this->getDetail('testPaths', array());
-
- foreach ($test_paths as $test_path) {
- if (preg_match($test_path, $filename)) {
- return true;
- }
- }
- return false;
- }
-
-
-/* -( PhabricatorApplicationTransactionInterface )------------------------- */
-
-
- public function getApplicationTransactionEditor() {
- return new ReleephProductEditor();
- }
-
- public function getApplicationTransactionTemplate() {
- return new ReleephProductTransaction();
- }
-
-
-/* -( PhabricatorPolicyInterface )----------------------------------------- */
-
-
- public function getCapabilities() {
- return array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- );
- }
-
- public function getPolicy($capability) {
- return PhabricatorPolicies::POLICY_USER;
- }
-
- public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
- return false;
- }
-
-}
diff --git a/src/applications/releeph/storage/ReleephRequest.php b/src/applications/releeph/storage/ReleephRequest.php
deleted file mode 100644
index c21f9b28c8..0000000000
--- a/src/applications/releeph/storage/ReleephRequest.php
+++ /dev/null
@@ -1,354 +0,0 @@
-<?php
-
-final class ReleephRequest extends ReleephDAO
- implements
- PhabricatorApplicationTransactionInterface,
- PhabricatorPolicyInterface,
- PhabricatorCustomFieldInterface {
-
- protected $branchID;
- protected $requestUserPHID;
- protected $details = array();
- protected $userIntents = array();
- protected $inBranch;
- protected $pickStatus;
- protected $mailKey;
-
- /**
- * The object which is being requested. Normally this is a commit, but it
- * might also be a revision. In the future, it could be a repository branch
- * or an external object (like a GitHub pull request).
- */
- protected $requestedObjectPHID;
-
- // Information about the thing being requested
- protected $requestCommitPHID;
-
- // Information about the last commit to the releeph branch
- protected $commitIdentifier;
- protected $commitPHID;
-
-
- private $customFields = self::ATTACHABLE;
- private $branch = self::ATTACHABLE;
- private $requestedObject = self::ATTACHABLE;
-
-
-/* -( Constants and helper methods )--------------------------------------- */
-
- const INTENT_WANT = 'want';
- const INTENT_PASS = 'pass';
-
- const PICK_PENDING = 1; // old
- const PICK_FAILED = 2;
- const PICK_OK = 3;
- const PICK_MANUAL = 4; // old
- const REVERT_OK = 5;
- const REVERT_FAILED = 6;
-
- public function shouldBeInBranch() {
- return
- $this->getPusherIntent() == self::INTENT_WANT &&
- /**
- * We use "!= pass" instead of "== want" in case the requestor intent is
- * not present. In other words, only revert if the requestor explicitly
- * passed.
- */
- $this->getRequestorIntent() != self::INTENT_PASS;
- }
-
- /**
- * Will return INTENT_WANT if any pusher wants this request, and no pusher
- * passes on this request.
- */
- public function getPusherIntent() {
- $product = $this->getBranch()->getProduct();
-
- if (!$product->getPushers()) {
- return self::INTENT_WANT;
- }
-
- $found_pusher_want = false;
- foreach ($this->userIntents as $phid => $intent) {
- if ($product->isAuthoritativePHID($phid)) {
- if ($intent == self::INTENT_PASS) {
- return self::INTENT_PASS;
- }
-
- $found_pusher_want = true;
- }
- }
-
- if ($found_pusher_want) {
- return self::INTENT_WANT;
- } else {
- return null;
- }
- }
-
- public function getRequestorIntent() {
- return idx($this->userIntents, $this->requestUserPHID);
- }
-
- public function getStatus() {
- return $this->calculateStatus();
- }
-
- public function getMonogram() {
- return 'Y'.$this->getID();
- }
-
- public function getBranch() {
- return $this->assertAttached($this->branch);
- }
-
- public function attachBranch(ReleephBranch $branch) {
- $this->branch = $branch;
- return $this;
- }
-
- public function getRequestedObject() {
- return $this->assertAttached($this->requestedObject);
- }
-
- public function attachRequestedObject($object) {
- $this->requestedObject = $object;
- return $this;
- }
-
- private function calculateStatus() {
- if ($this->shouldBeInBranch()) {
- if ($this->getInBranch()) {
- return ReleephRequestStatus::STATUS_PICKED;
- } else {
- return ReleephRequestStatus::STATUS_NEEDS_PICK;
- }
- } else {
- if ($this->getInBranch()) {
- return ReleephRequestStatus::STATUS_NEEDS_REVERT;
- } else {
- $intent_pass = self::INTENT_PASS;
- $intent_want = self::INTENT_WANT;
-
- $has_been_in_branch = $this->getCommitIdentifier();
- // Regardless of why we reverted something, always say reverted if it
- // was once in the branch.
- if ($has_been_in_branch) {
- return ReleephRequestStatus::STATUS_REVERTED;
- } else if ($this->getPusherIntent() === $intent_pass) {
- // Otherwise, if it has never been in the branch, explicitly say why:
- return ReleephRequestStatus::STATUS_REJECTED;
- } else if ($this->getRequestorIntent() === $intent_want) {
- return ReleephRequestStatus::STATUS_REQUESTED;
- } else {
- return ReleephRequestStatus::STATUS_ABANDONED;
- }
- }
- }
- }
-
-
-/* -( Lisk mechanics )----------------------------------------------------- */
-
- protected function getConfiguration() {
- return array(
- self::CONFIG_AUX_PHID => true,
- self::CONFIG_SERIALIZATION => array(
- 'details' => self::SERIALIZATION_JSON,
- 'userIntents' => self::SERIALIZATION_JSON,
- ),
- self::CONFIG_COLUMN_SCHEMA => array(
- 'requestCommitPHID' => 'phid?',
- 'commitIdentifier' => 'text40?',
- 'commitPHID' => 'phid?',
- 'pickStatus' => 'uint32?',
- 'inBranch' => 'bool',
- 'mailKey' => 'bytes20',
- 'userIntents' => 'text?',
- ),
- self::CONFIG_KEY_SCHEMA => array(
- 'key_phid' => null,
- 'phid' => array(
- 'columns' => array('phid'),
- 'unique' => true,
- ),
- 'requestIdentifierBranch' => array(
- 'columns' => array('requestCommitPHID', 'branchID'),
- 'unique' => true,
- ),
- 'branchID' => array(
- 'columns' => array('branchID'),
- ),
- 'key_requestedObject' => array(
- 'columns' => array('requestedObjectPHID'),
- ),
- ),
- ) + parent::getConfiguration();
- }
-
- public function generatePHID() {
- return PhabricatorPHID::generateNewPHID(
- ReleephRequestPHIDType::TYPECONST);
- }
-
- public function save() {
- if (!$this->getMailKey()) {
- $this->setMailKey(Filesystem::readRandomCharacters(20));
- }
- return parent::save();
- }
-
-
-/* -( Helpful accessors )--------------------------------------------------- */
-
-
- public function getDetail($key, $default = null) {
- return idx($this->getDetails(), $key, $default);
- }
-
- public function setDetail($key, $value) {
- $this->details[$key] = $value;
- return $this;
- }
-
-
- /**
- * Get the commit PHIDs this request is requesting.
- *
- * NOTE: For now, this always returns one PHID.
- *
- * @return list<phid> Commit PHIDs requested by this request.
- */
- public function getCommitPHIDs() {
- return array(
- $this->requestCommitPHID,
- );
- }
-
- public function getReason() {
- // Backward compatibility: reason used to be called comments
- $reason = $this->getDetail('reason');
- if (!$reason) {
- return $this->getDetail('comments');
- }
- return $reason;
- }
-
- /**
- * Allow a null summary, and fall back to the title of the commit.
- */
- public function getSummaryForDisplay() {
- $summary = $this->getDetail('summary');
-
- if (!strlen($summary)) {
- $commit = $this->loadPhabricatorRepositoryCommit();
- if ($commit) {
- $summary = $commit->getSummary();
- }
- }
-
- if (!strlen($summary)) {
- $summary = pht('None');
- }
-
- return $summary;
- }
-
-/* -( Loading external objects )------------------------------------------- */
-
- public function loadPhabricatorRepositoryCommit() {
- return id(new PhabricatorRepositoryCommit())->loadOneWhere(
- 'phid = %s',
- $this->getRequestCommitPHID());
- }
-
- public function loadPhabricatorRepositoryCommitData() {
- $commit = $this->loadPhabricatorRepositoryCommit();
- if ($commit) {
- return id(new PhabricatorRepositoryCommitData())->loadOneWhere(
- 'commitID = %d',
- $commit->getID());
- }
- }
-
-
-/* -( State change helpers )----------------------------------------------- */
-
- public function setUserIntent(PhabricatorUser $user, $intent) {
- $this->userIntents[$user->getPHID()] = $intent;
- return $this;
- }
-
-
-/* -( Migrating to status-less ReleephRequests )--------------------------- */
-
- protected function didReadData() {
- if ($this->userIntents === null) {
- $this->userIntents = array();
- }
- }
-
- public function setStatus($value) {
- throw new Exception(pht('`%s` is now deprecated!', 'status'));
- }
-
-/* -( PhabricatorApplicationTransactionInterface )------------------------- */
-
-
- public function getApplicationTransactionEditor() {
- return new ReleephRequestTransactionalEditor();
- }
-
- public function getApplicationTransactionTemplate() {
- return new ReleephRequestTransaction();
- }
-
-
-/* -( PhabricatorPolicyInterface )----------------------------------------- */
-
-
- public function getCapabilities() {
- return array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- );
- }
-
- public function getPolicy($capability) {
- return $this->getBranch()->getPolicy($capability);
- }
-
- public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
- return $this->getBranch()->hasAutomaticCapability($capability, $viewer);
- }
-
- public function describeAutomaticCapability($capability) {
- return pht(
- 'Pull requests have the same policies as the branches they are '.
- 'requested against.');
- }
-
-
-
-/* -( PhabricatorCustomFieldInterface )------------------------------------ */
-
-
- public function getCustomFieldSpecificationForRole($role) {
- return PhabricatorEnv::getEnvConfig('releeph.fields');
- }
-
- public function getCustomFieldBaseClass() {
- return 'ReleephFieldSpecification';
- }
-
- public function getCustomFields() {
- return $this->assertAttached($this->customFields);
- }
-
- public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
- $this->customFields = $fields;
- return $this;
- }
-
-
-}
diff --git a/src/applications/releeph/storage/ReleephRequestTransaction.php b/src/applications/releeph/storage/ReleephRequestTransaction.php
deleted file mode 100644
index 6c7f98b3db..0000000000
--- a/src/applications/releeph/storage/ReleephRequestTransaction.php
+++ /dev/null
@@ -1,275 +0,0 @@
-<?php
-
-final class ReleephRequestTransaction
- extends PhabricatorApplicationTransaction {
-
- const TYPE_REQUEST = 'releeph:request';
- const TYPE_USER_INTENT = 'releeph:user_intent';
- const TYPE_EDIT_FIELD = 'releeph:edit_field';
- const TYPE_PICK_STATUS = 'releeph:pick_status';
- const TYPE_COMMIT = 'releeph:commit';
- const TYPE_DISCOVERY = 'releeph:discovery';
- const TYPE_MANUAL_IN_BRANCH = 'releeph:manual';
-
- public function getApplicationName() {
- return 'releeph';
- }
-
- public function getApplicationTransactionType() {
- return ReleephRequestPHIDType::TYPECONST;
- }
-
- public function getApplicationTransactionCommentObject() {
- return new ReleephRequestTransactionComment();
- }
-
- public function hasChangeDetails() {
- switch ($this->getTransactionType()) {
- default;
- break;
- }
- return parent::hasChangeDetails();
- }
-
- public function getRequiredHandlePHIDs() {
- $phids = parent::getRequiredHandlePHIDs();
- $phids[] = $this->getObjectPHID();
-
- $new = $this->getNewValue();
-
- switch ($this->getTransactionType()) {
- case self::TYPE_REQUEST:
- case self::TYPE_DISCOVERY:
- $phids[] = $new;
- break;
-
- case self::TYPE_EDIT_FIELD:
- self::searchForPHIDs($this->getOldValue(), $phids);
- self::searchForPHIDs($this->getNewValue(), $phids);
- break;
- }
-
- return $phids;
- }
-
- public function getTitle() {
- $author_phid = $this->getAuthorPHID();
- $object_phid = $this->getObjectPHID();
-
- $old = $this->getOldValue();
- $new = $this->getNewValue();
-
- switch ($this->getTransactionType()) {
- case self::TYPE_REQUEST:
- return pht(
- '%s requested %s',
- $this->renderHandleLink($author_phid),
- $this->renderHandleLink($new));
- break;
-
- case self::TYPE_USER_INTENT:
- return $this->getIntentTitle();
- break;
-
- case self::TYPE_EDIT_FIELD:
- $field = newv($this->getMetadataValue('fieldClass'), array());
- $name = $field->getName();
-
- $markup = $name;
- if ($this->getRenderingTarget() ===
- parent::TARGET_HTML) {
-
- $markup = hsprintf('<em>%s</em>', $name);
- }
-
- return pht(
- '%s changed the %s to "%s"',
- $this->renderHandleLink($author_phid),
- $markup,
- $field->normalizeForTransactionView($this, $new));
- break;
-
- case self::TYPE_PICK_STATUS:
- switch ($new) {
- case ReleephRequest::PICK_OK:
- return pht('%s found this request picks without error',
- $this->renderHandleLink($author_phid));
-
- case ReleephRequest::REVERT_OK:
- return pht('%s found this request reverts without error',
- $this->renderHandleLink($author_phid));
-
- case ReleephRequest::PICK_FAILED:
- return pht("%s couldn't pick this request",
- $this->renderHandleLink($author_phid));
-
- case ReleephRequest::REVERT_FAILED:
- return pht("%s couldn't revert this request",
- $this->renderHandleLink($author_phid));
- }
- break;
-
- case self::TYPE_COMMIT:
- $action_type = $this->getMetadataValue('action');
- switch ($action_type) {
- case 'pick':
- return pht(
- '%s picked this request and committed the result upstream',
- $this->renderHandleLink($author_phid));
- break;
-
- case 'revert':
- return pht(
- '%s reverted this request and committed the result upstream',
- $this->renderHandleLink($author_phid));
- break;
- }
- break;
-
- case self::TYPE_MANUAL_IN_BRANCH:
- $action = $new ? pht('picked') : pht('reverted');
- return pht(
- '%s marked this request as manually %s',
- $this->renderHandleLink($author_phid),
- $action);
- break;
-
- case self::TYPE_DISCOVERY:
- return pht('%s discovered this commit as %s',
- $this->renderHandleLink($author_phid),
- $this->renderHandleLink($new));
- break;
-
- default:
- return parent::getTitle();
- break;
- }
- }
-
- public function getActionName() {
- switch ($this->getTransactionType()) {
- case self::TYPE_REQUEST:
- return pht('Requested');
-
- case self::TYPE_COMMIT:
- $action_type = $this->getMetadataValue('action');
- switch ($action_type) {
- case 'pick':
- return pht('Picked');
-
- case 'revert':
- return pht('Reverted');
- }
- }
-
- return parent::getActionName();
- }
-
- public function getColor() {
- $new = $this->getNewValue();
-
- switch ($this->getTransactionType()) {
- case self::TYPE_USER_INTENT:
- switch ($new) {
- case ReleephRequest::INTENT_WANT:
- return PhabricatorTransactions::COLOR_GREEN;
- case ReleephRequest::INTENT_PASS:
- return PhabricatorTransactions::COLOR_RED;
- }
- }
- return parent::getColor();
- }
-
- private static function searchForPHIDs($thing, array &$phids) {
- /**
- * To implement something like getRequiredHandlePHIDs() in a
- * ReleephFieldSpecification, we'd have to provide the field with its
- * ReleephRequest (so that it could load the PHIDs from the
- * ReleephRequest's storage, and return them.)
- *
- * We don't have fields initialized with their ReleephRequests, but we can
- * make a good guess at what handles will be needed for rendering the field
- * in this transaction by inspecting the old and new values.
- */
- if (!is_array($thing)) {
- $thing = array($thing);
- }
-
- foreach ($thing as $value) {
- if (phid_get_type($value) !==
- PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) {
-
- $phids[] = $value;
- }
- }
- }
-
- private function getIntentTitle() {
- $author_phid = $this->getAuthorPHID();
- $object_phid = $this->getObjectPHID();
-
- $new = $this->getNewValue();
- $is_pusher = $this->getMetadataValue('isPusher');
-
- switch ($new) {
- case ReleephRequest::INTENT_WANT:
- if ($is_pusher) {
- return pht(
- '%s approved this request',
- $this->renderHandleLink($author_phid));
- } else {
- return pht(
- '%s wanted this request',
- $this->renderHandleLink($author_phid));
- }
-
- case ReleephRequest::INTENT_PASS:
- if ($is_pusher) {
- return pht(
- '%s rejected this request',
- $this->renderHandleLink($author_phid));
- } else {
- return pht(
- '%s passed on this request',
- $this->renderHandleLink($author_phid));
- }
- }
- }
-
- public function shouldHide() {
- $type = $this->getTransactionType();
-
- if ($type === self::TYPE_USER_INTENT &&
- $this->getMetadataValue('isRQCreate')) {
-
- return true;
- }
-
- if ($this->isBoringPickStatus()) {
- return true;
- }
-
- // ReleephSummaryFieldSpecification is usually blank when an RQ is created,
- // creating a transaction change from null to "". Hide these!
- if ($type === self::TYPE_EDIT_FIELD) {
- if ($this->getOldValue() === null && $this->getNewValue() === '') {
- return true;
- }
- }
- return parent::shouldHide();
- }
-
- public function isBoringPickStatus() {
- $type = $this->getTransactionType();
- if ($type === self::TYPE_PICK_STATUS) {
- $new = $this->getNewValue();
- if ($new === ReleephRequest::PICK_OK ||
- $new === ReleephRequest::REVERT_OK) {
-
- return true;
- }
- }
- return false;
- }
-
-}
diff --git a/src/applications/releeph/storage/ReleephRequestTransactionComment.php b/src/applications/releeph/storage/ReleephRequestTransactionComment.php
deleted file mode 100644
index 71844b7367..0000000000
--- a/src/applications/releeph/storage/ReleephRequestTransactionComment.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-final class ReleephRequestTransactionComment
- extends PhabricatorApplicationTransactionComment {
-
- public function getApplicationTransactionObject() {
- return new ReleephRequestTransaction();
- }
-
-}
diff --git a/src/applications/releeph/view/ReleephRequestView.php b/src/applications/releeph/view/ReleephRequestView.php
deleted file mode 100644
index ef00c5d63d..0000000000
--- a/src/applications/releeph/view/ReleephRequestView.php
+++ /dev/null
@@ -1,245 +0,0 @@
-<?php
-
-final class ReleephRequestView extends AphrontView {
-
- private $pullRequest;
- private $customFields;
- private $isListView;
-
- public function setIsListView($is_list_view) {
- $this->isListView = $is_list_view;
- return $this;
- }
-
- public function getIsListView() {
- return $this->isListView;
- }
-
- public function setCustomFields(PhabricatorCustomFieldList $custom_fields) {
- $this->customFields = $custom_fields;
- return $this;
- }
-
- public function getCustomFields() {
- return $this->customFields;
- }
-
- public function setPullRequest(ReleephRequest $pull_request) {
- $this->pullRequest = $pull_request;
- return $this;
- }
-
- public function getPullRequest() {
- return $this->pullRequest;
- }
-
- public function render() {
- $viewer = $this->getUser();
-
- $field_list = $this->getCustomFields();
- $pull = $this->getPullRequest();
-
- $header = $this->buildHeader($pull);
-
- $action_list = $this->buildActionList($pull);
-
- $property_list = id(new PHUIPropertyListView())
- ->setUser($viewer)
- ->setActionList($action_list);
-
- $field_list->appendFieldsToPropertyList(
- $pull,
- $viewer,
- $property_list);
-
- $warnings = $this->getWarnings($pull);
-
- if ($this->getIsListView()) {
- Javelin::initBehavior('releeph-request-state-change');
- }
-
- return id(new PHUIObjectBoxView())
- ->setHeader($header)
- ->setFormErrors($warnings)
- ->addSigil('releeph-request-box')
- ->setMetadata(array('uri' => '/'.$pull->getMonogram()))
- ->appendChild($property_list);
- }
-
- private function buildHeader(ReleephRequest $pull) {
- $header_text = $pull->getSummaryForDisplay();
- if ($this->getIsListView()) {
- $header_text = phutil_tag(
- 'a',
- array(
- 'href' => '/'.$pull->getMonogram(),
- ),
- $header_text);
- }
-
- $header = id(new PHUIHeaderView())
- ->setHeader($header_text)
- ->setUser($this->getUser())
- ->setPolicyObject($pull);
-
- switch ($pull->getStatus()) {
- case ReleephRequestStatus::STATUS_REQUESTED:
- $icon = 'open';
- $color = null;
- break;
- case ReleephRequestStatus::STATUS_REJECTED:
- $icon = 'reject';
- $color = 'red';
- break;
- case ReleephRequestStatus::STATUS_PICKED:
- $icon = 'accept';
- $color = 'green';
- break;
- case ReleephRequestStatus::STATUS_REVERTED:
- case ReleephRequestStatus::STATUS_ABANDONED:
- $icon = 'reject';
- $color = 'dark';
- break;
- case ReleephRequestStatus::STATUS_NEEDS_PICK:
- $icon = 'warning';
- $color = 'green';
- break;
- case ReleephRequestStatus::STATUS_NEEDS_REVERT:
- $icon = 'warning';
- $color = 'red';
- break;
- default:
- $icon = 'question';
- $color = null;
- break;
- }
- $text = ReleephRequestStatus::getStatusDescriptionFor($pull->getStatus());
- $header->setStatus($icon, $color, $text);
-
- return $header;
- }
-
- private function buildActionList(ReleephRequest $pull) {
- $viewer = $this->getUser();
- $id = $pull->getID();
-
- $edit_uri = '/releeph/request/edit/'.$id.'/';
-
- $view = id(new PhabricatorActionListView())
- ->setUser($viewer);
-
- $product = $pull->getBranch()->getProduct();
- $viewer_is_pusher = $product->isAuthoritativePHID($viewer->getPHID());
- $viewer_is_requestor = ($pull->getRequestUserPHID() == $viewer->getPHID());
-
- if ($viewer_is_pusher) {
- $yes_text = pht('Approve Pull');
- $no_text = pht('Reject Pull');
- $yes_icon = 'fa-check';
- $no_icon = 'fa-times';
- } else if ($viewer_is_requestor) {
- $yes_text = pht('Request Pull');
- $no_text = pht('Cancel Pull');
- $yes_icon = 'fa-check';
- $no_icon = 'fa-times';
- } else {
- $yes_text = pht('Support Pull');
- $no_text = pht('Discourage Pull');
- $yes_icon = 'fa-thumbs-o-up';
- $no_icon = 'fa-thumbs-o-down';
- }
-
- $yes_href = '/releeph/request/action/want/'.$id.'/';
- $no_href = '/releeph/request/action/pass/'.$id.'/';
-
- $intents = $pull->getUserIntents();
- $current_intent = idx($intents, $viewer->getPHID());
-
- $yes_disabled = ($current_intent == ReleephRequest::INTENT_WANT);
- $no_disabled = ($current_intent == ReleephRequest::INTENT_PASS);
-
- $use_workflow = (!$this->getIsListView());
-
- $view->addAction(
- id(new PhabricatorActionView())
- ->setName($yes_text)
- ->setHref($yes_href)
- ->setWorkflow($use_workflow)
- ->setRenderAsForm($use_workflow)
- ->setDisabled($yes_disabled)
- ->addSigil('releeph-request-state-change')
- ->addSigil('want')
- ->setIcon($yes_icon));
-
- $view->addAction(
- id(new PhabricatorActionView())
- ->setName($no_text)
- ->setHref($no_href)
- ->setWorkflow($use_workflow)
- ->setRenderAsForm($use_workflow)
- ->setDisabled($no_disabled)
- ->addSigil('releeph-request-state-change')
- ->addSigil('pass')
- ->setIcon($no_icon));
-
-
- if ($viewer_is_pusher || $viewer_is_requestor) {
-
- $pulled_href = '/releeph/request/action/mark-manually-picked/'.$id.'/';
- $revert_href = '/releeph/request/action/mark-manually-reverted/'.$id.'/';
-
- if ($pull->getInBranch()) {
- $view->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Mark as Reverted'))
- ->setHref($revert_href)
- ->setWorkflow($use_workflow)
- ->setRenderAsForm($use_workflow)
- ->addSigil('releeph-request-state-change')
- ->addSigil('mark-manually-reverted')
- ->setIcon($no_icon));
- } else {
- $view->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Mark as Pulled'))
- ->setHref($pulled_href)
- ->setWorkflow($use_workflow)
- ->setRenderAsForm($use_workflow)
- ->addSigil('releeph-request-state-change')
- ->addSigil('mark-manually-picked')
- ->setIcon('fa-exclamation-triangle'));
- }
- }
-
-
- if (!$this->getIsListView()) {
- $view->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Edit Pull Request'))
- ->setIcon('fa-pencil')
- ->setHref($edit_uri));
- }
-
- return $view;
- }
-
- private function getWarnings(ReleephRequest $pull) {
- $warnings = array();
-
- switch ($pull->getStatus()) {
- case ReleephRequestStatus::STATUS_NEEDS_PICK:
- if ($pull->getPickStatus() == ReleephRequest::PICK_FAILED) {
- $warnings[] = pht('Last pull failed!');
- }
- break;
- case ReleephRequestStatus::STATUS_NEEDS_REVERT:
- if ($pull->getPickStatus() == ReleephRequest::REVERT_FAILED) {
- $warnings[] = pht('Last revert failed!');
- }
- break;
- }
-
- return $warnings;
- }
-
-}
diff --git a/src/applications/releeph/view/branch/ReleephBranchPreviewView.php b/src/applications/releeph/view/branch/ReleephBranchPreviewView.php
deleted file mode 100644
index 462ed73db3..0000000000
--- a/src/applications/releeph/view/branch/ReleephBranchPreviewView.php
+++ /dev/null
@@ -1,62 +0,0 @@
-<?php
-
-final class ReleephBranchPreviewView extends AphrontFormControl {
-
- private $statics = array();
- private $dynamics = array();
-
- public function addControl($param_name, AphrontFormControl $control) {
- $celerity_id = celerity_generate_unique_node_id();
- $control->setID($celerity_id);
- $this->dynamics[$param_name] = $celerity_id;
- return $this;
- }
-
- public function addStatic($param_name, $value) {
- $this->statics[$param_name] = $value;
- return $this;
- }
-
- protected function getCustomControlClass() {
- require_celerity_resource('releeph-preview-branch');
- return 'releeph-preview-branch';
- }
-
- protected function renderInput() {
- static $required_params = array(
- 'repositoryPHID',
- 'projectName',
- 'isSymbolic',
- 'template',
- );
-
- $all_params = array_merge($this->statics, $this->dynamics);
- foreach ($required_params as $param_name) {
- if (idx($all_params, $param_name) === null) {
- throw new Exception(
- pht(
- "'%s' is not set as either a static or dynamic!",
- $param_name));
- }
- }
-
- $output_id = celerity_generate_unique_node_id();
-
- Javelin::initBehavior('releeph-preview-branch', array(
- 'uri' => '/releeph/branch/preview/',
- 'outputID' => $output_id,
- 'params' => array(
- 'static' => $this->statics,
- 'dynamic' => $this->dynamics,
- ),
- ));
-
- return phutil_tag(
- 'div',
- array(
- 'id' => $output_id,
- ),
- '');
- }
-
-}
diff --git a/src/applications/releeph/view/branch/ReleephBranchTemplate.php b/src/applications/releeph/view/branch/ReleephBranchTemplate.php
deleted file mode 100644
index f29e7a9fc2..0000000000
--- a/src/applications/releeph/view/branch/ReleephBranchTemplate.php
+++ /dev/null
@@ -1,195 +0,0 @@
-<?php
-
-final class ReleephBranchTemplate extends Phobject {
-
- const KEY = 'releeph.default-branch-template';
-
- private $commitHandle;
- private $branchDate = null;
- private $projectName;
- private $isSymbolic;
-
- public static function getDefaultTemplate() {
- return PhabricatorEnv::getEnvConfig(self::KEY);
- }
-
- public static function getRequiredDefaultTemplate() {
- $template = self::getDefaultTemplate();
- if (!$template) {
- throw new Exception(pht(
- "Config setting '%s' must be set, ".
- "or you must provide a branch-template for each project!",
- self::KEY));
- }
- return $template;
- }
-
- public static function getFakeCommitHandleFor(
- $repository_phid,
- PhabricatorUser $viewer) {
-
- $repository = id(new PhabricatorRepositoryQuery())
- ->setViewer($viewer)
- ->withPHIDs(array($repository_phid))
- ->executeOne();
-
- $fake_handle = 'SOFAKE';
- if ($repository) {
- $fake_handle = id(new PhabricatorObjectHandle())
- ->setName($repository->formatCommitName('100000000000'));
- }
- return $fake_handle;
- }
-
- public function setCommitHandle(PhabricatorObjectHandle $handle) {
- $this->commitHandle = $handle;
- return $this;
- }
-
- public function setBranchDate($branch_date) {
- $this->branchDate = $branch_date;
- return $this;
- }
-
- public function setReleephProjectName($project_name) {
- $this->projectName = $project_name;
- return $this;
- }
-
- public function setSymbolic($is_symbolic) {
- $this->isSymbolic = $is_symbolic;
- return $this;
- }
-
- public function interpolate($template) {
- if (!$this->projectName) {
- return array('', array());
- }
-
- list($name, $name_errors) = $this->interpolateInner(
- $template,
- $this->isSymbolic);
-
- if ($this->isSymbolic) {
- return array($name, $name_errors);
- } else {
- $validate_errors = $this->validateAsBranchName($name);
- $errors = array_merge($name_errors, $validate_errors);
- return array($name, $errors);
- }
- }
-
- /*
- * xsprintf() would be useful here, but that's for formatting concrete lists
- * of things in a certain way...
- *
- * animal_printf('%A %A %A', $dog1, $dog2, $dog3);
- *
- * ...rather than interpolating percent-control-strings like strftime does.
- */
- private function interpolateInner($template, $is_symbolic) {
- $name = $template;
- $errors = array();
-
- $safe_project_name = str_replace(' ', '-', $this->projectName);
- $short_commit_id = last(
- preg_split('/r[A-Z]+/', $this->commitHandle->getName()));
-
- $interpolations = array();
- for ($ii = 0; $ii < strlen($name); $ii++) {
- $char = substr($name, $ii, 1);
- $prev = null;
- if ($ii > 0) {
- $prev = substr($name, $ii - 1, 1);
- }
- $next = substr($name, $ii + 1, 1);
- if ($next && $char == '%' && $prev != '%') {
- $interpolations[$ii] = $next;
- }
- }
-
- $variable_interpolations = array();
-
- $reverse_interpolations = $interpolations;
- krsort($reverse_interpolations);
-
- if ($this->branchDate) {
- $branch_date = $this->branchDate;
- } else {
- $branch_date = $this->commitHandle->getTimestamp();
- }
-
- foreach ($reverse_interpolations as $position => $code) {
- $replacement = null;
- switch ($code) {
- case 'v':
- $replacement = $this->commitHandle->getName();
- $is_variable = true;
- break;
-
- case 'V':
- $replacement = $short_commit_id;
- $is_variable = true;
- break;
-
- case 'P':
- $replacement = $safe_project_name;
- $is_variable = false;
- break;
-
- case 'p':
- $replacement = strtolower($safe_project_name);
- $is_variable = false;
- break;
-
- default:
- // Format anything else using strftime()
- $replacement = strftime("%{$code}", $branch_date);
- $is_variable = true;
- break;
- }
-
- if ($is_variable) {
- $variable_interpolations[] = $code;
- }
- $name = substr_replace($name, $replacement, $position, 2);
- }
-
- if (!$is_symbolic && !$variable_interpolations) {
- $errors[] = pht("Include additional interpolations that aren't static!");
- }
-
- return array($name, $errors);
- }
-
- private function validateAsBranchName($name) {
- $errors = array();
-
- if (preg_match('{^/}', $name) || preg_match('{/$}', $name)) {
- $errors[] = pht("Branches cannot begin or end with '%s'", '/');
- }
-
- if (preg_match('{//+}', $name)) {
- $errors[] = pht("Branches cannot contain multiple consecutive '%s'", '/');
- }
-
- $parts = array_filter(explode('/', $name));
- foreach ($parts as $index => $part) {
- $part_error = null;
- if (preg_match('{^\.}', $part) || preg_match('{\.$}', $part)) {
- $errors[] = pht("Path components cannot begin or end with '%s'", '.');
- } else if (preg_match('{^(?!\w)}', $part)) {
- $errors[] = pht('Path components must begin with an alphanumeric.');
- } else if (!preg_match('{^\w ([\w-_%\.]* [\w-_%])?$}x', $part)) {
- $errors[] = pht(
- "Path components may only contain alphanumerics ".
- "or '%s', '%s' or '%s'.",
- '-',
- '_',
- '.');
- }
- }
-
- return $errors;
- }
-}
diff --git a/src/applications/releeph/view/request/ReleephRequestTypeaheadControl.php b/src/applications/releeph/view/request/ReleephRequestTypeaheadControl.php
deleted file mode 100644
index e8ed53f946..0000000000
--- a/src/applications/releeph/view/request/ReleephRequestTypeaheadControl.php
+++ /dev/null
@@ -1,60 +0,0 @@
-<?php
-
-final class ReleephRequestTypeaheadControl extends AphrontFormControl {
-
- const PLACEHOLDER = 'Type a commit id or first line of commit message...';
-
- private $repo;
- private $startTime;
-
- public function setRepo(PhabricatorRepository $repo) {
- $this->repo = $repo;
- return $this;
- }
-
- public function setStartTime($epoch) {
- $this->startTime = $epoch;
- return $this;
- }
-
- protected function getCustomControlClass() {
- return 'releeph-request-typeahead';
- }
-
- protected function renderInput() {
- $id = celerity_generate_unique_node_id();
-
- $div = phutil_tag(
- 'div',
- array(
- 'style' => 'position: relative;',
- 'id' => $id,
- ),
- phutil_tag(
- 'input',
- array(
- 'autocomplete' => 'off',
- 'type' => 'text',
- 'name' => $this->getName(),
- ),
- ''));
-
- require_celerity_resource('releeph-request-typeahead-css');
-
- Javelin::initBehavior('releeph-request-typeahead', array(
- 'id' => $id,
- 'src' => '/releeph/request/typeahead/',
- 'placeholder' => self::PLACEHOLDER,
- 'value' => $this->getValue(),
- 'aux' => array(
- 'repo' => $this->repo->getID(),
- 'callsign' => $this->repo->getCallsign(),
- 'since' => $this->startTime,
- 'limit' => 16,
- ),
- ));
-
- return $div;
- }
-
-}
diff --git a/src/applications/remarkup/RemarkupValue.php b/src/applications/remarkup/RemarkupValue.php
new file mode 100644
index 0000000000..84546ee2b9
--- /dev/null
+++ b/src/applications/remarkup/RemarkupValue.php
@@ -0,0 +1,27 @@
+<?php
+
+final class RemarkupValue
+ extends Phobject {
+
+ private $corpus;
+ private $metadata;
+
+ public function setCorpus($corpus) {
+ $this->corpus = $corpus;
+ return $this;
+ }
+
+ public function getCorpus() {
+ return $this->corpus;
+ }
+
+ public function setMetadata(array $metadata) {
+ $this->metadata = $metadata;
+ return $this;
+ }
+
+ public function getMetadata() {
+ return $this->metadata;
+ }
+
+}
diff --git a/src/applications/remarkup/conduit/RemarkupProcessConduitAPIMethod.php b/src/applications/remarkup/conduit/RemarkupProcessConduitAPIMethod.php
index f9169cd08c..ae181f9860 100644
--- a/src/applications/remarkup/conduit/RemarkupProcessConduitAPIMethod.php
+++ b/src/applications/remarkup/conduit/RemarkupProcessConduitAPIMethod.php
@@ -1,76 +1,76 @@
<?php
final class RemarkupProcessConduitAPIMethod extends ConduitAPIMethod {
public function getAPIMethodName() {
return 'remarkup.process';
}
public function getMethodStatus() {
return self::METHOD_STATUS_UNSTABLE;
}
public function getMethodDescription() {
- return pht('Process text through remarkup in Phabricator context.');
+ return pht('Process text through remarkup.');
}
protected function defineReturnType() {
return 'nonempty dict';
}
protected function defineErrorTypes() {
return array(
'ERR-NO-CONTENT' => pht('Content may not be empty.'),
'ERR-INVALID-ENGINE' => pht('Invalid markup engine.'),
);
}
protected function defineParamTypes() {
$available_contexts = array_keys($this->getEngineContexts());
$available_const = $this->formatStringConstants($available_contexts);
return array(
'context' => 'required '.$available_const,
'contents' => 'required list<string>',
);
}
protected function execute(ConduitAPIRequest $request) {
$contents = $request->getValue('contents');
$context = $request->getValue('context');
$engine_class = idx($this->getEngineContexts(), $context);
if (!$engine_class) {
throw new ConduitException('ERR-INVALID_ENGINE');
}
$engine = PhabricatorMarkupEngine::$engine_class();
$engine->setConfig('viewer', $request->getUser());
$results = array();
foreach ($contents as $content) {
$text = $engine->markupText($content);
if ($text) {
$content = hsprintf('%s', $text)->getHTMLContent();
} else {
$content = '';
}
$results[] = array(
'content' => $content,
);
}
return $results;
}
private function getEngineContexts() {
return array(
'phriction' => 'newPhrictionMarkupEngine',
'maniphest' => 'newManiphestMarkupEngine',
'differential' => 'newDifferentialMarkupEngine',
'phame' => 'newPhameMarkupEngine',
'feed' => 'newFeedMarkupEngine',
'diffusion' => 'newDiffusionMarkupEngine',
);
}
}
diff --git a/src/applications/repository/config/PhabricatorRepositoryConfigOptions.php b/src/applications/repository/config/PhabricatorRepositoryConfigOptions.php
index 26e0e9d5d1..f01515501a 100644
--- a/src/applications/repository/config/PhabricatorRepositoryConfigOptions.php
+++ b/src/applications/repository/config/PhabricatorRepositoryConfigOptions.php
@@ -1,37 +1,38 @@
<?php
final class PhabricatorRepositoryConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Repositories');
}
public function getDescription() {
return pht('Configure repositories.');
}
public function getIcon() {
return 'fa-hdd-o';
}
public function getGroup() {
return 'apps';
}
public function getOptions() {
return array(
$this->newOption('repository.default-local-path', 'string', '/var/repo/')
->setLocked(true)
->setSummary(
pht('Default location to store local copies of repositories.'))
->setDescription(
pht(
'The default location in which to store working copies and other '.
- 'data about repositories. Phabricator will control and manage '.
+ 'data about repositories. %s will control and manage '.
'data here, so you should **not** choose an existing directory '.
- 'full of data you care about.')),
+ 'full of data you care about.',
+ PlatformSymbols::getPlatformServerName())),
);
}
}
diff --git a/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php b/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php
index ed856957f0..32689d3a77 100644
--- a/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php
+++ b/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php
@@ -1,133 +1,133 @@
<?php
/**
* Pushes a repository to its mirrors.
*/
final class PhabricatorRepositoryMirrorEngine
extends PhabricatorRepositoryEngine {
public function pushToMirrors() {
$viewer = $this->getViewer();
$repository = $this->getRepository();
if (!$repository->canMirror()) {
return;
}
if (PhabricatorEnv::getEnvConfig('phabricator.silent')) {
$this->log(
- pht('Phabricator is running in silent mode; declining to mirror.'));
+ pht('This software is running in silent mode; declining to mirror.'));
return;
}
$uris = id(new PhabricatorRepositoryURIQuery())
->setViewer($viewer)
->withRepositories(array($repository))
->execute();
$io_mirror = PhabricatorRepositoryURI::IO_MIRROR;
$exceptions = array();
foreach ($uris as $mirror) {
if ($mirror->getIsDisabled()) {
continue;
}
$io_type = $mirror->getEffectiveIOType();
if ($io_type != $io_mirror) {
continue;
}
try {
$this->pushRepositoryToMirror($repository, $mirror);
} catch (Exception $ex) {
$exceptions[] = $ex;
}
}
if ($exceptions) {
throw new PhutilAggregateException(
pht(
'Exceptions occurred while mirroring the "%s" repository.',
$repository->getDisplayName()),
$exceptions);
}
}
private function pushRepositoryToMirror(
PhabricatorRepository $repository,
PhabricatorRepositoryURI $mirror_uri) {
$this->log(
pht(
'Pushing to remote "%s"...',
$mirror_uri->getEffectiveURI()));
if ($repository->isGit()) {
$this->pushToGitRepository($repository, $mirror_uri);
} else if ($repository->isHg()) {
$this->pushToHgRepository($repository, $mirror_uri);
} else {
throw new Exception(pht('Unsupported VCS!'));
}
}
private function pushToGitRepository(
PhabricatorRepository $repository,
PhabricatorRepositoryURI $mirror_uri) {
// See T5965. Test if we have any refs to mirror. If we have nothing, git
// will exit with an error ("No refs in common and none specified; ...")
// when we run "git push --mirror".
// If we don't have any refs, we just bail out. (This is arguably sort of
// the wrong behavior: to mirror an empty repository faithfully we should
// delete everything in the remote.)
list($stdout) = $repository->execxLocalCommand(
'for-each-ref --count 1 --');
if (!strlen($stdout)) {
return;
}
$argv = array(
'push --verbose --mirror -- %P',
$mirror_uri->getURIEnvelope(),
);
$future = $mirror_uri->newCommandEngine()
->setArgv($argv)
->newFuture();
$future
->setCWD($repository->getLocalPath())
->resolvex();
}
private function pushToHgRepository(
PhabricatorRepository $repository,
PhabricatorRepositoryURI $mirror_uri) {
$argv = array(
'push --verbose --rev tip -- %P',
$mirror_uri->getURIEnvelope(),
);
$future = $mirror_uri->newCommandEngine()
->setArgv($argv)
->newFuture();
try {
$future
->setCWD($repository->getLocalPath())
->resolvex();
} catch (CommandException $ex) {
if (preg_match('/no changes found/', $ex->getStdout())) {
// mercurial says nothing changed, but that's good
} else {
throw $ex;
}
}
}
}
diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementCacheWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementCacheWorkflow.php
index a78dff1a73..81955f36f3 100644
--- a/src/applications/repository/management/PhabricatorRepositoryManagementCacheWorkflow.php
+++ b/src/applications/repository/management/PhabricatorRepositoryManagementCacheWorkflow.php
@@ -1,94 +1,94 @@
<?php
final class PhabricatorRepositoryManagementCacheWorkflow
extends PhabricatorRepositoryManagementWorkflow {
protected function didConstruct() {
$this
->setName('cache')
->setExamples(
'**cache** [__options__] --commit __commit__ --path __path__')
->setSynopsis(pht('Manage the repository graph cache.'))
->setArguments(
array(
array(
'name' => 'commit',
'param' => 'commit',
'help' => pht('Specify a commit to look up.'),
),
array(
'name' => 'path',
'param' => 'path',
'help' => pht('Specify a path to look up.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$commit_name = $args->getArg('commit');
if ($commit_name === null) {
throw new PhutilArgumentUsageException(
pht(
'Specify a commit to look up with `%s`.',
'--commit'));
}
$commit = $this->loadNamedCommit($commit_name);
$path_name = $args->getArg('path');
if ($path_name === null) {
throw new PhutilArgumentUsageException(
pht(
'Specify a path to look up with `%s`.',
'--path'));
}
$path_map = id(new DiffusionPathIDQuery(array($path_name)))
->loadPathIDs();
if (empty($path_map[$path_name])) {
throw new PhutilArgumentUsageException(
- pht('Path "%s" is not known to Phabricator.', $path_name));
+ pht('Path "%s" is not unknown.', $path_name));
}
$path_id = $path_map[$path_name];
$graph_cache = new PhabricatorRepositoryGraphCache();
$t_start = microtime(true);
$cache_result = $graph_cache->loadLastModifiedCommitID(
$commit->getID(),
$path_id);
$t_end = microtime(true);
$console = PhutilConsole::getConsole();
$console->writeOut(
"%s\n",
pht('Query took %s ms.', new PhutilNumber(1000 * ($t_end - $t_start))));
if ($cache_result === false) {
$console->writeOut("%s\n", pht('Not found in graph cache.'));
} else if ($cache_result === null) {
$console->writeOut(
"%s\n",
pht('Path not modified in any ancestor commit.'));
} else {
$last = id(new DiffusionCommitQuery())
->setViewer($this->getViewer())
->withIDs(array($cache_result))
->executeOne();
if (!$last) {
throw new Exception(pht('Cache returned bogus result!'));
}
$console->writeOut(
"%s\n",
pht(
'Path was last changed at %s.',
$commit->getRepository()->formatCommitName(
$last->getcommitIdentifier())));
}
return 0;
}
}
diff --git a/src/applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php b/src/applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php
index cd693be605..08f0ada159 100644
--- a/src/applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php
+++ b/src/applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php
@@ -1,64 +1,60 @@
<?php
final class PhabricatorRepositoryGitLFSRefQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $repositoryPHIDs;
private $objectHashes;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withRepositoryPHIDs(array $phids) {
$this->repositoryPHIDs = $phids;
return $this;
}
public function withObjectHashes(array $hashes) {
$this->objectHashes = $hashes;
return $this;
}
public function newResultObject() {
return new PhabricatorRepositoryGitLFSRef();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->repositoryPHIDs !== null) {
$where[] = qsprintf(
$conn,
'repositoryPHID IN (%Ls)',
$this->repositoryPHIDs);
}
if ($this->objectHashes !== null) {
$where[] = qsprintf(
$conn,
'objectHash IN (%Ls)',
$this->objectHashes);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorDiffusionApplication';
}
}
diff --git a/src/applications/repository/query/PhabricatorRepositoryIdentityQuery.php b/src/applications/repository/query/PhabricatorRepositoryIdentityQuery.php
index 7de97de4d6..2b05b542d5 100644
--- a/src/applications/repository/query/PhabricatorRepositoryIdentityQuery.php
+++ b/src/applications/repository/query/PhabricatorRepositoryIdentityQuery.php
@@ -1,160 +1,156 @@
<?php
final class PhabricatorRepositoryIdentityQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $identityNames;
private $emailAddresses;
private $assignedPHIDs;
private $effectivePHIDs;
private $identityNameLike;
private $hasEffectivePHID;
private $relatedPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withIdentityNames(array $names) {
$this->identityNames = $names;
return $this;
}
public function withIdentityNameLike($name_like) {
$this->identityNameLike = $name_like;
return $this;
}
public function withEmailAddresses(array $addresses) {
$this->emailAddresses = $addresses;
return $this;
}
public function withAssignedPHIDs(array $assigned) {
$this->assignedPHIDs = $assigned;
return $this;
}
public function withEffectivePHIDs(array $effective) {
$this->effectivePHIDs = $effective;
return $this;
}
public function withRelatedPHIDs(array $related) {
$this->relatedPHIDs = $related;
return $this;
}
public function withHasEffectivePHID($has_effective_phid) {
$this->hasEffectivePHID = $has_effective_phid;
return $this;
}
public function newResultObject() {
return new PhabricatorRepositoryIdentity();
}
protected function getPrimaryTableAlias() {
return 'identity';
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'identity.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'identity.phid IN (%Ls)',
$this->phids);
}
if ($this->assignedPHIDs !== null) {
$where[] = qsprintf(
$conn,
'identity.manuallySetUserPHID IN (%Ls)',
$this->assignedPHIDs);
}
if ($this->effectivePHIDs !== null) {
$where[] = qsprintf(
$conn,
'identity.currentEffectiveUserPHID IN (%Ls)',
$this->effectivePHIDs);
}
if ($this->hasEffectivePHID !== null) {
if ($this->hasEffectivePHID) {
$where[] = qsprintf(
$conn,
'identity.currentEffectiveUserPHID IS NOT NULL');
} else {
$where[] = qsprintf(
$conn,
'identity.currentEffectiveUserPHID IS NULL');
}
}
if ($this->identityNames !== null) {
$name_hashes = array();
foreach ($this->identityNames as $name) {
$name_hashes[] = PhabricatorHash::digestForIndex($name);
}
$where[] = qsprintf(
$conn,
'identity.identityNameHash IN (%Ls)',
$name_hashes);
}
if ($this->emailAddresses !== null) {
$where[] = qsprintf(
$conn,
'identity.emailAddress IN (%Ls)',
$this->emailAddresses);
}
if ($this->identityNameLike != null) {
$where[] = qsprintf(
$conn,
'identity.identityNameRaw LIKE %~',
$this->identityNameLike);
}
if ($this->relatedPHIDs !== null) {
$where[] = qsprintf(
$conn,
'(identity.manuallySetUserPHID IN (%Ls) OR
identity.currentEffectiveUserPHID IN (%Ls) OR
identity.automaticGuessedUserPHID IN (%Ls))',
$this->relatedPHIDs,
$this->relatedPHIDs,
$this->relatedPHIDs);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorDiffusionApplication';
}
}
diff --git a/src/applications/repository/query/PhabricatorRepositoryPullEventQuery.php b/src/applications/repository/query/PhabricatorRepositoryPullEventQuery.php
index ce14a6f831..8d4f14e0ce 100644
--- a/src/applications/repository/query/PhabricatorRepositoryPullEventQuery.php
+++ b/src/applications/repository/query/PhabricatorRepositoryPullEventQuery.php
@@ -1,135 +1,131 @@
<?php
final class PhabricatorRepositoryPullEventQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $repositoryPHIDs;
private $pullerPHIDs;
private $epochMin;
private $epochMax;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withRepositoryPHIDs(array $repository_phids) {
$this->repositoryPHIDs = $repository_phids;
return $this;
}
public function withPullerPHIDs(array $puller_phids) {
$this->pullerPHIDs = $puller_phids;
return $this;
}
public function withEpochBetween($min, $max) {
$this->epochMin = $min;
$this->epochMax = $max;
return $this;
}
public function newResultObject() {
return new PhabricatorRepositoryPullEvent();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $events) {
// If a pull targets an invalid repository or fails before authenticating,
// it may not have an associated repository.
$repository_phids = mpull($events, 'getRepositoryPHID');
$repository_phids = array_filter($repository_phids);
if ($repository_phids) {
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($this->getViewer())
->withPHIDs($repository_phids)
->execute();
$repositories = mpull($repositories, null, 'getPHID');
} else {
$repositories = array();
}
foreach ($events as $key => $event) {
$phid = $event->getRepositoryPHID();
if (!$phid) {
$event->attachRepository(null);
continue;
}
if (empty($repositories[$phid])) {
unset($events[$key]);
$this->didRejectResult($event);
continue;
}
$event->attachRepository($repositories[$phid]);
}
return $events;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->repositoryPHIDs !== null) {
$where[] = qsprintf(
$conn,
'repositoryPHID IN (%Ls)',
$this->repositoryPHIDs);
}
if ($this->pullerPHIDs !== null) {
$where[] = qsprintf(
$conn,
'pullerPHID in (%Ls)',
$this->pullerPHIDs);
}
if ($this->epochMin !== null) {
$where[] = qsprintf(
$conn,
'epoch >= %d',
$this->epochMin);
}
if ($this->epochMax !== null) {
$where[] = qsprintf(
$conn,
'epoch <= %d',
$this->epochMax);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorDiffusionApplication';
}
}
diff --git a/src/applications/repository/query/PhabricatorRepositoryPushEventQuery.php b/src/applications/repository/query/PhabricatorRepositoryPushEventQuery.php
index f3e5fc62b4..d1ce937b86 100644
--- a/src/applications/repository/query/PhabricatorRepositoryPushEventQuery.php
+++ b/src/applications/repository/query/PhabricatorRepositoryPushEventQuery.php
@@ -1,122 +1,118 @@
<?php
final class PhabricatorRepositoryPushEventQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $repositoryPHIDs;
private $pusherPHIDs;
private $needLogs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withRepositoryPHIDs(array $repository_phids) {
$this->repositoryPHIDs = $repository_phids;
return $this;
}
public function withPusherPHIDs(array $pusher_phids) {
$this->pusherPHIDs = $pusher_phids;
return $this;
}
public function needLogs($need_logs) {
$this->needLogs = $need_logs;
return $this;
}
public function newResultObject() {
return new PhabricatorRepositoryPushEvent();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $events) {
$repository_phids = mpull($events, 'getRepositoryPHID');
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($this->getViewer())
->withPHIDs($repository_phids)
->execute();
$repositories = mpull($repositories, null, 'getPHID');
foreach ($events as $key => $event) {
$phid = $event->getRepositoryPHID();
if (empty($repositories[$phid])) {
unset($events[$key]);
continue;
}
$event->attachRepository($repositories[$phid]);
}
return $events;
}
protected function didFilterPage(array $events) {
$phids = mpull($events, 'getPHID');
if ($this->needLogs) {
$logs = id(new PhabricatorRepositoryPushLogQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPushEventPHIDs($phids)
->execute();
$logs = mgroup($logs, 'getPushEventPHID');
foreach ($events as $key => $event) {
$event_logs = idx($logs, $event->getPHID(), array());
$event->attachLogs($event_logs);
}
}
return $events;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->repositoryPHIDs !== null) {
$where[] = qsprintf(
$conn,
'repositoryPHID IN (%Ls)',
$this->repositoryPHIDs);
}
if ($this->pusherPHIDs !== null) {
$where[] = qsprintf(
$conn,
'pusherPHID in (%Ls)',
$this->pusherPHIDs);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorDiffusionApplication';
}
}
diff --git a/src/applications/repository/query/PhabricatorRepositoryPushLogQuery.php b/src/applications/repository/query/PhabricatorRepositoryPushLogQuery.php
index d4734f61e6..16897a1e4b 100644
--- a/src/applications/repository/query/PhabricatorRepositoryPushLogQuery.php
+++ b/src/applications/repository/query/PhabricatorRepositoryPushLogQuery.php
@@ -1,199 +1,195 @@
<?php
final class PhabricatorRepositoryPushLogQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $repositoryPHIDs;
private $pusherPHIDs;
private $refTypes;
private $newRefs;
private $pushEventPHIDs;
private $epochMin;
private $epochMax;
private $blockingHeraldRulePHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withRepositoryPHIDs(array $repository_phids) {
$this->repositoryPHIDs = $repository_phids;
return $this;
}
public function withPusherPHIDs(array $pusher_phids) {
$this->pusherPHIDs = $pusher_phids;
return $this;
}
public function withRefTypes(array $ref_types) {
$this->refTypes = $ref_types;
return $this;
}
public function withNewRefs(array $new_refs) {
$this->newRefs = $new_refs;
return $this;
}
public function withPushEventPHIDs(array $phids) {
$this->pushEventPHIDs = $phids;
return $this;
}
public function withEpochBetween($min, $max) {
$this->epochMin = $min;
$this->epochMax = $max;
return $this;
}
public function withBlockingHeraldRulePHIDs(array $phids) {
$this->blockingHeraldRulePHIDs = $phids;
return $this;
}
public function newResultObject() {
return new PhabricatorRepositoryPushLog();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $logs) {
$event_phids = mpull($logs, 'getPushEventPHID');
$events = id(new PhabricatorObjectQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($event_phids)
->execute();
$events = mpull($events, null, 'getPHID');
foreach ($logs as $key => $log) {
$event = idx($events, $log->getPushEventPHID());
if (!$event) {
unset($logs[$key]);
continue;
}
$log->attachPushEvent($event);
}
return $logs;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'log.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'log.phid IN (%Ls)',
$this->phids);
}
if ($this->repositoryPHIDs !== null) {
$where[] = qsprintf(
$conn,
'log.repositoryPHID IN (%Ls)',
$this->repositoryPHIDs);
}
if ($this->pusherPHIDs !== null) {
$where[] = qsprintf(
$conn,
'log.pusherPHID in (%Ls)',
$this->pusherPHIDs);
}
if ($this->pushEventPHIDs !== null) {
$where[] = qsprintf(
$conn,
'log.pushEventPHID in (%Ls)',
$this->pushEventPHIDs);
}
if ($this->refTypes !== null) {
$where[] = qsprintf(
$conn,
'log.refType IN (%Ls)',
$this->refTypes);
}
if ($this->newRefs !== null) {
$where[] = qsprintf(
$conn,
'log.refNew IN (%Ls)',
$this->newRefs);
}
if ($this->epochMin !== null) {
$where[] = qsprintf(
$conn,
'log.epoch >= %d',
$this->epochMin);
}
if ($this->epochMax !== null) {
$where[] = qsprintf(
$conn,
'log.epoch <= %d',
$this->epochMax);
}
if ($this->blockingHeraldRulePHIDs !== null) {
$where[] = qsprintf(
$conn,
'(event.rejectCode = %d AND event.rejectDetails IN (%Ls))',
PhabricatorRepositoryPushLog::REJECT_HERALD,
$this->blockingHeraldRulePHIDs);
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->shouldJoinPushEventTable()) {
$joins[] = qsprintf(
$conn,
'JOIN %T event ON event.phid = log.pushEventPHID',
id(new PhabricatorRepositoryPushEvent())->getTableName());
}
return $joins;
}
private function shouldJoinPushEventTable() {
if ($this->blockingHeraldRulePHIDs !== null) {
return true;
}
return false;
}
public function getQueryApplicationClass() {
return 'PhabricatorDiffusionApplication';
}
protected function getPrimaryTableAlias() {
return 'log';
}
}
diff --git a/src/applications/repository/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php
index 9150c692b6..05b011e85a 100644
--- a/src/applications/repository/query/PhabricatorRepositoryQuery.php
+++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php
@@ -1,706 +1,706 @@
<?php
final class PhabricatorRepositoryQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $callsigns;
private $types;
private $uuids;
private $uris;
private $datasourceQuery;
private $slugs;
private $almanacServicePHIDs;
private $numericIdentifiers;
private $callsignIdentifiers;
private $phidIdentifiers;
private $monogramIdentifiers;
private $slugIdentifiers;
private $identifierMap;
const STATUS_OPEN = 'status-open';
const STATUS_CLOSED = 'status-closed';
const STATUS_ALL = 'status-all';
private $status = self::STATUS_ALL;
const HOSTED_PHABRICATOR = 'hosted-phab';
const HOSTED_REMOTE = 'hosted-remote';
const HOSTED_ALL = 'hosted-all';
private $hosted = self::HOSTED_ALL;
private $needMostRecentCommits;
private $needCommitCounts;
private $needProjectPHIDs;
private $needURIs;
private $needProfileImage;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withCallsigns(array $callsigns) {
$this->callsigns = $callsigns;
return $this;
}
public function withIdentifiers(array $identifiers) {
$identifiers = array_fuse($identifiers);
$ids = array();
$callsigns = array();
$phids = array();
$monograms = array();
$slugs = array();
foreach ($identifiers as $identifier) {
if (ctype_digit((string)$identifier)) {
$ids[$identifier] = $identifier;
continue;
}
if (preg_match('/^(r[A-Z]+|R[1-9]\d*)\z/', $identifier)) {
$monograms[$identifier] = $identifier;
continue;
}
$repository_type = PhabricatorRepositoryRepositoryPHIDType::TYPECONST;
if (phid_get_type($identifier) === $repository_type) {
$phids[$identifier] = $identifier;
continue;
}
if (preg_match('/^[A-Z]+\z/', $identifier)) {
$callsigns[$identifier] = $identifier;
continue;
}
$slugs[$identifier] = $identifier;
}
$this->numericIdentifiers = $ids;
$this->callsignIdentifiers = $callsigns;
$this->phidIdentifiers = $phids;
$this->monogramIdentifiers = $monograms;
$this->slugIdentifiers = $slugs;
return $this;
}
public function withStatus($status) {
$this->status = $status;
return $this;
}
public function withHosted($hosted) {
$this->hosted = $hosted;
return $this;
}
public function withTypes(array $types) {
$this->types = $types;
return $this;
}
public function withUUIDs(array $uuids) {
$this->uuids = $uuids;
return $this;
}
public function withURIs(array $uris) {
$this->uris = $uris;
return $this;
}
public function withDatasourceQuery($query) {
$this->datasourceQuery = $query;
return $this;
}
public function withSlugs(array $slugs) {
$this->slugs = $slugs;
return $this;
}
public function withAlmanacServicePHIDs(array $phids) {
$this->almanacServicePHIDs = $phids;
return $this;
}
public function needCommitCounts($need_counts) {
$this->needCommitCounts = $need_counts;
return $this;
}
public function needMostRecentCommits($need_commits) {
$this->needMostRecentCommits = $need_commits;
return $this;
}
public function needProjectPHIDs($need_phids) {
$this->needProjectPHIDs = $need_phids;
return $this;
}
public function needURIs($need_uris) {
$this->needURIs = $need_uris;
return $this;
}
public function needProfileImage($need) {
$this->needProfileImage = $need;
return $this;
}
public function getBuiltinOrders() {
return array(
'committed' => array(
'vector' => array('committed', 'id'),
'name' => pht('Most Recent Commit'),
),
'name' => array(
'vector' => array('name', 'id'),
'name' => pht('Name'),
),
'callsign' => array(
'vector' => array('callsign'),
'name' => pht('Callsign'),
),
'size' => array(
'vector' => array('size', 'id'),
'name' => pht('Size'),
),
) + parent::getBuiltinOrders();
}
public function getIdentifierMap() {
if ($this->identifierMap === null) {
throw new PhutilInvalidStateException('execute');
}
return $this->identifierMap;
}
protected function willExecute() {
$this->identifierMap = array();
}
public function newResultObject() {
return new PhabricatorRepository();
}
protected function loadPage() {
$table = $this->newResultObject();
$data = $this->loadStandardPageRows($table);
$repositories = $table->loadAllFromArray($data);
if ($this->needCommitCounts) {
$sizes = ipull($data, 'size', 'id');
foreach ($repositories as $id => $repository) {
$repository->attachCommitCount(nonempty($sizes[$id], 0));
}
}
if ($this->needMostRecentCommits) {
$commit_ids = ipull($data, 'lastCommitID', 'id');
$commit_ids = array_filter($commit_ids);
if ($commit_ids) {
$commits = id(new DiffusionCommitQuery())
->setViewer($this->getViewer())
->withIDs($commit_ids)
->needCommitData(true)
->needIdentities(true)
->execute();
} else {
$commits = array();
}
foreach ($repositories as $id => $repository) {
$commit = null;
if (idx($commit_ids, $id)) {
$commit = idx($commits, $commit_ids[$id]);
}
$repository->attachMostRecentCommit($commit);
}
}
return $repositories;
}
protected function willFilterPage(array $repositories) {
assert_instances_of($repositories, 'PhabricatorRepository');
// TODO: Denormalize repository status into the PhabricatorRepository
// table so we can do this filtering in the database.
foreach ($repositories as $key => $repo) {
$status = $this->status;
switch ($status) {
case self::STATUS_OPEN:
if (!$repo->isTracked()) {
unset($repositories[$key]);
}
break;
case self::STATUS_CLOSED:
if ($repo->isTracked()) {
unset($repositories[$key]);
}
break;
case self::STATUS_ALL:
break;
default:
throw new Exception("Unknown status '{$status}'!");
}
// TODO: This should also be denormalized.
$hosted = $this->hosted;
switch ($hosted) {
case self::HOSTED_PHABRICATOR:
if (!$repo->isHosted()) {
unset($repositories[$key]);
}
break;
case self::HOSTED_REMOTE:
if ($repo->isHosted()) {
unset($repositories[$key]);
}
break;
case self::HOSTED_ALL:
break;
default:
throw new Exception(pht("Unknown hosted failed '%s'!", $hosted));
}
}
// Build the identifierMap
if ($this->numericIdentifiers) {
foreach ($this->numericIdentifiers as $id) {
if (isset($repositories[$id])) {
$this->identifierMap[$id] = $repositories[$id];
}
}
}
if ($this->callsignIdentifiers) {
$repository_callsigns = mpull($repositories, null, 'getCallsign');
foreach ($this->callsignIdentifiers as $callsign) {
if (isset($repository_callsigns[$callsign])) {
$this->identifierMap[$callsign] = $repository_callsigns[$callsign];
}
}
}
if ($this->phidIdentifiers) {
$repository_phids = mpull($repositories, null, 'getPHID');
foreach ($this->phidIdentifiers as $phid) {
if (isset($repository_phids[$phid])) {
$this->identifierMap[$phid] = $repository_phids[$phid];
}
}
}
if ($this->monogramIdentifiers) {
$monogram_map = array();
foreach ($repositories as $repository) {
foreach ($repository->getAllMonograms() as $monogram) {
$monogram_map[$monogram] = $repository;
}
}
foreach ($this->monogramIdentifiers as $monogram) {
if (isset($monogram_map[$monogram])) {
$this->identifierMap[$monogram] = $monogram_map[$monogram];
}
}
}
if ($this->slugIdentifiers) {
$slug_map = array();
foreach ($repositories as $repository) {
$slug = $repository->getRepositorySlug();
if ($slug === null) {
continue;
}
$normal = phutil_utf8_strtolower($slug);
$slug_map[$normal] = $repository;
}
foreach ($this->slugIdentifiers as $slug) {
$normal = phutil_utf8_strtolower($slug);
if (isset($slug_map[$normal])) {
$this->identifierMap[$slug] = $slug_map[$normal];
}
}
}
return $repositories;
}
protected function didFilterPage(array $repositories) {
if ($this->needProjectPHIDs) {
$type_project = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(mpull($repositories, 'getPHID'))
->withEdgeTypes(array($type_project));
$edge_query->execute();
foreach ($repositories as $repository) {
$project_phids = $edge_query->getDestinationPHIDs(
array(
$repository->getPHID(),
));
$repository->attachProjectPHIDs($project_phids);
}
}
$viewer = $this->getViewer();
if ($this->needURIs) {
$uris = id(new PhabricatorRepositoryURIQuery())
->setViewer($viewer)
->withRepositories($repositories)
->execute();
$uri_groups = mgroup($uris, 'getRepositoryPHID');
foreach ($repositories as $repository) {
$repository_uris = idx($uri_groups, $repository->getPHID(), array());
$repository->attachURIs($repository_uris);
}
}
if ($this->needProfileImage) {
$default = null;
$file_phids = mpull($repositories, 'getProfileImagePHID');
$file_phids = array_filter($file_phids);
if ($file_phids) {
$files = id(new PhabricatorFileQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($file_phids)
->execute();
$files = mpull($files, null, 'getPHID');
} else {
$files = array();
}
foreach ($repositories as $repository) {
$file = idx($files, $repository->getProfileImagePHID());
if (!$file) {
if (!$default) {
$default = PhabricatorFile::loadBuiltin(
$this->getViewer(),
'repo/code.png');
}
$file = $default;
}
$repository->attachProfileImageFile($file);
}
}
return $repositories;
}
protected function getPrimaryTableAlias() {
return 'r';
}
public function getOrderableColumns() {
return parent::getOrderableColumns() + array(
'committed' => array(
'table' => 's',
'column' => 'epoch',
'type' => 'int',
'null' => 'tail',
),
'callsign' => array(
'table' => 'r',
'column' => 'callsign',
'type' => 'string',
'unique' => true,
'reverse' => true,
'null' => 'tail',
),
'name' => array(
'table' => 'r',
'column' => 'name',
'type' => 'string',
'reverse' => true,
),
'size' => array(
'table' => 's',
'column' => 'size',
'type' => 'int',
'null' => 'tail',
),
);
}
protected function newPagingMapFromCursorObject(
PhabricatorQueryCursor $cursor,
array $keys) {
$repository = $cursor->getObject();
$map = array(
'id' => (int)$repository->getID(),
'callsign' => $repository->getCallsign(),
'name' => $repository->getName(),
);
if (isset($keys['committed'])) {
$map['committed'] = $cursor->getRawRowProperty('epoch');
}
if (isset($keys['size'])) {
$map['size'] = $cursor->getRawRowProperty('size');
}
return $map;
}
protected function buildSelectClauseParts(AphrontDatabaseConnection $conn) {
$parts = parent::buildSelectClauseParts($conn);
if ($this->shouldJoinSummaryTable()) {
$parts[] = qsprintf($conn, 's.*');
}
return $parts;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->shouldJoinSummaryTable()) {
$joins[] = qsprintf(
$conn,
'LEFT JOIN %T s ON r.id = s.repositoryID',
PhabricatorRepository::TABLE_SUMMARY);
}
if ($this->shouldJoinURITable()) {
$joins[] = qsprintf(
$conn,
'LEFT JOIN %R uri ON r.phid = uri.repositoryPHID',
new PhabricatorRepositoryURIIndex());
}
return $joins;
}
protected function shouldGroupQueryResultRows() {
if ($this->shouldJoinURITable()) {
return true;
}
return parent::shouldGroupQueryResultRows();
}
private function shouldJoinURITable() {
return ($this->uris !== null);
}
private function shouldJoinSummaryTable() {
if ($this->needCommitCounts) {
return true;
}
if ($this->needMostRecentCommits) {
return true;
}
$vector = $this->getOrderVector();
if ($vector->containsKey('committed')) {
return true;
}
if ($vector->containsKey('size')) {
return true;
}
return false;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'r.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'r.phid IN (%Ls)',
$this->phids);
}
if ($this->callsigns !== null) {
$where[] = qsprintf(
$conn,
'r.callsign IN (%Ls)',
$this->callsigns);
}
if ($this->numericIdentifiers ||
$this->callsignIdentifiers ||
$this->phidIdentifiers ||
$this->monogramIdentifiers ||
$this->slugIdentifiers) {
$identifier_clause = array();
if ($this->numericIdentifiers) {
$identifier_clause[] = qsprintf(
$conn,
'r.id IN (%Ld)',
$this->numericIdentifiers);
}
if ($this->callsignIdentifiers) {
$identifier_clause[] = qsprintf(
$conn,
'r.callsign IN (%Ls)',
$this->callsignIdentifiers);
}
if ($this->phidIdentifiers) {
$identifier_clause[] = qsprintf(
$conn,
'r.phid IN (%Ls)',
$this->phidIdentifiers);
}
if ($this->monogramIdentifiers) {
$monogram_callsigns = array();
$monogram_ids = array();
foreach ($this->monogramIdentifiers as $identifier) {
if ($identifier[0] == 'r') {
$monogram_callsigns[] = substr($identifier, 1);
} else {
$monogram_ids[] = substr($identifier, 1);
}
}
if ($monogram_ids) {
$identifier_clause[] = qsprintf(
$conn,
'r.id IN (%Ld)',
$monogram_ids);
}
if ($monogram_callsigns) {
$identifier_clause[] = qsprintf(
$conn,
'r.callsign IN (%Ls)',
$monogram_callsigns);
}
}
if ($this->slugIdentifiers) {
$identifier_clause[] = qsprintf(
$conn,
'r.repositorySlug IN (%Ls)',
$this->slugIdentifiers);
}
$where[] = qsprintf($conn, '%LO', $identifier_clause);
}
if ($this->types) {
$where[] = qsprintf(
$conn,
'r.versionControlSystem IN (%Ls)',
$this->types);
}
if ($this->uuids) {
$where[] = qsprintf(
$conn,
'r.uuid IN (%Ls)',
$this->uuids);
}
- if (strlen($this->datasourceQuery)) {
+ if (phutil_nonempty_string($this->datasourceQuery)) {
// This handles having "rP" match callsigns starting with "P...".
$query = trim($this->datasourceQuery);
if (preg_match('/^r/', $query)) {
$callsign = substr($query, 1);
} else {
$callsign = $query;
}
$where[] = qsprintf(
$conn,
'r.name LIKE %> OR r.callsign LIKE %> OR r.repositorySlug LIKE %>',
$query,
$callsign,
$query);
}
if ($this->slugs !== null) {
$where[] = qsprintf(
$conn,
'r.repositorySlug IN (%Ls)',
$this->slugs);
}
if ($this->uris !== null) {
$try_uris = $this->getNormalizedURIs();
$try_uris = array_fuse($try_uris);
$where[] = qsprintf(
$conn,
'uri.repositoryURI IN (%Ls)',
$try_uris);
}
if ($this->almanacServicePHIDs !== null) {
$where[] = qsprintf(
$conn,
'r.almanacServicePHID IN (%Ls)',
$this->almanacServicePHIDs);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorDiffusionApplication';
}
private function getNormalizedURIs() {
$normalized_uris = array();
// Since we don't know which type of repository this URI is in the general
// case, just generate all the normalizations. We could refine this in some
// cases: if the query specifies VCS types, or the URI is a git-style URI
// or an `svn+ssh` URI, we could deduce how to normalize it. However, this
// would be more complicated and it's not clear if it matters in practice.
$domain_map = PhabricatorRepositoryURI::getURINormalizerDomainMap();
$types = ArcanistRepositoryURINormalizer::getAllURITypes();
foreach ($this->uris as $uri) {
foreach ($types as $type) {
$normalized_uri = new ArcanistRepositoryURINormalizer($type, $uri);
$normalized_uri->setDomainMap($domain_map);
$normalized_uris[] = $normalized_uri->getNormalizedURI();
}
}
return array_unique($normalized_uris);
}
}
diff --git a/src/applications/repository/query/PhabricatorRepositoryRefCursorQuery.php b/src/applications/repository/query/PhabricatorRepositoryRefCursorQuery.php
index b3c960e025..5e894333f6 100644
--- a/src/applications/repository/query/PhabricatorRepositoryRefCursorQuery.php
+++ b/src/applications/repository/query/PhabricatorRepositoryRefCursorQuery.php
@@ -1,153 +1,149 @@
<?php
final class PhabricatorRepositoryRefCursorQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $repositoryPHIDs;
private $refTypes;
private $refNames;
private $datasourceQuery;
private $needPositions;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withRepositoryPHIDs(array $phids) {
$this->repositoryPHIDs = $phids;
return $this;
}
public function withRefTypes(array $types) {
$this->refTypes = $types;
return $this;
}
public function withRefNames(array $names) {
$this->refNames = $names;
return $this;
}
public function withDatasourceQuery($query) {
$this->datasourceQuery = $query;
return $this;
}
public function needPositions($need) {
$this->needPositions = $need;
return $this;
}
public function newResultObject() {
return new PhabricatorRepositoryRefCursor();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $refs) {
$repository_phids = mpull($refs, 'getRepositoryPHID');
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($repository_phids)
->execute();
$repositories = mpull($repositories, null, 'getPHID');
foreach ($refs as $key => $ref) {
$repository = idx($repositories, $ref->getRepositoryPHID());
if (!$repository) {
$this->didRejectResult($ref);
unset($refs[$key]);
continue;
}
$ref->attachRepository($repository);
}
if (!$refs) {
return $refs;
}
if ($this->needPositions) {
$positions = id(new PhabricatorRepositoryRefPosition())->loadAllWhere(
'cursorID IN (%Ld)',
mpull($refs, 'getID'));
$positions = mgroup($positions, 'getCursorID');
foreach ($refs as $key => $ref) {
$ref_positions = idx($positions, $ref->getID(), array());
$ref->attachPositions($ref_positions);
}
}
return $refs;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->repositoryPHIDs !== null) {
$where[] = qsprintf(
$conn,
'repositoryPHID IN (%Ls)',
$this->repositoryPHIDs);
}
if ($this->refTypes !== null) {
$where[] = qsprintf(
$conn,
'refType IN (%Ls)',
$this->refTypes);
}
if ($this->refNames !== null) {
$name_hashes = array();
foreach ($this->refNames as $name) {
$name_hashes[] = PhabricatorHash::digestForIndex($name);
}
$where[] = qsprintf(
$conn,
'refNameHash IN (%Ls)',
$name_hashes);
}
if (strlen($this->datasourceQuery)) {
$where[] = qsprintf(
$conn,
'refNameRaw LIKE %>',
$this->datasourceQuery);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorDiffusionApplication';
}
}
diff --git a/src/applications/repository/query/PhabricatorRepositorySyncEventQuery.php b/src/applications/repository/query/PhabricatorRepositorySyncEventQuery.php
index 542cb5cdc0..cc568ef8e1 100644
--- a/src/applications/repository/query/PhabricatorRepositorySyncEventQuery.php
+++ b/src/applications/repository/query/PhabricatorRepositorySyncEventQuery.php
@@ -1,115 +1,111 @@
<?php
final class PhabricatorRepositorySyncEventQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $repositoryPHIDs;
private $epochMin;
private $epochMax;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withRepositoryPHIDs(array $repository_phids) {
$this->repositoryPHIDs = $repository_phids;
return $this;
}
public function withEpochBetween($min, $max) {
$this->epochMin = $min;
$this->epochMax = $max;
return $this;
}
public function newResultObject() {
return new PhabricatorRepositorySyncEvent();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $events) {
$repository_phids = mpull($events, 'getRepositoryPHID');
$repository_phids = array_filter($repository_phids);
if ($repository_phids) {
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($this->getViewer())
->withPHIDs($repository_phids)
->execute();
$repositories = mpull($repositories, null, 'getPHID');
} else {
$repositories = array();
}
foreach ($events as $key => $event) {
$phid = $event->getRepositoryPHID();
if (empty($repositories[$phid])) {
unset($events[$key]);
$this->didRejectResult($event);
continue;
}
$event->attachRepository($repositories[$phid]);
}
return $events;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->repositoryPHIDs !== null) {
$where[] = qsprintf(
$conn,
'repositoryPHID IN (%Ls)',
$this->repositoryPHIDs);
}
if ($this->epochMin !== null) {
$where[] = qsprintf(
$conn,
'epoch >= %d',
$this->epochMin);
}
if ($this->epochMax !== null) {
$where[] = qsprintf(
$conn,
'epoch <= %d',
$this->epochMax);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorDiffusionApplication';
}
}
diff --git a/src/applications/repository/query/PhabricatorRepositoryURIQuery.php b/src/applications/repository/query/PhabricatorRepositoryURIQuery.php
index 71252a6fb7..5b75e1ef63 100644
--- a/src/applications/repository/query/PhabricatorRepositoryURIQuery.php
+++ b/src/applications/repository/query/PhabricatorRepositoryURIQuery.php
@@ -1,101 +1,97 @@
<?php
final class PhabricatorRepositoryURIQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $repositoryPHIDs;
private $repositories = array();
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withRepositoryPHIDs(array $phids) {
$this->repositoryPHIDs = $phids;
return $this;
}
public function withRepositories(array $repositories) {
$repositories = mpull($repositories, null, 'getPHID');
$this->withRepositoryPHIDs(array_keys($repositories));
$this->repositories = $repositories;
return $this;
}
public function newResultObject() {
return new PhabricatorRepositoryURI();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->repositoryPHIDs !== null) {
$where[] = qsprintf(
$conn,
'repositoryPHID IN (%Ls)',
$this->repositoryPHIDs);
}
return $where;
}
protected function willFilterPage(array $uris) {
$repositories = $this->repositories;
$repository_phids = mpull($uris, 'getRepositoryPHID');
$repository_phids = array_fuse($repository_phids);
$repository_phids = array_diff_key($repository_phids, $repositories);
if ($repository_phids) {
$more_repositories = id(new PhabricatorRepositoryQuery())
->setViewer($this->getViewer())
->withPHIDs($repository_phids)
->execute();
$repositories += mpull($more_repositories, null, 'getPHID');
}
foreach ($uris as $key => $uri) {
$repository_phid = $uri->getRepositoryPHID();
$repository = idx($repositories, $repository_phid);
if (!$repository) {
$this->didRejectResult($uri);
unset($uris[$key]);
continue;
}
$uri->attachRepository($repository);
}
return $uris;
}
public function getQueryApplicationClass() {
return 'PhabricatorDiffusionApplication';
}
}
diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php
index e719f2cd73..0ac6687ef8 100644
--- a/src/applications/repository/storage/PhabricatorRepository.php
+++ b/src/applications/repository/storage/PhabricatorRepository.php
@@ -1,2880 +1,2884 @@
<?php
/**
* @task uri Repository URI Management
* @task publishing Publishing
* @task sync Cluster Synchronization
*/
final class PhabricatorRepository extends PhabricatorRepositoryDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface,
PhabricatorFlaggableInterface,
PhabricatorMarkupInterface,
PhabricatorDestructibleInterface,
PhabricatorDestructibleCodexInterface,
PhabricatorProjectInterface,
PhabricatorSpacesInterface,
PhabricatorConduitResultInterface,
PhabricatorFulltextInterface,
PhabricatorFerretInterface {
/**
* Shortest hash we'll recognize in raw "a829f32" form.
*/
const MINIMUM_UNQUALIFIED_HASH = 7;
/**
* Shortest hash we'll recognize in qualified "rXab7ef2f8" form.
*/
const MINIMUM_QUALIFIED_HASH = 5;
/**
* Minimum number of commits to an empty repository to trigger "import" mode.
*/
const IMPORT_THRESHOLD = 7;
const LOWPRI_THRESHOLD = 64;
const TABLE_PATH = 'repository_path';
const TABLE_PATHCHANGE = 'repository_pathchange';
const TABLE_FILESYSTEM = 'repository_filesystem';
const TABLE_SUMMARY = 'repository_summary';
const TABLE_LINTMESSAGE = 'repository_lintmessage';
const TABLE_PARENTS = 'repository_parents';
const TABLE_COVERAGE = 'repository_coverage';
const STATUS_ACTIVE = 'active';
const STATUS_INACTIVE = 'inactive';
protected $name;
protected $callsign;
protected $repositorySlug;
protected $uuid;
protected $viewPolicy;
protected $editPolicy;
protected $pushPolicy;
protected $profileImagePHID;
protected $versionControlSystem;
protected $details = array();
protected $credentialPHID;
protected $almanacServicePHID;
protected $spacePHID;
protected $localPath;
private $commitCount = self::ATTACHABLE;
private $mostRecentCommit = self::ATTACHABLE;
private $projectPHIDs = self::ATTACHABLE;
private $uris = self::ATTACHABLE;
private $profileImageFile = self::ATTACHABLE;
public static function initializeNewRepository(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
->withClasses(array('PhabricatorDiffusionApplication'))
->executeOne();
$view_policy = $app->getPolicy(DiffusionDefaultViewCapability::CAPABILITY);
$edit_policy = $app->getPolicy(DiffusionDefaultEditCapability::CAPABILITY);
$push_policy = $app->getPolicy(DiffusionDefaultPushCapability::CAPABILITY);
$repository = id(new PhabricatorRepository())
->setViewPolicy($view_policy)
->setEditPolicy($edit_policy)
->setPushPolicy($push_policy)
->setSpacePHID($actor->getDefaultSpacePHID());
// Put the repository in "Importing" mode until we finish
// parsing it.
$repository->setDetail('importing', true);
return $repository;
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'details' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'sort255',
'callsign' => 'sort32?',
'repositorySlug' => 'sort64?',
'versionControlSystem' => 'text32',
'uuid' => 'text64?',
'pushPolicy' => 'policy',
'credentialPHID' => 'phid?',
'almanacServicePHID' => 'phid?',
'localPath' => 'text128?',
'profileImagePHID' => 'phid?',
),
self::CONFIG_KEY_SCHEMA => array(
'callsign' => array(
'columns' => array('callsign'),
'unique' => true,
),
'key_name' => array(
'columns' => array('name(128)'),
),
'key_vcs' => array(
'columns' => array('versionControlSystem'),
),
'key_slug' => array(
'columns' => array('repositorySlug'),
'unique' => true,
),
'key_local' => array(
'columns' => array('localPath'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorRepositoryRepositoryPHIDType::TYPECONST);
}
public static function getStatusMap() {
return array(
self::STATUS_ACTIVE => array(
'name' => pht('Active'),
'isTracked' => 1,
),
self::STATUS_INACTIVE => array(
'name' => pht('Inactive'),
'isTracked' => 0,
),
);
}
public static function getStatusNameMap() {
return ipull(self::getStatusMap(), 'name');
}
public function getStatus() {
if ($this->isTracked()) {
return self::STATUS_ACTIVE;
} else {
return self::STATUS_INACTIVE;
}
}
public function toDictionary() {
return array(
'id' => $this->getID(),
'name' => $this->getName(),
'phid' => $this->getPHID(),
'callsign' => $this->getCallsign(),
'monogram' => $this->getMonogram(),
'vcs' => $this->getVersionControlSystem(),
'uri' => PhabricatorEnv::getProductionURI($this->getURI()),
'remoteURI' => (string)$this->getRemoteURI(),
'description' => $this->getDetail('description'),
'isActive' => $this->isTracked(),
'isHosted' => $this->isHosted(),
'isImporting' => $this->isImporting(),
'encoding' => $this->getDefaultTextEncoding(),
'staging' => array(
'supported' => $this->supportsStaging(),
'prefix' => 'phabricator',
'uri' => $this->getStagingURI(),
),
);
}
public function getDefaultTextEncoding() {
return $this->getDetail('encoding', 'UTF-8');
}
public function getMonogram() {
$callsign = $this->getCallsign();
- if (strlen($callsign)) {
+ if (phutil_nonempty_string($callsign)) {
return "r{$callsign}";
}
$id = $this->getID();
return "R{$id}";
}
public function getDisplayName() {
$slug = $this->getRepositorySlug();
- if (strlen($slug)) {
+
+ if (phutil_nonempty_string($slug)) {
return $slug;
}
return $this->getMonogram();
}
public function getAllMonograms() {
$monograms = array();
$monograms[] = 'R'.$this->getID();
$callsign = $this->getCallsign();
if (strlen($callsign)) {
$monograms[] = 'r'.$callsign;
}
return $monograms;
}
public function setLocalPath($path) {
// Convert any extra slashes ("//") in the path to a single slash ("/").
$path = preg_replace('(//+)', '/', $path);
return parent::setLocalPath($path);
}
public function getDetail($key, $default = null) {
return idx($this->details, $key, $default);
}
public function setDetail($key, $value) {
$this->details[$key] = $value;
return $this;
}
public function attachCommitCount($count) {
$this->commitCount = $count;
return $this;
}
public function getCommitCount() {
return $this->assertAttached($this->commitCount);
}
public function attachMostRecentCommit(
PhabricatorRepositoryCommit $commit = null) {
$this->mostRecentCommit = $commit;
return $this;
}
public function getMostRecentCommit() {
return $this->assertAttached($this->mostRecentCommit);
}
public function getDiffusionBrowseURIForPath(
PhabricatorUser $user,
$path,
$line = null,
$branch = null) {
$drequest = DiffusionRequest::newFromDictionary(
array(
'user' => $user,
'repository' => $this,
'path' => $path,
'branch' => $branch,
));
return $drequest->generateURI(
array(
'action' => 'browse',
'line' => $line,
));
}
public function getSubversionBaseURI($commit = null) {
$subpath = $this->getDetail('svn-subpath');
- if (!strlen($subpath)) {
+
+ if (!phutil_nonempty_string($subpath)) {
$subpath = null;
}
+
return $this->getSubversionPathURI($subpath, $commit);
}
public function getSubversionPathURI($path = null, $commit = null) {
$vcs = $this->getVersionControlSystem();
if ($vcs != PhabricatorRepositoryType::REPOSITORY_TYPE_SVN) {
throw new Exception(pht('Not a subversion repository!'));
}
if ($this->isHosted()) {
$uri = 'file://'.$this->getLocalPath();
} else {
$uri = $this->getDetail('remote-uri');
}
$uri = rtrim($uri, '/');
- if (strlen($path)) {
+ if (phutil_nonempty_string($path)) {
$path = rawurlencode($path);
$path = str_replace('%2F', '/', $path);
$uri = $uri.'/'.ltrim($path, '/');
}
if ($path !== null || $commit !== null) {
$uri .= '@';
}
if ($commit !== null) {
$uri .= $commit;
}
return $uri;
}
public function attachProjectPHIDs(array $project_phids) {
$this->projectPHIDs = $project_phids;
return $this;
}
public function getProjectPHIDs() {
return $this->assertAttached($this->projectPHIDs);
}
/**
* Get the name of the directory this repository should clone or checkout
* into. For example, if the repository name is "Example Repository", a
* reasonable name might be "example-repository". This is used to help users
* get reasonable results when cloning repositories, since they generally do
* not want to clone into directories called "X/" or "Example Repository/".
*
* @return string
*/
public function getCloneName() {
$name = $this->getRepositorySlug();
// Make some reasonable effort to produce reasonable default directory
// names from repository names.
if (!strlen($name)) {
$name = $this->getName();
$name = phutil_utf8_strtolower($name);
$name = preg_replace('@[ -/:->]+@', '-', $name);
$name = trim($name, '-');
if (!strlen($name)) {
$name = $this->getCallsign();
}
}
return $name;
}
public static function isValidRepositorySlug($slug) {
try {
self::assertValidRepositorySlug($slug);
return true;
} catch (Exception $ex) {
return false;
}
}
public static function assertValidRepositorySlug($slug) {
if (!strlen($slug)) {
throw new Exception(
pht(
'The empty string is not a valid repository short name. '.
'Repository short names must be at least one character long.'));
}
if (strlen($slug) > 64) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names must not be longer than 64 characters.',
$slug));
}
if (preg_match('/[^a-zA-Z0-9._-]/', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names may only contain letters, numbers, periods, hyphens '.
'and underscores.',
$slug));
}
if (!preg_match('/^[a-zA-Z0-9]/', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names must begin with a letter or number.',
$slug));
}
if (!preg_match('/[a-zA-Z0-9]\z/', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names must end with a letter or number.',
$slug));
}
if (preg_match('/__|--|\\.\\./', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names must not contain multiple consecutive underscores, '.
'hyphens, or periods.',
$slug));
}
if (preg_match('/^[A-Z]+\z/', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names may not contain only uppercase letters.',
$slug));
}
if (preg_match('/^\d+\z/', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names may not contain only numbers.',
$slug));
}
if (preg_match('/\\.git/', $slug)) {
throw new Exception(
pht(
'The name "%s" is not a valid repository short name. Repository '.
'short names must not end in ".git". This suffix will be added '.
'automatically in appropriate contexts.',
$slug));
}
}
public static function assertValidCallsign($callsign) {
if (!strlen($callsign)) {
throw new Exception(
pht(
'A repository callsign must be at least one character long.'));
}
if (strlen($callsign) > 32) {
throw new Exception(
pht(
'The callsign "%s" is not a valid repository callsign. Callsigns '.
'must be no more than 32 bytes long.',
$callsign));
}
if (!preg_match('/^[A-Z]+\z/', $callsign)) {
throw new Exception(
pht(
'The callsign "%s" is not a valid repository callsign. Callsigns '.
'may only contain UPPERCASE letters.',
$callsign));
}
}
public function getProfileImageURI() {
return $this->getProfileImageFile()->getBestURI();
}
public function attachProfileImageFile(PhabricatorFile $file) {
$this->profileImageFile = $file;
return $this;
}
public function getProfileImageFile() {
return $this->assertAttached($this->profileImageFile);
}
/* -( Remote Command Execution )------------------------------------------- */
public function execRemoteCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newRemoteCommandFuture($args)->resolve();
}
public function execxRemoteCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newRemoteCommandFuture($args)->resolvex();
}
public function getRemoteCommandFuture($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newRemoteCommandFuture($args);
}
public function passthruRemoteCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newRemoteCommandPassthru($args)->resolve();
}
private function newRemoteCommandFuture(array $argv) {
return $this->newRemoteCommandEngine($argv)
->newFuture();
}
private function newRemoteCommandPassthru(array $argv) {
return $this->newRemoteCommandEngine($argv)
->setPassthru(true)
->newFuture();
}
private function newRemoteCommandEngine(array $argv) {
return DiffusionCommandEngine::newCommandEngine($this)
->setArgv($argv)
->setCredentialPHID($this->getCredentialPHID())
->setURI($this->getRemoteURIObject());
}
/* -( Local Command Execution )-------------------------------------------- */
public function execLocalCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newLocalCommandFuture($args)->resolve();
}
public function execxLocalCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newLocalCommandFuture($args)->resolvex();
}
public function getLocalCommandFuture($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newLocalCommandFuture($args);
}
public function passthruLocalCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
return $this->newLocalCommandPassthru($args)->resolve();
}
private function newLocalCommandFuture(array $argv) {
$this->assertLocalExists();
$future = DiffusionCommandEngine::newCommandEngine($this)
->setArgv($argv)
->newFuture();
if ($this->usesLocalWorkingCopy()) {
$future->setCWD($this->getLocalPath());
}
return $future;
}
private function newLocalCommandPassthru(array $argv) {
$this->assertLocalExists();
$future = DiffusionCommandEngine::newCommandEngine($this)
->setArgv($argv)
->setPassthru(true)
->newFuture();
if ($this->usesLocalWorkingCopy()) {
$future->setCWD($this->getLocalPath());
}
return $future;
}
public function getURI() {
$short_name = $this->getRepositorySlug();
- if (strlen($short_name)) {
+ if (phutil_nonempty_string($short_name)) {
return "/source/{$short_name}/";
}
$callsign = $this->getCallsign();
- if (strlen($callsign)) {
+ if (phutil_nonempty_string($callsign)) {
return "/diffusion/{$callsign}/";
}
$id = $this->getID();
return "/diffusion/{$id}/";
}
public function getPathURI($path) {
return $this->getURI().ltrim($path, '/');
}
public function getCommitURI($identifier) {
$callsign = $this->getCallsign();
- if (strlen($callsign)) {
+ if (phutil_nonempty_string($callsign)) {
return "/r{$callsign}{$identifier}";
}
$id = $this->getID();
return "/R{$id}:{$identifier}";
}
public static function parseRepositoryServicePath($request_path, $vcs) {
$is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT);
$patterns = array(
'(^'.
'(?P<base>/?(?:diffusion|source)/(?P<identifier>[^/]+))'.
'(?P<path>.*)'.
'\z)',
);
$identifier = null;
foreach ($patterns as $pattern) {
$matches = null;
if (!preg_match($pattern, $request_path, $matches)) {
continue;
}
$identifier = $matches['identifier'];
if ($is_git) {
$identifier = preg_replace('/\\.git\z/', '', $identifier);
}
$base = $matches['base'];
$path = $matches['path'];
break;
}
if ($identifier === null) {
return null;
}
return array(
'identifier' => $identifier,
'base' => $base,
'path' => $path,
);
}
public function getCanonicalPath($request_path) {
$standard_pattern =
'(^'.
'(?P<prefix>/(?:diffusion|source)/)'.
'(?P<identifier>[^/]+)'.
'(?P<suffix>(?:/.*)?)'.
'\z)';
$matches = null;
if (preg_match($standard_pattern, $request_path, $matches)) {
$suffix = $matches['suffix'];
return $this->getPathURI($suffix);
}
$commit_pattern =
'(^'.
'(?P<prefix>/)'.
'(?P<monogram>'.
'(?:'.
'r(?P<repositoryCallsign>[A-Z]+)'.
'|'.
'R(?P<repositoryID>[1-9]\d*):'.
')'.
'(?P<commit>[a-f0-9]+)'.
')'.
'\z)';
$matches = null;
if (preg_match($commit_pattern, $request_path, $matches)) {
$commit = $matches['commit'];
return $this->getCommitURI($commit);
}
return null;
}
public function generateURI(array $params) {
$req_branch = false;
$req_commit = false;
$action = idx($params, 'action');
switch ($action) {
case 'history':
case 'clone':
case 'blame':
case 'browse':
case 'document':
case 'change':
case 'lastmodified':
case 'tags':
case 'branches':
case 'lint':
case 'pathtree':
case 'refs':
case 'compare':
break;
case 'branch':
// NOTE: This does not actually require a branch, and won't have one
// in Subversion. Possibly this should be more clear.
break;
case 'commit':
case 'rendering-ref':
$req_commit = true;
break;
default:
throw new Exception(
pht(
'Action "%s" is not a valid repository URI action.',
$action));
}
$path = idx($params, 'path');
$branch = idx($params, 'branch');
$commit = idx($params, 'commit');
$line = idx($params, 'line');
$head = idx($params, 'head');
$against = idx($params, 'against');
if ($req_commit && !strlen($commit)) {
throw new Exception(
pht(
'Diffusion URI action "%s" requires commit!',
$action));
}
if ($req_branch && !strlen($branch)) {
throw new Exception(
pht(
'Diffusion URI action "%s" requires branch!',
$action));
}
if ($action === 'commit') {
return $this->getCommitURI($commit);
}
- if (strlen($path)) {
+ if (phutil_nonempty_string($path)) {
$path = ltrim($path, '/');
$path = str_replace(array(';', '$'), array(';;', '$$'), $path);
$path = phutil_escape_uri($path);
}
$raw_branch = $branch;
- if (strlen($branch)) {
+ if (phutil_nonempty_string($branch)) {
$branch = phutil_escape_uri_path_component($branch);
$path = "{$branch}/{$path}";
}
$raw_commit = $commit;
- if (strlen($commit)) {
+ if (phutil_nonempty_string($commit)) {
$commit = str_replace('$', '$$', $commit);
$commit = ';'.phutil_escape_uri($commit);
}
- if (strlen($line)) {
+ $line = phutil_string_cast($line);
+ if (phutil_nonempty_string($line)) {
$line = '$'.phutil_escape_uri($line);
}
$query = array();
switch ($action) {
case 'change':
case 'history':
case 'blame':
case 'browse':
case 'document':
case 'lastmodified':
case 'tags':
case 'branches':
case 'lint':
case 'pathtree':
case 'refs':
$uri = $this->getPathURI("/{$action}/{$path}{$commit}{$line}");
break;
case 'compare':
$uri = $this->getPathURI("/{$action}/");
if (strlen($head)) {
$query['head'] = $head;
} else if (strlen($raw_commit)) {
$query['commit'] = $raw_commit;
} else if (strlen($raw_branch)) {
$query['head'] = $raw_branch;
}
if (strlen($against)) {
$query['against'] = $against;
}
break;
case 'branch':
if (strlen($path)) {
$uri = $this->getPathURI("/repository/{$path}");
} else {
$uri = $this->getPathURI('/');
}
break;
case 'external':
$commit = ltrim($commit, ';');
$uri = "/diffusion/external/{$commit}/";
break;
case 'rendering-ref':
// This isn't a real URI per se, it's passed as a query parameter to
// the ajax changeset stuff but then we parse it back out as though
// it came from a URI.
$uri = rawurldecode("{$path}{$commit}");
break;
case 'clone':
$uri = $this->getPathURI("/{$action}/");
break;
}
if ($action == 'rendering-ref') {
return $uri;
}
if (isset($params['lint'])) {
$params['params'] = idx($params, 'params', array()) + array(
'lint' => $params['lint'],
);
}
$query = idx($params, 'params', array()) + $query;
return new PhutilURI($uri, $query);
}
public function updateURIIndex() {
$indexes = array();
$uris = $this->getURIs();
foreach ($uris as $uri) {
if ($uri->getIsDisabled()) {
continue;
}
$indexes[] = $uri->getNormalizedURI();
}
PhabricatorRepositoryURIIndex::updateRepositoryURIs(
$this->getPHID(),
$indexes);
return $this;
}
public function isTracked() {
$status = $this->getDetail('tracking-enabled');
$map = self::getStatusMap();
$spec = idx($map, $status);
if (!$spec) {
if ($status) {
$status = self::STATUS_ACTIVE;
} else {
$status = self::STATUS_INACTIVE;
}
$spec = idx($map, $status);
}
return (bool)idx($spec, 'isTracked', false);
}
public function getDefaultBranch() {
$default = $this->getDetail('default-branch');
- if (strlen($default)) {
+ if (phutil_nonempty_string($default)) {
return $default;
}
$default_branches = array(
PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => 'master',
PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL => 'default',
);
return idx($default_branches, $this->getVersionControlSystem());
}
public function getDefaultArcanistBranch() {
return coalesce($this->getDefaultBranch(), 'svn');
}
private function isBranchInFilter($branch, $filter_key) {
$vcs = $this->getVersionControlSystem();
$is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT);
$use_filter = ($is_git);
if (!$use_filter) {
// If this VCS doesn't use filters, pass everything through.
return true;
}
$filter = $this->getDetail($filter_key, array());
// If there's no filter set, let everything through.
if (!$filter) {
return true;
}
// If this branch isn't literally named `regexp(...)`, and it's in the
// filter list, let it through.
if (isset($filter[$branch])) {
if (self::extractBranchRegexp($branch) === null) {
return true;
}
}
// If the branch matches a regexp, let it through.
foreach ($filter as $pattern => $ignored) {
$regexp = self::extractBranchRegexp($pattern);
if ($regexp !== null) {
if (preg_match($regexp, $branch)) {
return true;
}
}
}
// Nothing matched, so filter this branch out.
return false;
}
public static function extractBranchRegexp($pattern) {
$matches = null;
if (preg_match('/^regexp\\((.*)\\)\z/', $pattern, $matches)) {
return $matches[1];
}
return null;
}
public function shouldTrackRef(DiffusionRepositoryRef $ref) {
// At least for now, don't track the staging area tags.
if ($ref->isTag()) {
if (preg_match('(^phabricator/)', $ref->getShortName())) {
return false;
}
}
if (!$ref->isBranch()) {
return true;
}
return $this->shouldTrackBranch($ref->getShortName());
}
public function shouldTrackBranch($branch) {
return $this->isBranchInFilter($branch, 'branch-filter');
}
public function isBranchPermanentRef($branch) {
return $this->isBranchInFilter($branch, 'close-commits-filter');
}
public function formatCommitName($commit_identifier, $local = false) {
$vcs = $this->getVersionControlSystem();
$type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
$type_hg = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL;
$is_git = ($vcs == $type_git);
$is_hg = ($vcs == $type_hg);
if ($is_git || $is_hg) {
$name = substr($commit_identifier, 0, 12);
$need_scope = false;
} else {
$name = $commit_identifier;
$need_scope = true;
}
if (!$local) {
$need_scope = true;
}
if ($need_scope) {
$callsign = $this->getCallsign();
if ($callsign) {
$scope = "r{$callsign}";
} else {
$id = $this->getID();
$scope = "R{$id}:";
}
$name = $scope.$name;
}
return $name;
}
public function isImporting() {
return (bool)$this->getDetail('importing', false);
}
public function isNewlyInitialized() {
return (bool)$this->getDetail('newly-initialized', false);
}
public function loadImportProgress() {
$progress = queryfx_all(
$this->establishConnection('r'),
'SELECT importStatus, count(*) N FROM %T WHERE repositoryID = %d
GROUP BY importStatus',
id(new PhabricatorRepositoryCommit())->getTableName(),
$this->getID());
$done = 0;
$total = 0;
foreach ($progress as $row) {
$total += $row['N'] * 3;
$status = $row['importStatus'];
if ($status & PhabricatorRepositoryCommit::IMPORTED_MESSAGE) {
$done += $row['N'];
}
if ($status & PhabricatorRepositoryCommit::IMPORTED_CHANGE) {
$done += $row['N'];
}
if ($status & PhabricatorRepositoryCommit::IMPORTED_PUBLISH) {
$done += $row['N'];
}
}
if ($total) {
$ratio = ($done / $total);
} else {
$ratio = 0;
}
// Cap this at "99.99%", because it's confusing to users when the actual
// fraction is "99.996%" and it rounds up to "100.00%".
if ($ratio > 0.9999) {
$ratio = 0.9999;
}
return $ratio;
}
/* -( Publishing )--------------------------------------------------------- */
public function newPublisher() {
return id(new PhabricatorRepositoryPublisher())
->setRepository($this);
}
public function isPublishingDisabled() {
return $this->getDetail('herald-disabled');
}
public function getPermanentRefRules() {
return array_keys($this->getDetail('close-commits-filter', array()));
}
public function setPermanentRefRules(array $rules) {
$rules = array_fill_keys($rules, true);
$this->setDetail('close-commits-filter', $rules);
return $this;
}
public function getTrackOnlyRules() {
return array_keys($this->getDetail('branch-filter', array()));
}
public function setTrackOnlyRules(array $rules) {
$rules = array_fill_keys($rules, true);
$this->setDetail('branch-filter', $rules);
return $this;
}
public function supportsFetchRules() {
if ($this->isGit()) {
return true;
}
return false;
}
public function getFetchRules() {
return $this->getDetail('fetch-rules', array());
}
public function setFetchRules(array $rules) {
return $this->setDetail('fetch-rules', $rules);
}
/* -( Repository URI Management )------------------------------------------ */
/**
* Get the remote URI for this repository.
*
* @return string
* @task uri
*/
public function getRemoteURI() {
return (string)$this->getRemoteURIObject();
}
/**
* Get the remote URI for this repository, including credentials if they're
* used by this repository.
*
* @return PhutilOpaqueEnvelope URI, possibly including credentials.
* @task uri
*/
public function getRemoteURIEnvelope() {
$uri = $this->getRemoteURIObject();
$remote_protocol = $this->getRemoteProtocol();
if ($remote_protocol == 'http' || $remote_protocol == 'https') {
// For SVN, we use `--username` and `--password` flags separately, so
// don't add any credentials here.
if (!$this->isSVN()) {
$credential_phid = $this->getCredentialPHID();
if ($credential_phid) {
$key = PassphrasePasswordKey::loadFromPHID(
$credential_phid,
PhabricatorUser::getOmnipotentUser());
$uri->setUser($key->getUsernameEnvelope()->openEnvelope());
$uri->setPass($key->getPasswordEnvelope()->openEnvelope());
}
}
}
return new PhutilOpaqueEnvelope((string)$uri);
}
/**
* Get the clone (or checkout) URI for this repository, without authentication
* information.
*
* @return string Repository URI.
* @task uri
*/
public function getPublicCloneURI() {
return (string)$this->getCloneURIObject();
}
/**
* Get the protocol for the repository's remote.
*
* @return string Protocol, like "ssh" or "git".
* @task uri
*/
public function getRemoteProtocol() {
$uri = $this->getRemoteURIObject();
return $uri->getProtocol();
}
/**
* Get a parsed object representation of the repository's remote URI..
*
* @return wild A @{class@arcanist:PhutilURI}.
* @task uri
*/
public function getRemoteURIObject() {
$raw_uri = $this->getDetail('remote-uri');
if (!strlen($raw_uri)) {
return new PhutilURI('');
}
if (!strncmp($raw_uri, '/', 1)) {
return new PhutilURI('file://'.$raw_uri);
}
return new PhutilURI($raw_uri);
}
/**
* Get the "best" clone/checkout URI for this repository, on any protocol.
*/
public function getCloneURIObject() {
if (!$this->isHosted()) {
if ($this->isSVN()) {
// Make sure we pick up the "Import Only" path for Subversion, so
// the user clones the repository starting at the correct path, not
// from the root.
$base_uri = $this->getSubversionBaseURI();
$base_uri = new PhutilURI($base_uri);
$path = $base_uri->getPath();
if (!$path) {
$path = '/';
}
// If the trailing "@" is not required to escape the URI, strip it for
// readability.
if (!preg_match('/@.*@/', $path)) {
$path = rtrim($path, '@');
}
$base_uri->setPath($path);
return $base_uri;
} else {
return $this->getRemoteURIObject();
}
}
// TODO: This should be cleaned up to deal with all the new URI handling.
$another_copy = id(new PhabricatorRepositoryQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($this->getPHID()))
->needURIs(true)
->executeOne();
$clone_uris = $another_copy->getCloneURIs();
if (!$clone_uris) {
return null;
}
return head($clone_uris)->getEffectiveURI();
}
private function getRawHTTPCloneURIObject() {
$uri = PhabricatorEnv::getProductionURI($this->getURI());
$uri = new PhutilURI($uri);
if ($this->isGit()) {
$uri->setPath($uri->getPath().$this->getCloneName().'.git');
} else if ($this->isHg()) {
$uri->setPath($uri->getPath().$this->getCloneName().'/');
}
return $uri;
}
/**
* Determine if we should connect to the remote using SSH flags and
* credentials.
*
* @return bool True to use the SSH protocol.
* @task uri
*/
private function shouldUseSSH() {
if ($this->isHosted()) {
return false;
}
$protocol = $this->getRemoteProtocol();
if ($this->isSSHProtocol($protocol)) {
return true;
}
return false;
}
/**
* Determine if we should connect to the remote using HTTP flags and
* credentials.
*
* @return bool True to use the HTTP protocol.
* @task uri
*/
private function shouldUseHTTP() {
if ($this->isHosted()) {
return false;
}
$protocol = $this->getRemoteProtocol();
return ($protocol == 'http' || $protocol == 'https');
}
/**
* Determine if we should connect to the remote using SVN flags and
* credentials.
*
* @return bool True to use the SVN protocol.
* @task uri
*/
private function shouldUseSVNProtocol() {
if ($this->isHosted()) {
return false;
}
$protocol = $this->getRemoteProtocol();
return ($protocol == 'svn');
}
/**
* Determine if a protocol is SSH or SSH-like.
*
* @param string A protocol string, like "http" or "ssh".
* @return bool True if the protocol is SSH-like.
* @task uri
*/
private function isSSHProtocol($protocol) {
return ($protocol == 'ssh' || $protocol == 'svn+ssh');
}
public function delete() {
$this->openTransaction();
$paths = id(new PhabricatorOwnersPath())
->loadAllWhere('repositoryPHID = %s', $this->getPHID());
foreach ($paths as $path) {
$path->delete();
}
queryfx(
$this->establishConnection('w'),
'DELETE FROM %T WHERE repositoryPHID = %s',
id(new PhabricatorRepositorySymbol())->getTableName(),
$this->getPHID());
$commits = id(new PhabricatorRepositoryCommit())
->loadAllWhere('repositoryID = %d', $this->getID());
foreach ($commits as $commit) {
// note PhabricatorRepositoryAuditRequests and
// PhabricatorRepositoryCommitData are deleted here too.
$commit->delete();
}
$uris = id(new PhabricatorRepositoryURI())
->loadAllWhere('repositoryPHID = %s', $this->getPHID());
foreach ($uris as $uri) {
$uri->delete();
}
$ref_cursors = id(new PhabricatorRepositoryRefCursor())
->loadAllWhere('repositoryPHID = %s', $this->getPHID());
foreach ($ref_cursors as $cursor) {
$cursor->delete();
}
$conn_w = $this->establishConnection('w');
queryfx(
$conn_w,
'DELETE FROM %T WHERE repositoryID = %d',
self::TABLE_FILESYSTEM,
$this->getID());
queryfx(
$conn_w,
'DELETE FROM %T WHERE repositoryID = %d',
self::TABLE_PATHCHANGE,
$this->getID());
queryfx(
$conn_w,
'DELETE FROM %T WHERE repositoryID = %d',
self::TABLE_SUMMARY,
$this->getID());
$result = parent::delete();
$this->saveTransaction();
return $result;
}
public function isGit() {
$vcs = $this->getVersionControlSystem();
return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT);
}
public function isSVN() {
$vcs = $this->getVersionControlSystem();
return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_SVN);
}
public function isHg() {
$vcs = $this->getVersionControlSystem();
return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL);
}
public function isHosted() {
return (bool)$this->getDetail('hosting-enabled', false);
}
public function setHosted($enabled) {
return $this->setDetail('hosting-enabled', $enabled);
}
public function canServeProtocol(
$protocol,
$write,
$is_intracluster = false) {
// See T13192. If a repository is inactive, don't serve it to users. We
// still synchronize it within the cluster and serve it to other repository
// nodes.
if (!$is_intracluster) {
if (!$this->isTracked()) {
return false;
}
}
$clone_uris = $this->getCloneURIs();
foreach ($clone_uris as $uri) {
if ($uri->getBuiltinProtocol() !== $protocol) {
continue;
}
$io_type = $uri->getEffectiveIoType();
if ($io_type == PhabricatorRepositoryURI::IO_READWRITE) {
return true;
}
if (!$write) {
if ($io_type == PhabricatorRepositoryURI::IO_READ) {
return true;
}
}
}
if ($write) {
if ($this->isReadOnly()) {
return false;
}
}
return false;
}
public function hasLocalWorkingCopy() {
try {
self::assertLocalExists();
return true;
} catch (Exception $ex) {
return false;
}
}
/**
* Raise more useful errors when there are basic filesystem problems.
*/
private function assertLocalExists() {
if (!$this->usesLocalWorkingCopy()) {
return;
}
$local = $this->getLocalPath();
Filesystem::assertExists($local);
Filesystem::assertIsDirectory($local);
Filesystem::assertReadable($local);
}
/**
* Determine if the working copy is bare or not. In Git, this corresponds
* to `--bare`. In Mercurial, `--noupdate`.
*/
public function isWorkingCopyBare() {
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
return false;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$local = $this->getLocalPath();
if (Filesystem::pathExists($local.'/.git')) {
return false;
} else {
return true;
}
}
}
public function usesLocalWorkingCopy() {
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
return $this->isHosted();
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
return true;
}
}
public function getHookDirectories() {
$directories = array();
if (!$this->isHosted()) {
return $directories;
}
$root = $this->getLocalPath();
switch ($this->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
if ($this->isWorkingCopyBare()) {
$directories[] = $root.'/hooks/pre-receive-phabricator.d/';
} else {
$directories[] = $root.'/.git/hooks/pre-receive-phabricator.d/';
}
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$directories[] = $root.'/hooks/pre-commit-phabricator.d/';
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
// NOTE: We don't support custom Mercurial hooks for now because they're
// messy and we can't easily just drop a `hooks.d/` directory next to
// the hooks.
break;
}
return $directories;
}
public function canDestroyWorkingCopy() {
if ($this->isHosted()) {
// Never destroy hosted working copies.
return false;
}
$default_path = PhabricatorEnv::getEnvConfig(
'repository.default-local-path');
return Filesystem::isDescendant($this->getLocalPath(), $default_path);
}
public function canUsePathTree() {
return !$this->isSVN();
}
public function canUseGitLFS() {
if (!$this->isGit()) {
return false;
}
if (!$this->isHosted()) {
return false;
}
if (!PhabricatorEnv::getEnvConfig('diffusion.allow-git-lfs')) {
return false;
}
return true;
}
public function getGitLFSURI($path = null) {
if (!$this->canUseGitLFS()) {
throw new Exception(
pht(
'This repository does not support Git LFS, so Git LFS URIs can '.
'not be generated for it.'));
}
$uri = $this->getRawHTTPCloneURIObject();
$uri = (string)$uri;
$uri = $uri.'/'.$path;
return $uri;
}
public function canMirror() {
if ($this->isGit() || $this->isHg()) {
return true;
}
return false;
}
public function canAllowDangerousChanges() {
if (!$this->isHosted()) {
return false;
}
// In Git and Mercurial, ref deletions and rewrites are dangerous.
// In Subversion, editing revprops is dangerous.
return true;
}
public function shouldAllowDangerousChanges() {
return (bool)$this->getDetail('allow-dangerous-changes');
}
public function canAllowEnormousChanges() {
if (!$this->isHosted()) {
return false;
}
return true;
}
public function shouldAllowEnormousChanges() {
return (bool)$this->getDetail('allow-enormous-changes');
}
public function writeStatusMessage(
$status_type,
$status_code,
array $parameters = array()) {
$table = new PhabricatorRepositoryStatusMessage();
$conn_w = $table->establishConnection('w');
$table_name = $table->getTableName();
if ($status_code === null) {
queryfx(
$conn_w,
'DELETE FROM %T WHERE repositoryID = %d AND statusType = %s',
$table_name,
$this->getID(),
$status_type);
} else {
// If the existing message has the same code (e.g., we just hit an
// error and also previously hit an error) we increment the message
// count. This allows us to determine how many times in a row we've
// run into an error.
// NOTE: The assignments in "ON DUPLICATE KEY UPDATE" are evaluated
// in order, so the "messageCount" assignment must occur before the
// "statusCode" assignment. See T11705.
queryfx(
$conn_w,
'INSERT INTO %T
(repositoryID, statusType, statusCode, parameters, epoch,
messageCount)
VALUES (%d, %s, %s, %s, %d, %d)
ON DUPLICATE KEY UPDATE
messageCount =
IF(
statusCode = VALUES(statusCode),
messageCount + VALUES(messageCount),
VALUES(messageCount)),
statusCode = VALUES(statusCode),
parameters = VALUES(parameters),
epoch = VALUES(epoch)',
$table_name,
$this->getID(),
$status_type,
$status_code,
json_encode($parameters),
time(),
1);
}
return $this;
}
public static function assertValidRemoteURI($uri) {
if (trim($uri) != $uri) {
throw new Exception(
pht('The remote URI has leading or trailing whitespace.'));
}
$uri_object = new PhutilURI($uri);
$protocol = $uri_object->getProtocol();
// Catch confusion between Git/SCP-style URIs and normal URIs. See T3619
// for discussion. This is usually a user adding "ssh://" to an implicit
// SSH Git URI.
if ($protocol == 'ssh') {
if (preg_match('(^[^:@]+://[^/:]+:[^\d])', $uri)) {
throw new Exception(
pht(
"The remote URI is not formatted correctly. Remote URIs ".
"with an explicit protocol should be in the form ".
"'%s', not '%s'. The '%s' syntax is only valid in SCP-style URIs.",
'proto://domain/path',
'proto://domain:/path',
':/path'));
}
}
switch ($protocol) {
case 'ssh':
case 'http':
case 'https':
case 'git':
case 'svn':
case 'svn+ssh':
break;
default:
// NOTE: We're explicitly rejecting 'file://' because it can be
// used to clone from the working copy of another repository on disk
// that you don't normally have permission to access.
throw new Exception(
pht(
'The URI protocol is unrecognized. It should begin with '.
'"%s", "%s", "%s", "%s", "%s", "%s", or be in the form "%s".',
'ssh://',
'http://',
'https://',
'git://',
'svn://',
'svn+ssh://',
'git@domain.com:path'));
}
return true;
}
/**
* Load the pull frequency for this repository, based on the time since the
* last activity.
*
* We pull rarely used repositories less frequently. This finds the most
* recent commit which is older than the current time (which prevents us from
* spinning on repositories with a silly commit post-dated to some time in
* 2037). We adjust the pull frequency based on when the most recent commit
* occurred.
*
* @param int The minimum update interval to use, in seconds.
* @return int Repository update interval, in seconds.
*/
public function loadUpdateInterval($minimum = 15) {
// First, check if we've hit errors recently. If we have, wait one period
// for each consecutive error. Normally, this corresponds to a backoff of
// 15s, 30s, 45s, etc.
$message_table = new PhabricatorRepositoryStatusMessage();
$conn = $message_table->establishConnection('r');
$error_count = queryfx_one(
$conn,
'SELECT MAX(messageCount) error_count FROM %T
WHERE repositoryID = %d
AND statusType IN (%Ls)
AND statusCode IN (%Ls)',
$message_table->getTableName(),
$this->getID(),
array(
PhabricatorRepositoryStatusMessage::TYPE_INIT,
PhabricatorRepositoryStatusMessage::TYPE_FETCH,
),
array(
PhabricatorRepositoryStatusMessage::CODE_ERROR,
));
$error_count = (int)$error_count['error_count'];
if ($error_count > 0) {
return (int)($minimum * $error_count);
}
// If a repository is still importing, always pull it as frequently as
// possible. This prevents us from hanging for a long time at 99.9% when
// importing an inactive repository.
if ($this->isImporting()) {
return $minimum;
}
$window_start = (PhabricatorTime::getNow() + $minimum);
$table = id(new PhabricatorRepositoryCommit());
$last_commit = queryfx_one(
$table->establishConnection('r'),
'SELECT epoch FROM %T
WHERE repositoryID = %d AND epoch <= %d
ORDER BY epoch DESC LIMIT 1',
$table->getTableName(),
$this->getID(),
$window_start);
if ($last_commit) {
$time_since_commit = ($window_start - $last_commit['epoch']);
} else {
// If the repository has no commits, treat the creation date as
// though it were the date of the last commit. This makes empty
// repositories update quickly at first but slow down over time
// if they don't see any activity.
$time_since_commit = ($window_start - $this->getDateCreated());
}
$last_few_days = phutil_units('3 days in seconds');
if ($time_since_commit <= $last_few_days) {
// For repositories with activity in the recent past, we wait one
// extra second for every 10 minutes since the last commit. This
// shorter backoff is intended to handle weekends and other short
// breaks from development.
$smart_wait = ($time_since_commit / 600);
} else {
// For repositories without recent activity, we wait one extra second
// for every 4 minutes since the last commit. This longer backoff
// handles rarely used repositories, up to the maximum.
$smart_wait = ($time_since_commit / 240);
}
// We'll never wait more than 6 hours to pull a repository.
$longest_wait = phutil_units('6 hours in seconds');
$smart_wait = min($smart_wait, $longest_wait);
$smart_wait = max($minimum, $smart_wait);
return (int)$smart_wait;
}
/**
* Time limit for cloning or copying this repository.
*
* This limit is used to timeout operations like `git clone` or `git fetch`
* when doing intracluster synchronization, building working copies, etc.
*
* @return int Maximum number of seconds to spend copying this repository.
*/
public function getCopyTimeLimit() {
return $this->getDetail('limit.copy');
}
public function setCopyTimeLimit($limit) {
return $this->setDetail('limit.copy', $limit);
}
public function getDefaultCopyTimeLimit() {
return phutil_units('15 minutes in seconds');
}
public function getEffectiveCopyTimeLimit() {
$limit = $this->getCopyTimeLimit();
if ($limit) {
return $limit;
}
return $this->getDefaultCopyTimeLimit();
}
public function getFilesizeLimit() {
return $this->getDetail('limit.filesize');
}
public function setFilesizeLimit($limit) {
return $this->setDetail('limit.filesize', $limit);
}
public function getTouchLimit() {
return $this->getDetail('limit.touch');
}
public function setTouchLimit($limit) {
return $this->setDetail('limit.touch', $limit);
}
/**
* Retrieve the service URI for the device hosting this repository.
*
* See @{method:newConduitClient} for a general discussion of interacting
* with repository services. This method provides lower-level resolution of
* services, returning raw URIs.
*
* @param PhabricatorUser Viewing user.
* @param map<string, wild> Constraints on selectable services.
* @return string|null URI, or `null` for local repositories.
*/
public function getAlmanacServiceURI(
PhabricatorUser $viewer,
array $options) {
$refs = $this->getAlmanacServiceRefs($viewer, $options);
if (!$refs) {
return null;
}
$ref = head($refs);
return $ref->getURI();
}
public function getAlmanacServiceRefs(
PhabricatorUser $viewer,
array $options) {
PhutilTypeSpec::checkMap(
$options,
array(
'neverProxy' => 'bool',
'protocols' => 'list<string>',
'writable' => 'optional bool',
));
$never_proxy = $options['neverProxy'];
$protocols = $options['protocols'];
$writable = idx($options, 'writable', false);
$cache_key = $this->getAlmanacServiceCacheKey();
if (!$cache_key) {
return array();
}
$cache = PhabricatorCaches::getMutableStructureCache();
$uris = $cache->getKey($cache_key, false);
// If we haven't built the cache yet, build it now.
if ($uris === false) {
$uris = $this->buildAlmanacServiceURIs();
$cache->setKey($cache_key, $uris);
}
if ($uris === null) {
return array();
}
$local_device = AlmanacKeys::getDeviceID();
if ($never_proxy && !$local_device) {
throw new Exception(
pht(
'Unable to handle proxied service request. This device is not '.
'registered, so it can not identify local services. Register '.
'this device before sending requests here.'));
}
$protocol_map = array_fuse($protocols);
$results = array();
foreach ($uris as $uri) {
// If we're never proxying this and it's locally satisfiable, return
// `null` to tell the caller to handle it locally. If we're allowed to
// proxy, we skip this check and may proxy the request to ourselves.
// (That proxied request will end up here with proxying forbidden,
// return `null`, and then the request will actually run.)
if ($local_device && $never_proxy) {
if ($uri['device'] == $local_device) {
return array();
}
}
if (isset($protocol_map[$uri['protocol']])) {
$results[] = $uri;
}
}
if (!$results) {
throw new Exception(
pht(
'The Almanac service for this repository is not bound to any '.
'interfaces which support the required protocols (%s).',
implode(', ', $protocols)));
}
if ($never_proxy) {
// See PHI1030. This error can arise from various device name/address
// mismatches which are hard to detect, so try to provide as much
// information as we can.
if ($writable) {
$request_type = pht('(This is a write request.)');
} else {
$request_type = pht('(This is a read request.)');
}
throw new Exception(
pht(
'This repository request (for repository "%s") has been '.
'incorrectly routed to a cluster host (with device name "%s", '.
'and hostname "%s") which can not serve the request.'.
"\n\n".
'The Almanac device address for the correct device may improperly '.
'point at this host, or the "device.id" configuration file on '.
'this host may be incorrect.'.
"\n\n".
- 'Requests routed within the cluster by Phabricator are always '.
+ 'Requests routed within the cluster are always '.
'expected to be sent to a node which can serve the request. To '.
'prevent loops, this request will not be proxied again.'.
"\n\n".
"%s",
$this->getDisplayName(),
$local_device,
php_uname('n'),
$request_type));
}
if (count($results) > 1) {
if (!$this->supportsSynchronization()) {
throw new Exception(
pht(
'Repository "%s" is bound to multiple active repository hosts, '.
'but this repository does not support cluster synchronization. '.
'Declusterize this repository or move it to a service with only '.
'one host.',
$this->getDisplayName()));
}
}
$refs = array();
foreach ($results as $result) {
$refs[] = DiffusionServiceRef::newFromDictionary($result);
}
// If we require a writable device, remove URIs which aren't writable.
if ($writable) {
foreach ($refs as $key => $ref) {
if (!$ref->isWritable()) {
unset($refs[$key]);
}
}
if (!$refs) {
throw new Exception(
pht(
'This repository ("%s") is not writable with the given '.
'protocols (%s). The Almanac service for this repository has no '.
'writable bindings that support these protocols.',
$this->getDisplayName(),
implode(', ', $protocols)));
}
}
if ($writable) {
$refs = $this->sortWritableAlmanacServiceRefs($refs);
} else {
$refs = $this->sortReadableAlmanacServiceRefs($refs);
}
return array_values($refs);
}
private function sortReadableAlmanacServiceRefs(array $refs) {
assert_instances_of($refs, 'DiffusionServiceRef');
shuffle($refs);
return $refs;
}
private function sortWritableAlmanacServiceRefs(array $refs) {
assert_instances_of($refs, 'DiffusionServiceRef');
// See T13109 for discussion of how this method routes requests.
// In the absence of other rules, we'll send traffic to devices randomly.
// We also want to select randomly among nodes which are equally good
// candidates to receive the write, and accomplish that by shuffling the
// list up front.
shuffle($refs);
$order = array();
// If some device is currently holding the write lock, send all requests
// to that device. We're trying to queue writes on a single device so they
// do not need to wait for read synchronization after earlier writes
// complete.
$writer = PhabricatorRepositoryWorkingCopyVersion::loadWriter(
$this->getPHID());
if ($writer) {
$device_phid = $writer->getWriteProperty('devicePHID');
foreach ($refs as $key => $ref) {
if ($ref->getDevicePHID() === $device_phid) {
$order[] = $key;
}
}
}
// If no device is currently holding the write lock, try to send requests
// to a device which is already up to date and will not need to synchronize
// before it can accept the write.
$versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
$this->getPHID());
if ($versions) {
$max_version = (int)max(mpull($versions, 'getRepositoryVersion'));
$max_devices = array();
foreach ($versions as $version) {
if ($version->getRepositoryVersion() == $max_version) {
$max_devices[] = $version->getDevicePHID();
}
}
$max_devices = array_fuse($max_devices);
foreach ($refs as $key => $ref) {
if (isset($max_devices[$ref->getDevicePHID()])) {
$order[] = $key;
}
}
}
// Reorder the results, putting any we've selected as preferred targets for
// the write at the head of the list.
$refs = array_select_keys($refs, $order) + $refs;
return $refs;
}
public function supportsSynchronization() {
// TODO: For now, this is only supported for Git.
if (!$this->isGit()) {
return false;
}
return true;
}
public function supportsRefs() {
if ($this->isSVN()) {
return false;
}
return true;
}
public function getAlmanacServiceCacheKey() {
$service_phid = $this->getAlmanacServicePHID();
if (!$service_phid) {
return null;
}
$repository_phid = $this->getPHID();
$parts = array(
"repo({$repository_phid})",
"serv({$service_phid})",
'v4',
);
return implode('.', $parts);
}
private function buildAlmanacServiceURIs() {
$service = $this->loadAlmanacService();
if (!$service) {
return null;
}
$bindings = $service->getActiveBindings();
if (!$bindings) {
throw new Exception(
pht(
'The Almanac service for this repository is not bound to any '.
'active interfaces.'));
}
$uris = array();
foreach ($bindings as $binding) {
$iface = $binding->getInterface();
$uri = $this->getClusterRepositoryURIFromBinding($binding);
$protocol = $uri->getProtocol();
$device_name = $iface->getDevice()->getName();
$device_phid = $iface->getDevice()->getPHID();
$uris[] = array(
'protocol' => $protocol,
'uri' => (string)$uri,
'device' => $device_name,
'writable' => (bool)$binding->getAlmanacPropertyValue('writable'),
'devicePHID' => $device_phid,
);
}
return $uris;
}
/**
* Build a new Conduit client in order to make a service call to this
* repository.
*
* If the repository is hosted locally, this method may return `null`. The
* caller should use `ConduitCall` or other local logic to complete the
* request.
*
* By default, we will return a @{class:ConduitClient} for any repository with
* a service, even if that service is on the current device.
*
* We do this because this configuration does not make very much sense in a
* production context, but is very common in a test/development context
* (where the developer's machine is both the web host and the repository
* service). By proxying in development, we get more consistent behavior
* between development and production, and don't have a major untested
* codepath.
*
* The `$never_proxy` parameter can be used to prevent this local proxying.
* If the flag is passed:
*
* - The method will return `null` (implying a local service call)
* if the repository service is hosted on the current device.
* - The method will throw if it would need to return a client.
*
* This is used to prevent loops in Conduit: the first request will proxy,
* even in development, but the second request will be identified as a
* cluster request and forced not to proxy.
*
* For lower-level service resolution, see @{method:getAlmanacServiceURI}.
*
* @param PhabricatorUser Viewing user.
* @param bool `true` to throw if a client would be returned.
* @return ConduitClient|null Client, or `null` for local repositories.
*/
public function newConduitClient(
PhabricatorUser $viewer,
$never_proxy = false) {
$uri = $this->getAlmanacServiceURI(
$viewer,
array(
'neverProxy' => $never_proxy,
'protocols' => array(
'http',
'https',
),
// At least today, no Conduit call can ever write to a repository,
// so it's fine to send anything to a read-only node.
'writable' => false,
));
if ($uri === null) {
return null;
}
$domain = id(new PhutilURI(PhabricatorEnv::getURI('/')))->getDomain();
$client = id(new ConduitClient($uri))
->setHost($domain);
if ($viewer->isOmnipotent()) {
// If the caller is the omnipotent user (normally, a daemon), we will
// sign the request with this host's asymmetric keypair.
$public_path = AlmanacKeys::getKeyPath('device.pub');
try {
$public_key = Filesystem::readFile($public_path);
} catch (Exception $ex) {
throw new PhutilAggregateException(
pht(
'Unable to read device public key while attempting to make '.
- 'authenticated method call within the Phabricator cluster. '.
+ 'authenticated method call within the cluster. '.
'Use `%s` to register keys for this device. Exception: %s',
'bin/almanac register',
$ex->getMessage()),
array($ex));
}
$private_path = AlmanacKeys::getKeyPath('device.key');
try {
$private_key = Filesystem::readFile($private_path);
$private_key = new PhutilOpaqueEnvelope($private_key);
} catch (Exception $ex) {
throw new PhutilAggregateException(
pht(
'Unable to read device private key while attempting to make '.
- 'authenticated method call within the Phabricator cluster. '.
+ 'authenticated method call within the cluster. '.
'Use `%s` to register keys for this device. Exception: %s',
'bin/almanac register',
$ex->getMessage()),
array($ex));
}
$client->setSigningKeys($public_key, $private_key);
} else {
// If the caller is a normal user, we generate or retrieve a cluster
// API token.
$token = PhabricatorConduitToken::loadClusterTokenForUser($viewer);
if ($token) {
$client->setConduitToken($token->getToken());
}
}
return $client;
}
public function newConduitClientForRequest(ConduitAPIRequest $request) {
// Figure out whether we're going to handle this request on this device,
// or proxy it to another node in the cluster.
// If this is a cluster request and we need to proxy, we'll explode here
// to prevent infinite recursion.
$viewer = $request->getViewer();
$is_cluster_request = $request->getIsClusterRequest();
$client = $this->newConduitClient(
$viewer,
$is_cluster_request);
return $client;
}
public function newConduitFuture(
PhabricatorUser $viewer,
$method,
array $params,
$never_proxy = false) {
$client = $this->newConduitClient(
$viewer,
$never_proxy);
if (!$client) {
$conduit_call = id(new ConduitCall($method, $params))
->setUser($viewer);
$future = new MethodCallFuture($conduit_call, 'execute');
} else {
$future = $client->callMethod($method, $params);
}
return $future;
}
public function getPassthroughEnvironmentalVariables() {
$env = $_ENV;
if ($this->isGit()) {
// $_ENV does not populate in CLI contexts if "E" is missing from
// "variables_order" in PHP config. Currently, we do not require this
// to be configured. Since it may not be, explicitly bring expected Git
// environmental variables into scope. This list is not exhaustive, but
// only lists variables with a known impact on commit hook behavior.
// This can be removed if we later require "E" in "variables_order".
$git_env = array(
'GIT_OBJECT_DIRECTORY',
'GIT_ALTERNATE_OBJECT_DIRECTORIES',
'GIT_QUARANTINE_PATH',
);
foreach ($git_env as $key) {
$value = getenv($key);
if (strlen($value)) {
$env[$key] = $value;
}
}
$key = 'GIT_PUSH_OPTION_COUNT';
$git_count = getenv($key);
if (strlen($git_count)) {
$git_count = (int)$git_count;
$env[$key] = $git_count;
for ($ii = 0; $ii < $git_count; $ii++) {
$key = 'GIT_PUSH_OPTION_'.$ii;
$env[$key] = getenv($key);
}
}
}
$result = array();
foreach ($env as $key => $value) {
// In Git, pass anything matching "GIT_*" though. Some of these variables
// need to be preserved to allow `git` operations to work properly when
// running from commit hooks.
if ($this->isGit()) {
if (preg_match('/^GIT_/', $key)) {
$result[$key] = $value;
}
}
}
return $result;
}
public function supportsBranchComparison() {
return $this->isGit();
}
public function isReadOnly() {
return (bool)$this->getDetail('read-only');
}
public function setReadOnly($read_only) {
return $this->setDetail('read-only', $read_only);
}
public function getReadOnlyMessage() {
return $this->getDetail('read-only-message');
}
public function setReadOnlyMessage($message) {
return $this->setDetail('read-only-message', $message);
}
public function getReadOnlyMessageForDisplay() {
$parts = array();
$parts[] = pht(
'This repository is currently in read-only maintenance mode.');
$message = $this->getReadOnlyMessage();
if ($message !== null) {
$parts[] = $message;
}
return implode("\n\n", $parts);
}
/* -( Repository URIs )---------------------------------------------------- */
public function attachURIs(array $uris) {
$custom_map = array();
foreach ($uris as $key => $uri) {
$builtin_key = $uri->getRepositoryURIBuiltinKey();
if ($builtin_key !== null) {
$custom_map[$builtin_key] = $key;
}
}
$builtin_uris = $this->newBuiltinURIs();
$seen_builtins = array();
foreach ($builtin_uris as $builtin_uri) {
$builtin_key = $builtin_uri->getRepositoryURIBuiltinKey();
$seen_builtins[$builtin_key] = true;
// If this builtin URI is disabled, don't attach it and remove the
// persisted version if it exists.
if ($builtin_uri->getIsDisabled()) {
if (isset($custom_map[$builtin_key])) {
unset($uris[$custom_map[$builtin_key]]);
}
continue;
}
// If the URI exists, make sure it's marked as not being disabled.
if (isset($custom_map[$builtin_key])) {
$uris[$custom_map[$builtin_key]]->setIsDisabled(false);
}
}
// Remove any builtins which no longer exist.
foreach ($custom_map as $builtin_key => $key) {
if (empty($seen_builtins[$builtin_key])) {
unset($uris[$key]);
}
}
$this->uris = $uris;
return $this;
}
public function getURIs() {
return $this->assertAttached($this->uris);
}
public function getCloneURIs() {
$uris = $this->getURIs();
$clone = array();
foreach ($uris as $uri) {
if (!$uri->isBuiltin()) {
continue;
}
if ($uri->getIsDisabled()) {
continue;
}
$io_type = $uri->getEffectiveIoType();
$is_clone =
($io_type == PhabricatorRepositoryURI::IO_READ) ||
($io_type == PhabricatorRepositoryURI::IO_READWRITE);
if (!$is_clone) {
continue;
}
$clone[] = $uri;
}
$clone = msort($clone, 'getURIScore');
$clone = array_reverse($clone);
return $clone;
}
public function newBuiltinURIs() {
$has_callsign = ($this->getCallsign() !== null);
$has_shortname = ($this->getRepositorySlug() !== null);
$identifier_map = array(
PhabricatorRepositoryURI::BUILTIN_IDENTIFIER_CALLSIGN => $has_callsign,
PhabricatorRepositoryURI::BUILTIN_IDENTIFIER_SHORTNAME => $has_shortname,
PhabricatorRepositoryURI::BUILTIN_IDENTIFIER_ID => true,
);
// If the view policy of the repository is public, support anonymous HTTP
// even if authenticated HTTP is not supported.
if ($this->getViewPolicy() === PhabricatorPolicies::POLICY_PUBLIC) {
$allow_http = true;
} else {
$allow_http = PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth');
}
$base_uri = PhabricatorEnv::getURI('/');
$base_uri = new PhutilURI($base_uri);
$has_https = ($base_uri->getProtocol() == 'https');
$has_https = ($has_https && $allow_http);
$has_http = !PhabricatorEnv::getEnvConfig('security.require-https');
$has_http = ($has_http && $allow_http);
// HTTP is not supported for Subversion.
if ($this->isSVN()) {
$has_http = false;
$has_https = false;
}
$has_ssh = (bool)strlen(PhabricatorEnv::getEnvConfig('phd.user'));
$protocol_map = array(
PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH => $has_ssh,
PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTPS => $has_https,
PhabricatorRepositoryURI::BUILTIN_PROTOCOL_HTTP => $has_http,
);
$uris = array();
foreach ($protocol_map as $protocol => $proto_supported) {
foreach ($identifier_map as $identifier => $id_supported) {
// This is just a dummy value because it can't be empty; we'll force
// it to a proper value when using it in the UI.
$builtin_uri = "{$protocol}://{$identifier}";
$uris[] = PhabricatorRepositoryURI::initializeNewURI()
->setRepositoryPHID($this->getPHID())
->attachRepository($this)
->setBuiltinProtocol($protocol)
->setBuiltinIdentifier($identifier)
->setURI($builtin_uri)
->setIsDisabled((int)(!$proto_supported || !$id_supported));
}
}
return $uris;
}
public function getClusterRepositoryURIFromBinding(
AlmanacBinding $binding) {
$protocol = $binding->getAlmanacPropertyValue('protocol');
if ($protocol === null) {
$protocol = 'https';
}
$iface = $binding->getInterface();
$address = $iface->renderDisplayAddress();
$path = $this->getURI();
return id(new PhutilURI("{$protocol}://{$address}"))
->setPath($path);
}
public function loadAlmanacService() {
$service_phid = $this->getAlmanacServicePHID();
if (!$service_phid) {
// No service, so this is a local repository.
return null;
}
$service = id(new AlmanacServiceQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($service_phid))
->needActiveBindings(true)
->needProperties(true)
->executeOne();
if (!$service) {
throw new Exception(
pht(
'The Almanac service for this repository is invalid or could not '.
'be loaded.'));
}
$service_type = $service->getServiceImplementation();
if (!($service_type instanceof AlmanacClusterRepositoryServiceType)) {
throw new Exception(
pht(
'The Almanac service for this repository does not have the correct '.
'service type.'));
}
return $service;
}
public function markImporting() {
$this->openTransaction();
$this->beginReadLocking();
$repository = $this->reload();
$repository->setDetail('importing', true);
$repository->save();
$this->endReadLocking();
$this->saveTransaction();
return $repository;
}
/* -( Symbols )-------------------------------------------------------------*/
public function getSymbolSources() {
return $this->getDetail('symbol-sources', array());
}
public function getSymbolLanguages() {
return $this->getDetail('symbol-languages', array());
}
/* -( Staging )------------------------------------------------------------ */
public function supportsStaging() {
return $this->isGit();
}
public function getStagingURI() {
if (!$this->supportsStaging()) {
return null;
}
return $this->getDetail('staging-uri', null);
}
/* -( Automation )--------------------------------------------------------- */
public function supportsAutomation() {
return $this->isGit();
}
public function canPerformAutomation() {
if (!$this->supportsAutomation()) {
return false;
}
if (!$this->getAutomationBlueprintPHIDs()) {
return false;
}
return true;
}
public function getAutomationBlueprintPHIDs() {
if (!$this->supportsAutomation()) {
return array();
}
return $this->getDetail('automation.blueprintPHIDs', array());
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorRepositoryEditor();
}
public function getApplicationTransactionTemplate() {
return new PhabricatorRepositoryTransaction();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
DiffusionPushCapability::CAPABILITY,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
case DiffusionPushCapability::CAPABILITY:
return $this->getPushPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
return false;
}
/* -( PhabricatorMarkupInterface )----------------------------------------- */
public function getMarkupFieldKey($field) {
$hash = PhabricatorHash::digestForIndex($this->getMarkupText($field));
return "repo:{$hash}";
}
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newMarkupEngine(array());
}
public function getMarkupText($field) {
return $this->getDetail('description');
}
public function didMarkupText(
$field,
$output,
PhutilMarkupEngine $engine) {
require_celerity_resource('phabricator-remarkup-css');
return phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$output);
}
public function shouldUseMarkupCache($field) {
return true;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$phid = $this->getPHID();
$this->openTransaction();
$this->delete();
PhabricatorRepositoryURIIndex::updateRepositoryURIs($phid, array());
$books = id(new DivinerBookQuery())
->setViewer($engine->getViewer())
->withRepositoryPHIDs(array($phid))
->execute();
foreach ($books as $book) {
$engine->destroyObject($book);
}
$atoms = id(new DivinerAtomQuery())
->setViewer($engine->getViewer())
->withRepositoryPHIDs(array($phid))
->execute();
foreach ($atoms as $atom) {
$engine->destroyObject($atom);
}
$lfs_refs = id(new PhabricatorRepositoryGitLFSRefQuery())
->setViewer($engine->getViewer())
->withRepositoryPHIDs(array($phid))
->execute();
foreach ($lfs_refs as $ref) {
$engine->destroyObject($ref);
}
$this->saveTransaction();
}
/* -( PhabricatorDestructibleCodexInterface )------------------------------ */
public function newDestructibleCodex() {
return new PhabricatorRepositoryDestructibleCodex();
}
/* -( PhabricatorSpacesInterface )----------------------------------------- */
public function getSpacePHID() {
return $this->spacePHID;
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('name')
->setType('string')
->setDescription(pht('The repository name.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('vcs')
->setType('string')
->setDescription(
pht('The VCS this repository uses ("git", "hg" or "svn").')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('callsign')
->setType('string')
->setDescription(pht('The repository callsign, if it has one.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('shortName')
->setType('string')
->setDescription(pht('Unique short name, if the repository has one.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('status')
->setType('string')
->setDescription(pht('Active or inactive status.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('isImporting')
->setType('bool')
->setDescription(
pht(
'True if the repository is importing initial commits.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('almanacServicePHID')
->setType('phid?')
->setDescription(
pht(
'The Almanac Service that hosts this repository, if the '.
'repository is clustered.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('refRules')
->setType('map<string, list<string>>')
->setDescription(
pht(
'The "Fetch" and "Permanent Ref" rules for this repository.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('defaultBranch')
->setType('string?')
->setDescription(pht('Default branch name.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('description')
->setType('remarkup')
->setDescription(pht('Repository description.')),
);
}
public function getFieldValuesForConduit() {
$fetch_rules = $this->getFetchRules();
$track_rules = $this->getTrackOnlyRules();
$permanent_rules = $this->getPermanentRefRules();
$fetch_rules = $this->getStringListForConduit($fetch_rules);
$track_rules = $this->getStringListForConduit($track_rules);
$permanent_rules = $this->getStringListForConduit($permanent_rules);
$default_branch = $this->getDefaultBranch();
if (!strlen($default_branch)) {
$default_branch = null;
}
return array(
'name' => $this->getName(),
'vcs' => $this->getVersionControlSystem(),
'callsign' => $this->getCallsign(),
'shortName' => $this->getRepositorySlug(),
'status' => $this->getStatus(),
'isImporting' => (bool)$this->isImporting(),
'almanacServicePHID' => $this->getAlmanacServicePHID(),
'refRules' => array(
'fetchRules' => $fetch_rules,
'trackRules' => $track_rules,
'permanentRefRules' => $permanent_rules,
),
'defaultBranch' => $default_branch,
'description' => array(
'raw' => (string)$this->getDetail('description'),
),
);
}
private function getStringListForConduit($list) {
if (!is_array($list)) {
$list = array();
}
foreach ($list as $key => $value) {
$value = (string)$value;
if (!strlen($value)) {
unset($list[$key]);
}
}
return array_values($list);
}
public function getConduitSearchAttachments() {
return array(
id(new DiffusionRepositoryURIsSearchEngineAttachment())
->setAttachmentKey('uris'),
id(new DiffusionRepositoryMetricsSearchEngineAttachment())
->setAttachmentKey('metrics'),
);
}
/* -( PhabricatorFulltextInterface )--------------------------------------- */
public function newFulltextEngine() {
return new PhabricatorRepositoryFulltextEngine();
}
/* -( PhabricatorFerretInterface )----------------------------------------- */
public function newFerretEngine() {
return new PhabricatorRepositoryFerretEngine();
}
}
diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php
index 0303e36919..9e20a36676 100644
--- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php
+++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php
@@ -1,978 +1,978 @@
<?php
final class PhabricatorRepositoryCommit
extends PhabricatorRepositoryDAO
implements
PhabricatorPolicyInterface,
PhabricatorFlaggableInterface,
PhabricatorProjectInterface,
PhabricatorTokenReceiverInterface,
PhabricatorSubscribableInterface,
PhabricatorMentionableInterface,
HarbormasterBuildableInterface,
HarbormasterCircleCIBuildableInterface,
HarbormasterBuildkiteBuildableInterface,
PhabricatorCustomFieldInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorTimelineInterface,
PhabricatorFulltextInterface,
PhabricatorFerretInterface,
PhabricatorConduitResultInterface,
PhabricatorDraftInterface {
protected $repositoryID;
protected $phid;
protected $authorIdentityPHID;
protected $committerIdentityPHID;
protected $commitIdentifier;
protected $epoch;
protected $authorPHID;
protected $auditStatus = DiffusionCommitAuditStatus::NONE;
protected $summary = '';
protected $importStatus = 0;
const IMPORTED_MESSAGE = 1;
const IMPORTED_CHANGE = 2;
const IMPORTED_PUBLISH = 8;
const IMPORTED_ALL = 11;
const IMPORTED_PERMANENT = 1024;
const IMPORTED_UNREACHABLE = 2048;
private $commitData = self::ATTACHABLE;
private $audits = self::ATTACHABLE;
private $repository = self::ATTACHABLE;
private $customFields = self::ATTACHABLE;
private $authorIdentity = self::ATTACHABLE;
private $committerIdentity = self::ATTACHABLE;
private $drafts = array();
private $auditAuthorityPHIDs = array();
public function attachRepository(PhabricatorRepository $repository) {
$this->repository = $repository;
return $this;
}
public function getRepository($assert_attached = true) {
if ($assert_attached) {
return $this->assertAttached($this->repository);
}
return $this->repository;
}
public function isPartiallyImported($mask) {
return (($mask & $this->getImportStatus()) == $mask);
}
public function isImported() {
return $this->isPartiallyImported(self::IMPORTED_ALL);
}
public function isUnreachable() {
return $this->isPartiallyImported(self::IMPORTED_UNREACHABLE);
}
public function writeImportStatusFlag($flag) {
return $this->adjustImportStatusFlag($flag, true);
}
public function clearImportStatusFlag($flag) {
return $this->adjustImportStatusFlag($flag, false);
}
private function adjustImportStatusFlag($flag, $set) {
$conn_w = $this->establishConnection('w');
$table_name = $this->getTableName();
$id = $this->getID();
if ($set) {
queryfx(
$conn_w,
'UPDATE %T SET importStatus = (importStatus | %d) WHERE id = %d',
$table_name,
$flag,
$id);
$this->setImportStatus($this->getImportStatus() | $flag);
} else {
queryfx(
$conn_w,
'UPDATE %T SET importStatus = (importStatus & ~%d) WHERE id = %d',
$table_name,
$flag,
$id);
$this->setImportStatus($this->getImportStatus() & ~$flag);
}
return $this;
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'commitIdentifier' => 'text40',
'authorPHID' => 'phid?',
'authorIdentityPHID' => 'phid?',
'committerIdentityPHID' => 'phid?',
'auditStatus' => 'text32',
'summary' => 'text255',
'importStatus' => 'uint32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
),
'repositoryID' => array(
'columns' => array('repositoryID', 'importStatus'),
),
'authorPHID' => array(
'columns' => array('authorPHID', 'auditStatus', 'epoch'),
),
'repositoryID_2' => array(
'columns' => array('repositoryID', 'epoch'),
),
'key_commit_identity' => array(
'columns' => array('commitIdentifier', 'repositoryID'),
'unique' => true,
),
'key_epoch' => array(
'columns' => array('epoch'),
),
'key_author' => array(
'columns' => array('authorPHID', 'epoch'),
),
),
self::CONFIG_NO_MUTATE => array(
'importStatus',
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorRepositoryCommitPHIDType::TYPECONST);
}
public function loadCommitData() {
if (!$this->getID()) {
return null;
}
return id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$this->getID());
}
public function attachCommitData(
PhabricatorRepositoryCommitData $data = null) {
$this->commitData = $data;
return $this;
}
public function hasCommitData() {
return ($this->commitData !== self::ATTACHABLE) &&
($this->commitData !== null);
}
public function getCommitData() {
return $this->assertAttached($this->commitData);
}
public function attachAudits(array $audits) {
assert_instances_of($audits, 'PhabricatorRepositoryAuditRequest');
$this->audits = $audits;
return $this;
}
public function getAudits() {
return $this->assertAttached($this->audits);
}
public function hasAttachedAudits() {
return ($this->audits !== self::ATTACHABLE);
}
public function attachIdentities(
PhabricatorRepositoryIdentity $author = null,
PhabricatorRepositoryIdentity $committer = null) {
$this->authorIdentity = $author;
$this->committerIdentity = $committer;
return $this;
}
public function getAuthorIdentity() {
return $this->assertAttached($this->authorIdentity);
}
public function getCommitterIdentity() {
return $this->assertAttached($this->committerIdentity);
}
public function attachAuditAuthority(
PhabricatorUser $user,
array $authority) {
$user_phid = $user->getPHID();
if (!$user->getPHID()) {
throw new Exception(
pht('You can not attach audit authority for a user with no PHID.'));
}
$this->auditAuthorityPHIDs[$user_phid] = $authority;
return $this;
}
public function hasAuditAuthority(
PhabricatorUser $user,
PhabricatorRepositoryAuditRequest $audit) {
$user_phid = $user->getPHID();
if (!$user_phid) {
return false;
}
$map = $this->assertAttachedKey($this->auditAuthorityPHIDs, $user_phid);
return isset($map[$audit->getAuditorPHID()]);
}
public function writeOwnersEdges(array $package_phids) {
$src_phid = $this->getPHID();
$edge_type = DiffusionCommitHasPackageEdgeType::EDGECONST;
$editor = new PhabricatorEdgeEditor();
$dst_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$src_phid,
$edge_type);
foreach ($dst_phids as $dst_phid) {
$editor->removeEdge($src_phid, $edge_type, $dst_phid);
}
foreach ($package_phids as $package_phid) {
$editor->addEdge($src_phid, $edge_type, $package_phid);
}
$editor->save();
return $this;
}
public function getAuditorPHIDsForEdit() {
$audits = $this->getAudits();
return mpull($audits, 'getAuditorPHID');
}
public function delete() {
$data = $this->loadCommitData();
$audits = id(new PhabricatorRepositoryAuditRequest())
->loadAllWhere('commitPHID = %s', $this->getPHID());
$this->openTransaction();
if ($data) {
$data->delete();
}
foreach ($audits as $audit) {
$audit->delete();
}
$result = parent::delete();
$this->saveTransaction();
return $result;
}
public function getDateCreated() {
// This is primarily to make analysis of commits with the Fact engine work.
return $this->getEpoch();
}
public function getURI() {
return '/'.$this->getMonogram();
}
/**
* Synchronize a commit's overall audit status with the individual audit
* triggers.
*/
public function updateAuditStatus(array $requests) {
assert_instances_of($requests, 'PhabricatorRepositoryAuditRequest');
$any_concern = false;
$any_accept = false;
$any_need = false;
foreach ($requests as $request) {
switch ($request->getAuditStatus()) {
case PhabricatorAuditRequestStatus::AUDIT_REQUIRED:
case PhabricatorAuditRequestStatus::AUDIT_REQUESTED:
$any_need = true;
break;
case PhabricatorAuditRequestStatus::ACCEPTED:
$any_accept = true;
break;
case PhabricatorAuditRequestStatus::CONCERNED:
$any_concern = true;
break;
}
}
if ($any_concern) {
if ($this->isAuditStatusNeedsVerification()) {
// If the change is in "Needs Verification", we keep it there as
// long as any auditors still have concerns.
$status = DiffusionCommitAuditStatus::NEEDS_VERIFICATION;
} else {
$status = DiffusionCommitAuditStatus::CONCERN_RAISED;
}
} else if ($any_accept) {
if ($any_need) {
$status = DiffusionCommitAuditStatus::PARTIALLY_AUDITED;
} else {
$status = DiffusionCommitAuditStatus::AUDITED;
}
} else if ($any_need) {
$status = DiffusionCommitAuditStatus::NEEDS_AUDIT;
} else {
$status = DiffusionCommitAuditStatus::NONE;
}
return $this->setAuditStatus($status);
}
public function getMonogram() {
$repository = $this->getRepository();
$callsign = $repository->getCallsign();
$identifier = $this->getCommitIdentifier();
if ($callsign !== null) {
return "r{$callsign}{$identifier}";
} else {
$id = $repository->getID();
return "R{$id}:{$identifier}";
}
}
public function getDisplayName() {
$repository = $this->getRepository();
$identifier = $this->getCommitIdentifier();
return $repository->formatCommitName($identifier);
}
/**
* Return a local display name for use in the context of the containing
* repository.
*
* In Git and Mercurial, this returns only a short hash, like "abcdef012345".
* See @{method:getDisplayName} for a short name that always includes
* repository context.
*
* @return string Short human-readable name for use inside a repository.
*/
public function getLocalName() {
$repository = $this->getRepository();
$identifier = $this->getCommitIdentifier();
return $repository->formatCommitName($identifier, $local = true);
}
public function loadIdentities(PhabricatorUser $viewer) {
if ($this->authorIdentity !== self::ATTACHABLE) {
return $this;
}
$commit = id(new DiffusionCommitQuery())
->setViewer($viewer)
->withIDs(array($this->getID()))
->needIdentities(true)
->executeOne();
$author_identity = $commit->getAuthorIdentity();
$committer_identity = $commit->getCommitterIdentity();
return $this->attachIdentities($author_identity, $committer_identity);
}
public function hasCommitterIdentity() {
return ($this->getCommitterIdentity() !== null);
}
public function hasAuthorIdentity() {
return ($this->getAuthorIdentity() !== null);
}
public function getCommitterDisplayPHID() {
if ($this->hasCommitterIdentity()) {
return $this->getCommitterIdentity()->getIdentityDisplayPHID();
}
$data = $this->getCommitData();
return $data->getCommitDetail('committerPHID');
}
public function getAuthorDisplayPHID() {
if ($this->hasAuthorIdentity()) {
return $this->getAuthorIdentity()->getIdentityDisplayPHID();
}
$data = $this->getCommitData();
return $data->getCommitDetail('authorPHID');
}
public function getEffectiveAuthorPHID() {
if ($this->hasAuthorIdentity()) {
$identity = $this->getAuthorIdentity();
if ($identity->hasEffectiveUser()) {
return $identity->getCurrentEffectiveUserPHID();
}
}
$data = $this->getCommitData();
return $data->getCommitDetail('authorPHID');
}
public function getAuditStatusObject() {
$status = $this->getAuditStatus();
return DiffusionCommitAuditStatus::newForStatus($status);
}
public function isAuditStatusNoAudit() {
return $this->getAuditStatusObject()->isNoAudit();
}
public function isAuditStatusNeedsAudit() {
return $this->getAuditStatusObject()->isNeedsAudit();
}
public function isAuditStatusConcernRaised() {
return $this->getAuditStatusObject()->isConcernRaised();
}
public function isAuditStatusNeedsVerification() {
return $this->getAuditStatusObject()->isNeedsVerification();
}
public function isAuditStatusPartiallyAudited() {
return $this->getAuditStatusObject()->isPartiallyAudited();
}
public function isAuditStatusAudited() {
return $this->getAuditStatusObject()->isAudited();
}
public function isPermanentCommit() {
return (bool)$this->isPartiallyImported(self::IMPORTED_PERMANENT);
}
public function newCommitAuthorView(PhabricatorUser $viewer) {
$author_phid = $this->getAuthorDisplayPHID();
if ($author_phid) {
$handles = $viewer->loadHandles(array($author_phid));
return $handles[$author_phid]->renderLink();
}
$author = $this->getRawAuthorStringForDisplay();
if (strlen($author)) {
return DiffusionView::renderName($author);
}
return null;
}
public function newCommitCommitterView(PhabricatorUser $viewer) {
$committer_phid = $this->getCommitterDisplayPHID();
if ($committer_phid) {
$handles = $viewer->loadHandles(array($committer_phid));
return $handles[$committer_phid]->renderLink();
}
$committer = $this->getRawCommitterStringForDisplay();
if (strlen($committer)) {
return DiffusionView::renderName($committer);
}
return null;
}
public function isAuthorSameAsCommitter() {
$author_phid = $this->getAuthorDisplayPHID();
$committer_phid = $this->getCommitterDisplayPHID();
if ($author_phid && $committer_phid) {
return ($author_phid === $committer_phid);
}
if ($author_phid || $committer_phid) {
return false;
}
$author = $this->getRawAuthorStringForDisplay();
$committer = $this->getRawCommitterStringForDisplay();
return ($author === $committer);
}
private function getRawAuthorStringForDisplay() {
$data = $this->getCommitData();
return $data->getAuthorString();
}
private function getRawCommitterStringForDisplay() {
$data = $this->getCommitData();
return $data->getCommitterString();
}
public function getCommitMessageForDisplay() {
$data = $this->getCommitData();
$message = $data->getCommitMessage();
return $message;
}
public function newCommitRef(PhabricatorUser $viewer) {
$repository = $this->getRepository();
$future = $repository->newConduitFuture(
$viewer,
'internal.commit.search',
array(
'constraints' => array(
'repositoryPHIDs' => array($repository->getPHID()),
'phids' => array($this->getPHID()),
),
));
$result = $future->resolve();
$commit_display = $this->getMonogram();
if (empty($result['data'])) {
throw new Exception(
pht(
'Unable to retrieve details for commit "%s"!',
$commit_display));
}
if (count($result['data']) !== 1) {
throw new Exception(
pht(
'Got too many results (%s) for commit "%s", expected %s.',
phutil_count($result['data']),
$commit_display,
1));
}
$record = head($result['data']);
$ref_record = idxv($record, array('fields', 'ref'));
if (!$ref_record) {
throw new Exception(
pht(
'Unable to retrieve CommitRef record for commit "%s".',
$commit_display));
}
return DiffusionCommitRef::newFromDictionary($ref_record);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getRepository()->getPolicy($capability);
case PhabricatorPolicyCapability::CAN_EDIT:
return PhabricatorPolicies::POLICY_USER;
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getRepository()->hasAutomaticCapability($capability, $viewer);
}
public function describeAutomaticCapability($capability) {
return pht(
'Commits inherit the policies of the repository they belong to.');
}
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
public function getUsersToNotifyOfTokenGiven() {
return array(
$this->getAuthorPHID(),
);
}
/* -( Stuff for serialization )---------------------------------------------- */
/**
* NOTE: this is not a complete serialization; only the 'protected' fields are
* involved. This is due to ease of (ab)using the Lisk abstraction to get this
* done, as well as complexity of the other fields.
*/
public function toDictionary() {
return array(
'repositoryID' => $this->getRepositoryID(),
'phid' => $this->getPHID(),
'commitIdentifier' => $this->getCommitIdentifier(),
'epoch' => $this->getEpoch(),
'authorPHID' => $this->getAuthorPHID(),
'auditStatus' => $this->getAuditStatus(),
'summary' => $this->getSummary(),
'importStatus' => $this->getImportStatus(),
);
}
public static function newFromDictionary(array $dict) {
return id(new PhabricatorRepositoryCommit())
->loadFromArray($dict);
}
/* -( HarbormasterBuildableInterface )------------------------------------- */
public function getHarbormasterBuildableDisplayPHID() {
return $this->getHarbormasterBuildablePHID();
}
public function getHarbormasterBuildablePHID() {
return $this->getPHID();
}
public function getHarbormasterContainerPHID() {
return $this->getRepository()->getPHID();
}
public function getBuildVariables() {
$results = array();
$results['buildable.commit'] = $this->getCommitIdentifier();
$repo = $this->getRepository();
$results['repository.callsign'] = $repo->getCallsign();
$results['repository.phid'] = $repo->getPHID();
$results['repository.vcs'] = $repo->getVersionControlSystem();
$results['repository.uri'] = $repo->getPublicCloneURI();
return $results;
}
public function getAvailableBuildVariables() {
return array(
'buildable.commit' => pht('The commit identifier, if applicable.'),
'repository.callsign' =>
- pht('The callsign of the repository in Phabricator.'),
+ pht('The callsign of the repository.'),
'repository.phid' =>
- pht('The PHID of the repository in Phabricator.'),
+ pht('The PHID of the repository.'),
'repository.vcs' =>
pht('The version control system, either "svn", "hg" or "git".'),
'repository.uri' =>
pht('The URI to clone or checkout the repository from.'),
);
}
public function newBuildableEngine() {
return new DiffusionBuildableEngine();
}
/* -( HarbormasterCircleCIBuildableInterface )----------------------------- */
public function getCircleCIGitHubRepositoryURI() {
$repository = $this->getRepository();
$commit_phid = $this->getPHID();
$repository_phid = $repository->getPHID();
if ($repository->isHosted()) {
throw new Exception(
pht(
'This commit ("%s") is associated with a hosted repository '.
'("%s"). Repositories must be imported from GitHub to be built '.
'with CircleCI.',
$commit_phid,
$repository_phid));
}
$remote_uri = $repository->getRemoteURI();
$path = HarbormasterCircleCIBuildStepImplementation::getGitHubPath(
$remote_uri);
if (!$path) {
throw new Exception(
pht(
'This commit ("%s") is associated with a repository ("%s") which '.
'has a remote URI ("%s") that does not appear to be hosted on '.
'GitHub. Repositories must be hosted on GitHub to be built with '.
'CircleCI.',
$commit_phid,
$repository_phid,
$remote_uri));
}
return $remote_uri;
}
public function getCircleCIBuildIdentifierType() {
return 'revision';
}
public function getCircleCIBuildIdentifier() {
return $this->getCommitIdentifier();
}
/* -( HarbormasterBuildkiteBuildableInterface )---------------------------- */
public function getBuildkiteBranch() {
$viewer = PhabricatorUser::getOmnipotentUser();
$repository = $this->getRepository();
$branches = DiffusionQuery::callConduitWithDiffusionRequest(
$viewer,
DiffusionRequest::newFromDictionary(
array(
'repository' => $repository,
'user' => $viewer,
)),
'diffusion.branchquery',
array(
'contains' => $this->getCommitIdentifier(),
'repository' => $repository->getPHID(),
));
if (!$branches) {
throw new Exception(
pht(
'Commit "%s" is not an ancestor of any branch head, so it can not '.
'be built with Buildkite.',
$this->getCommitIdentifier()));
}
$branch = head($branches);
return 'refs/heads/'.$branch['shortName'];
}
public function getBuildkiteCommit() {
return $this->getCommitIdentifier();
}
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
public function getCustomFieldSpecificationForRole($role) {
return PhabricatorEnv::getEnvConfig('diffusion.fields');
}
public function getCustomFieldBaseClass() {
return 'PhabricatorCommitCustomField';
}
public function getCustomFields() {
return $this->assertAttached($this->customFields);
}
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
$this->customFields = $fields;
return $this;
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
public function isAutomaticallySubscribed($phid) {
// TODO: This should also list auditors, but handling that is a bit messy
// right now because we are not guaranteed to have the data. (It should not
// include resigned auditors.)
return ($phid == $this->getAuthorPHID());
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorAuditEditor();
}
public function getApplicationTransactionTemplate() {
return new PhabricatorAuditTransaction();
}
/* -( PhabricatorFulltextInterface )--------------------------------------- */
public function newFulltextEngine() {
return new DiffusionCommitFulltextEngine();
}
/* -( PhabricatorFerretInterface )----------------------------------------- */
public function newFerretEngine() {
return new DiffusionCommitFerretEngine();
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('identifier')
->setType('string')
->setDescription(pht('The commit identifier.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('repositoryPHID')
->setType('phid')
->setDescription(pht('The repository this commit belongs to.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('author')
->setType('map<string, wild>')
->setDescription(pht('Information about the commit author.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('committer')
->setType('map<string, wild>')
->setDescription(pht('Information about the committer.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('isImported')
->setType('bool')
->setDescription(pht('True if the commit is fully imported.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('isUnreachable')
->setType('bool')
->setDescription(
pht(
'True if the commit is not the ancestor of any tag, branch, or '.
'ref.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('auditStatus')
->setType('map<string, wild>')
->setDescription(pht('Information about the current audit status.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('message')
->setType('string')
->setDescription(pht('The commit message.')),
);
}
public function getFieldValuesForConduit() {
$data = $this->getCommitData();
$author_identity = $this->getAuthorIdentity();
if ($author_identity) {
$author_name = $author_identity->getIdentityDisplayName();
$author_email = $author_identity->getIdentityEmailAddress();
$author_raw = $author_identity->getIdentityName();
$author_identity_phid = $author_identity->getPHID();
$author_user_phid = $author_identity->getCurrentEffectiveUserPHID();
} else {
$author_name = null;
$author_email = null;
$author_raw = null;
$author_identity_phid = null;
$author_user_phid = null;
}
$committer_identity = $this->getCommitterIdentity();
if ($committer_identity) {
$committer_name = $committer_identity->getIdentityDisplayName();
$committer_email = $committer_identity->getIdentityEmailAddress();
$committer_raw = $committer_identity->getIdentityName();
$committer_identity_phid = $committer_identity->getPHID();
$committer_user_phid = $committer_identity->getCurrentEffectiveUserPHID();
} else {
$committer_name = null;
$committer_email = null;
$committer_raw = null;
$committer_identity_phid = null;
$committer_user_phid = null;
}
$author_epoch = $data->getAuthorEpoch();
$audit_status = $this->getAuditStatusObject();
return array(
'identifier' => $this->getCommitIdentifier(),
'repositoryPHID' => $this->getRepository()->getPHID(),
'author' => array(
'name' => $author_name,
'email' => $author_email,
'raw' => $author_raw,
'epoch' => $author_epoch,
'identityPHID' => $author_identity_phid,
'userPHID' => $author_user_phid,
),
'committer' => array(
'name' => $committer_name,
'email' => $committer_email,
'raw' => $committer_raw,
'epoch' => (int)$this->getEpoch(),
'identityPHID' => $committer_identity_phid,
'userPHID' => $committer_user_phid,
),
'isUnreachable' => (bool)$this->isUnreachable(),
'isImported' => (bool)$this->isImported(),
'auditStatus' => array(
'value' => $audit_status->getKey(),
'name' => $audit_status->getName(),
'closed' => (bool)$audit_status->getIsClosed(),
'color.ansi' => $audit_status->getAnsiColor(),
),
'message' => $data->getCommitMessage(),
);
}
public function getConduitSearchAttachments() {
return array(
id(new DiffusionAuditorsSearchEngineAttachment())
->setAttachmentKey('auditors'),
);
}
/* -( PhabricatorDraftInterface )------------------------------------------ */
public function newDraftEngine() {
return new DiffusionCommitDraftEngine();
}
public function getHasDraft(PhabricatorUser $viewer) {
return $this->assertAttachedKey($this->drafts, $viewer->getCacheFragment());
}
public function attachHasDraft(PhabricatorUser $viewer, $has_draft) {
$this->drafts[$viewer->getCacheFragment()] = $has_draft;
return $this;
}
/* -( PhabricatorTimelineInterface )--------------------------------------- */
public function newTimelineEngine() {
return new DiffusionCommitTimelineEngine();
}
}
diff --git a/src/applications/repository/storage/PhabricatorRepositoryURI.php b/src/applications/repository/storage/PhabricatorRepositoryURI.php
index 98c08baa00..26db694a66 100644
--- a/src/applications/repository/storage/PhabricatorRepositoryURI.php
+++ b/src/applications/repository/storage/PhabricatorRepositoryURI.php
@@ -1,764 +1,762 @@
<?php
final class PhabricatorRepositoryURI
extends PhabricatorRepositoryDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface,
PhabricatorExtendedPolicyInterface,
PhabricatorConduitResultInterface {
protected $repositoryPHID;
protected $uri;
protected $builtinProtocol;
protected $builtinIdentifier;
protected $credentialPHID;
protected $ioType;
protected $displayType;
protected $isDisabled;
private $repository = self::ATTACHABLE;
const BUILTIN_PROTOCOL_SSH = 'ssh';
const BUILTIN_PROTOCOL_HTTP = 'http';
const BUILTIN_PROTOCOL_HTTPS = 'https';
const BUILTIN_IDENTIFIER_ID = 'id';
const BUILTIN_IDENTIFIER_SHORTNAME = 'shortname';
const BUILTIN_IDENTIFIER_CALLSIGN = 'callsign';
const DISPLAY_DEFAULT = 'default';
const DISPLAY_NEVER = 'never';
const DISPLAY_ALWAYS = 'always';
const IO_DEFAULT = 'default';
const IO_OBSERVE = 'observe';
const IO_MIRROR = 'mirror';
const IO_NONE = 'none';
const IO_READ = 'read';
const IO_READWRITE = 'readwrite';
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'uri' => 'text255',
'builtinProtocol' => 'text32?',
'builtinIdentifier' => 'text32?',
'credentialPHID' => 'phid?',
'ioType' => 'text32',
'displayType' => 'text32',
'isDisabled' => 'bool',
),
self::CONFIG_KEY_SCHEMA => array(
'key_builtin' => array(
'columns' => array(
'repositoryPHID',
'builtinProtocol',
'builtinIdentifier',
),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public static function initializeNewURI() {
return id(new self())
->setIoType(self::IO_DEFAULT)
->setDisplayType(self::DISPLAY_DEFAULT)
->setIsDisabled(0);
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorRepositoryURIPHIDType::TYPECONST);
}
public function attachRepository(PhabricatorRepository $repository) {
$this->repository = $repository;
return $this;
}
public function getRepository() {
return $this->assertAttached($this->repository);
}
public function getRepositoryURIBuiltinKey() {
if (!$this->getBuiltinProtocol()) {
return null;
}
$parts = array(
$this->getBuiltinProtocol(),
$this->getBuiltinIdentifier(),
);
return implode('.', $parts);
}
public function isBuiltin() {
return (bool)$this->getBuiltinProtocol();
}
public function getEffectiveDisplayType() {
$display = $this->getDisplayType();
if ($display != self::DISPLAY_DEFAULT) {
return $display;
}
return $this->getDefaultDisplayType();
}
public function getDefaultDisplayType() {
switch ($this->getEffectiveIOType()) {
case self::IO_MIRROR:
case self::IO_OBSERVE:
case self::IO_NONE:
return self::DISPLAY_NEVER;
case self::IO_READ:
case self::IO_READWRITE:
// By default, only show the "best" version of the builtin URI, not the
// other redundant versions.
$repository = $this->getRepository();
$other_uris = $repository->getURIs();
$identifier_value = array(
self::BUILTIN_IDENTIFIER_SHORTNAME => 3,
self::BUILTIN_IDENTIFIER_CALLSIGN => 2,
self::BUILTIN_IDENTIFIER_ID => 1,
);
$have_identifiers = array();
foreach ($other_uris as $other_uri) {
if ($other_uri->getIsDisabled()) {
continue;
}
$identifier = $other_uri->getBuiltinIdentifier();
if (!$identifier) {
continue;
}
$have_identifiers[$identifier] = $identifier_value[$identifier];
}
$best_identifier = max($have_identifiers);
$this_identifier = $identifier_value[$this->getBuiltinIdentifier()];
if ($this_identifier < $best_identifier) {
return self::DISPLAY_NEVER;
}
return self::DISPLAY_ALWAYS;
}
return self::DISPLAY_NEVER;
}
public function getEffectiveIOType() {
$io = $this->getIoType();
if ($io != self::IO_DEFAULT) {
return $io;
}
return $this->getDefaultIOType();
}
public function getDefaultIOType() {
if ($this->isBuiltin()) {
$repository = $this->getRepository();
$other_uris = $repository->getURIs();
$any_observe = false;
foreach ($other_uris as $other_uri) {
if ($other_uri->getIoType() == self::IO_OBSERVE) {
$any_observe = true;
break;
}
}
if ($any_observe) {
return self::IO_READ;
} else {
return self::IO_READWRITE;
}
}
return self::IO_NONE;
}
public function getNormalizedURI() {
$vcs = $this->getRepository()->getVersionControlSystem();
$map = array(
PhabricatorRepositoryType::REPOSITORY_TYPE_GIT =>
ArcanistRepositoryURINormalizer::TYPE_GIT,
PhabricatorRepositoryType::REPOSITORY_TYPE_SVN =>
ArcanistRepositoryURINormalizer::TYPE_SVN,
PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL =>
ArcanistRepositoryURINormalizer::TYPE_MERCURIAL,
);
$type = $map[$vcs];
$display = (string)$this->getDisplayURI();
$normalizer = new ArcanistRepositoryURINormalizer($type, $display);
$domain_map = self::getURINormalizerDomainMap();
$normalizer->setDomainMap($domain_map);
return $normalizer->getNormalizedURI();
}
public function getDisplayURI() {
return $this->getURIObject();
}
public function getEffectiveURI() {
return $this->getURIObject();
}
public function getURIEnvelope() {
$uri = $this->getEffectiveURI();
$command_engine = $this->newCommandEngine();
$is_http = $command_engine->isAnyHTTPProtocol();
// For SVN, we use `--username` and `--password` flags separately in the
// CommandEngine, so we don't need to add any credentials here.
$is_svn = $this->getRepository()->isSVN();
$credential_phid = $this->getCredentialPHID();
if ($is_http && !$is_svn && $credential_phid) {
$key = PassphrasePasswordKey::loadFromPHID(
$credential_phid,
PhabricatorUser::getOmnipotentUser());
$uri->setUser($key->getUsernameEnvelope()->openEnvelope());
$uri->setPass($key->getPasswordEnvelope()->openEnvelope());
}
return new PhutilOpaqueEnvelope((string)$uri);
}
private function getURIObject() {
// Users can provide Git/SCP-style URIs in the form "user@host:path".
// In the general case, these are not equivalent to any "ssh://..." form
// because the path is relative.
if ($this->isBuiltin()) {
$builtin_protocol = $this->getForcedProtocol();
$builtin_domain = $this->getForcedHost();
$raw_uri = "{$builtin_protocol}://{$builtin_domain}";
} else {
$raw_uri = $this->getURI();
}
$port = $this->getForcedPort();
$default_ports = array(
'ssh' => 22,
'http' => 80,
'https' => 443,
);
$uri = new PhutilURI($raw_uri);
// Make sure to remove any password from the URI before we do anything
// with it; this should always be provided by the associated credential.
$uri->setPass(null);
$protocol = $this->getForcedProtocol();
if ($protocol) {
$uri->setProtocol($protocol);
}
if ($port) {
$uri->setPort($port);
}
// Remove any explicitly set default ports.
$uri_port = $uri->getPort();
$uri_protocol = $uri->getProtocol();
$uri_default = idx($default_ports, $uri_protocol);
if ($uri_default && ($uri_default == $uri_port)) {
$uri->setPort(null);
}
$user = $this->getForcedUser();
if ($user) {
$uri->setUser($user);
}
$host = $this->getForcedHost();
if ($host) {
$uri->setDomain($host);
}
$path = $this->getForcedPath();
if ($path) {
$uri->setPath($path);
}
return $uri;
}
private function getForcedProtocol() {
$repository = $this->getRepository();
switch ($this->getBuiltinProtocol()) {
case self::BUILTIN_PROTOCOL_SSH:
if ($repository->isSVN()) {
return 'svn+ssh';
} else {
return 'ssh';
}
case self::BUILTIN_PROTOCOL_HTTP:
return 'http';
case self::BUILTIN_PROTOCOL_HTTPS:
return 'https';
default:
return null;
}
}
private function getForcedUser() {
switch ($this->getBuiltinProtocol()) {
case self::BUILTIN_PROTOCOL_SSH:
return AlmanacKeys::getClusterSSHUser();
default:
return null;
}
}
private function getForcedHost() {
$phabricator_uri = PhabricatorEnv::getURI('/');
$phabricator_uri = new PhutilURI($phabricator_uri);
$phabricator_host = $phabricator_uri->getDomain();
switch ($this->getBuiltinProtocol()) {
case self::BUILTIN_PROTOCOL_SSH:
$ssh_host = PhabricatorEnv::getEnvConfig('diffusion.ssh-host');
if ($ssh_host !== null) {
return $ssh_host;
}
return $phabricator_host;
case self::BUILTIN_PROTOCOL_HTTP:
case self::BUILTIN_PROTOCOL_HTTPS:
return $phabricator_host;
default:
return null;
}
}
private function getForcedPort() {
$protocol = $this->getBuiltinProtocol();
if ($protocol == self::BUILTIN_PROTOCOL_SSH) {
return PhabricatorEnv::getEnvConfig('diffusion.ssh-port');
}
// If Phabricator is running on a nonstandard port, use that as the default
// port for URIs with the same protocol.
$is_http = ($protocol == self::BUILTIN_PROTOCOL_HTTP);
$is_https = ($protocol == self::BUILTIN_PROTOCOL_HTTPS);
if ($is_http || $is_https) {
$uri = PhabricatorEnv::getURI('/');
$uri = new PhutilURI($uri);
$port = $uri->getPort();
if (!$port) {
return null;
}
$uri_protocol = $uri->getProtocol();
$use_port =
($is_http && ($uri_protocol == 'http')) ||
($is_https && ($uri_protocol == 'https'));
if (!$use_port) {
return null;
}
return $port;
}
return null;
}
private function getForcedPath() {
if (!$this->isBuiltin()) {
return null;
}
$repository = $this->getRepository();
$id = $repository->getID();
$callsign = $repository->getCallsign();
$short_name = $repository->getRepositorySlug();
$clone_name = $repository->getCloneName();
if ($repository->isGit()) {
$suffix = '.git';
} else if ($repository->isHg()) {
$suffix = '/';
} else {
$suffix = '';
$clone_name = '';
}
switch ($this->getBuiltinIdentifier()) {
case self::BUILTIN_IDENTIFIER_ID:
return "/diffusion/{$id}/{$clone_name}{$suffix}";
case self::BUILTIN_IDENTIFIER_SHORTNAME:
return "/source/{$short_name}{$suffix}";
case self::BUILTIN_IDENTIFIER_CALLSIGN:
return "/diffusion/{$callsign}/{$clone_name}{$suffix}";
default:
return null;
}
}
public function getViewURI() {
$id = $this->getID();
return $this->getRepository()->getPathURI("uri/view/{$id}/");
}
public function getEditURI() {
$id = $this->getID();
return $this->getRepository()->getPathURI("uri/edit/{$id}/");
}
public function getAvailableIOTypeOptions() {
$options = array(
self::IO_DEFAULT,
self::IO_NONE,
);
if ($this->isBuiltin()) {
$options[] = self::IO_READ;
$options[] = self::IO_READWRITE;
} else {
$options[] = self::IO_OBSERVE;
$options[] = self::IO_MIRROR;
}
$map = array();
$io_map = self::getIOTypeMap();
foreach ($options as $option) {
$spec = idx($io_map, $option, array());
$label = idx($spec, 'label', $option);
$short = idx($spec, 'short');
$name = pht('%s: %s', $label, $short);
$map[$option] = $name;
}
return $map;
}
public function getAvailableDisplayTypeOptions() {
$options = array(
self::DISPLAY_DEFAULT,
self::DISPLAY_ALWAYS,
self::DISPLAY_NEVER,
);
$map = array();
$display_map = self::getDisplayTypeMap();
foreach ($options as $option) {
$spec = idx($display_map, $option, array());
$label = idx($spec, 'label', $option);
$short = idx($spec, 'short');
$name = pht('%s: %s', $label, $short);
$map[$option] = $name;
}
return $map;
}
public static function getIOTypeMap() {
return array(
self::IO_DEFAULT => array(
'label' => pht('Default'),
'short' => pht('Use default behavior.'),
),
self::IO_OBSERVE => array(
'icon' => 'fa-download',
'color' => 'green',
'label' => pht('Observe'),
'note' => pht(
- 'Phabricator will observe changes to this URI and copy them.'),
+ 'Changes to this URI will be observed and pulled.'),
'short' => pht('Copy from a remote.'),
),
self::IO_MIRROR => array(
'icon' => 'fa-upload',
'color' => 'green',
'label' => pht('Mirror'),
'note' => pht(
- 'Phabricator will push a copy of any changes to this URI.'),
+ 'A copy of any changes will be pushed to this URI.'),
'short' => pht('Push a copy to a remote.'),
),
self::IO_NONE => array(
'icon' => 'fa-times',
'color' => 'grey',
'label' => pht('No I/O'),
'note' => pht(
- 'Phabricator will not push or pull any changes to this URI.'),
+ 'No changes will be pushed or pulled from this URI.'),
'short' => pht('Do not perform any I/O.'),
),
self::IO_READ => array(
'icon' => 'fa-folder',
'color' => 'blue',
'label' => pht('Read Only'),
'note' => pht(
- 'Phabricator will serve a read-only copy of the repository from '.
- 'this URI.'),
+ 'A read-only copy of the repository will be served from this URI.'),
'short' => pht('Serve repository in read-only mode.'),
),
self::IO_READWRITE => array(
'icon' => 'fa-folder-open',
'color' => 'blue',
'label' => pht('Read/Write'),
'note' => pht(
- 'Phabricator will serve a read/write copy of the repository from '.
- 'this URI.'),
+ 'A read/write copy of the repository will be served from this URI.'),
'short' => pht('Serve repository in read/write mode.'),
),
);
}
public static function getDisplayTypeMap() {
return array(
self::DISPLAY_DEFAULT => array(
'label' => pht('Default'),
'short' => pht('Use default behavior.'),
),
self::DISPLAY_ALWAYS => array(
'icon' => 'fa-eye',
'color' => 'green',
'label' => pht('Visible'),
'note' => pht('This URI will be shown to users as a clone URI.'),
'short' => pht('Show as a clone URI.'),
),
self::DISPLAY_NEVER => array(
'icon' => 'fa-eye-slash',
'color' => 'grey',
'label' => pht('Hidden'),
'note' => pht(
'This URI will be hidden from users.'),
'short' => pht('Do not show as a clone URI.'),
),
);
}
public function newCommandEngine() {
$repository = $this->getRepository();
return DiffusionCommandEngine::newCommandEngine($repository)
->setCredentialPHID($this->getCredentialPHID())
->setURI($this->getEffectiveURI());
}
public function getURIScore() {
$score = 0;
$io_points = array(
self::IO_READWRITE => 200,
self::IO_READ => 100,
);
$score += idx($io_points, $this->getEffectiveIOType(), 0);
$protocol_points = array(
self::BUILTIN_PROTOCOL_SSH => 30,
self::BUILTIN_PROTOCOL_HTTPS => 20,
self::BUILTIN_PROTOCOL_HTTP => 10,
);
$score += idx($protocol_points, $this->getBuiltinProtocol(), 0);
$identifier_points = array(
self::BUILTIN_IDENTIFIER_SHORTNAME => 3,
self::BUILTIN_IDENTIFIER_CALLSIGN => 2,
self::BUILTIN_IDENTIFIER_ID => 1,
);
$score += idx($identifier_points, $this->getBuiltinIdentifier(), 0);
return $score;
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new DiffusionURIEditor();
}
public function getApplicationTransactionTemplate() {
return new PhabricatorRepositoryURITransaction();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
case PhabricatorPolicyCapability::CAN_EDIT:
return PhabricatorPolicies::getMostOpenPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
$extended = array();
switch ($capability) {
case PhabricatorPolicyCapability::CAN_EDIT:
// To edit a repository URI, you must be able to edit the
// corresponding repository.
$extended[] = array($this->getRepository(), $capability);
break;
}
return $extended;
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('repositoryPHID')
->setType('phid')
->setDescription(pht('The associated repository PHID.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('uri')
->setType('map<string, string>')
->setDescription(pht('The raw and effective URI.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('io')
->setType('map<string, const>')
->setDescription(
pht('The raw, default, and effective I/O Type settings.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('display')
->setType('map<string, const>')
->setDescription(
pht('The raw, default, and effective Display Type settings.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('credentialPHID')
->setType('phid?')
->setDescription(
pht('The associated credential PHID, if one exists.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('disabled')
->setType('bool')
->setDescription(pht('True if the URI is disabled.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('builtin')
->setType('map<string, string>')
->setDescription(
pht('Information about builtin URIs.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('dateCreated')
->setType('int')
->setDescription(
pht('Epoch timestamp when the object was created.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('dateModified')
->setType('int')
->setDescription(
pht('Epoch timestamp when the object was last updated.')),
);
}
public function getFieldValuesForConduit() {
return array(
'repositoryPHID' => $this->getRepositoryPHID(),
'uri' => array(
'raw' => $this->getURI(),
'display' => (string)$this->getDisplayURI(),
'effective' => (string)$this->getEffectiveURI(),
'normalized' => (string)$this->getNormalizedURI(),
),
'io' => array(
'raw' => $this->getIOType(),
'default' => $this->getDefaultIOType(),
'effective' => $this->getEffectiveIOType(),
),
'display' => array(
'raw' => $this->getDisplayType(),
'default' => $this->getDefaultDisplayType(),
'effective' => $this->getEffectiveDisplayType(),
),
'credentialPHID' => $this->getCredentialPHID(),
'disabled' => (bool)$this->getIsDisabled(),
'builtin' => array(
'protocol' => $this->getBuiltinProtocol(),
'identifier' => $this->getBuiltinIdentifier(),
),
'dateCreated' => $this->getDateCreated(),
'dateModified' => $this->getDateModified(),
);
}
public function getConduitSearchAttachments() {
return array();
}
public static function getURINormalizerDomainMap() {
$domain_map = array();
// See T13435. If the domain for a repository URI is same as the install
// base URI, store it as a "<base-uri>" token instead of the actual domain
// so that the index does not fall out of date if the install moves.
$base_uri = PhabricatorEnv::getURI('/');
$base_uri = new PhutilURI($base_uri);
$base_domain = $base_uri->getDomain();
$domain_map['<base-uri>'] = $base_domain;
// Likewise, store a token for the "SSH Host" domain so it can be changed
// without requiring an index rebuild.
$ssh_host = PhabricatorEnv::getEnvConfig('diffusion.ssh-host');
if (strlen($ssh_host)) {
$domain_map['<ssh-host>'] = $ssh_host;
}
return $domain_map;
}
}
diff --git a/src/applications/repository/xaction/PhabricatorRepositoryVCSTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryVCSTransaction.php
index d5944d5425..84ee86dc76 100644
--- a/src/applications/repository/xaction/PhabricatorRepositoryVCSTransaction.php
+++ b/src/applications/repository/xaction/PhabricatorRepositoryVCSTransaction.php
@@ -1,66 +1,66 @@
<?php
final class PhabricatorRepositoryVCSTransaction
extends PhabricatorRepositoryTransactionType {
const TRANSACTIONTYPE = 'repo:vcs';
public function generateOldValue($object) {
return $object->getVersionControlSystem();
}
public function applyInternalEffects($object, $value) {
$object->setVersionControlSystem($value);
}
public function validateTransactions($object, array $xactions) {
$errors = array();
$vcs_map = PhabricatorRepositoryType::getAllRepositoryTypes();
$current_vcs = $object->getVersionControlSystem();
if (!$this->isNewObject()) {
foreach ($xactions as $xaction) {
if ($xaction->getNewValue() == $current_vcs) {
continue;
}
$errors[] = $this->newInvalidError(
pht(
'You can not change the version control system an existing '.
'repository uses. It can only be set when a repository is '.
'first created.'),
$xaction);
}
return $errors;
}
$value = $object->getVersionControlSystem();
foreach ($xactions as $xaction) {
$value = $xaction->getNewValue();
if (isset($vcs_map[$value])) {
continue;
}
$errors[] = $this->newInvalidError(
pht(
'Specified version control system must be a VCS '.
- 'recognized by Phabricator. Valid systems are: %s.',
+ 'recognized by this software. Valid systems are: %s.',
implode(', ', array_keys($vcs_map))),
$xaction);
}
if ($value === null) {
$errors[] = $this->newRequiredError(
pht(
'When creating a repository, you must specify a valid '.
'underlying version control system. Valid systems are: %s.',
implode(', ', array_keys($vcs_map))));
}
return $errors;
}
}
diff --git a/src/applications/search/query/PhabricatorNamedQueryConfigQuery.php b/src/applications/search/query/PhabricatorNamedQueryConfigQuery.php
index 6c4fad31a3..862f694fc8 100644
--- a/src/applications/search/query/PhabricatorNamedQueryConfigQuery.php
+++ b/src/applications/search/query/PhabricatorNamedQueryConfigQuery.php
@@ -1,64 +1,60 @@
<?php
final class PhabricatorNamedQueryConfigQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $engineClassNames;
private $scopePHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withScopePHIDs(array $scope_phids) {
$this->scopePHIDs = $scope_phids;
return $this;
}
public function withEngineClassNames(array $engine_class_names) {
$this->engineClassNames = $engine_class_names;
return $this;
}
public function newResultObject() {
return new PhabricatorNamedQueryConfig();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->engineClassNames !== null) {
$where[] = qsprintf(
$conn,
'engineClassName IN (%Ls)',
$this->engineClassNames);
}
if ($this->scopePHIDs !== null) {
$where[] = qsprintf(
$conn,
'scopePHID IN (%Ls)',
$this->scopePHIDs);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorSearchApplication';
}
}
diff --git a/src/applications/search/query/PhabricatorNamedQueryQuery.php b/src/applications/search/query/PhabricatorNamedQueryQuery.php
index 3decff5494..0ed92646e6 100644
--- a/src/applications/search/query/PhabricatorNamedQueryQuery.php
+++ b/src/applications/search/query/PhabricatorNamedQueryQuery.php
@@ -1,77 +1,73 @@
<?php
final class PhabricatorNamedQueryQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $engineClassNames;
private $userPHIDs;
private $queryKeys;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withUserPHIDs(array $user_phids) {
$this->userPHIDs = $user_phids;
return $this;
}
public function withEngineClassNames(array $engine_class_names) {
$this->engineClassNames = $engine_class_names;
return $this;
}
public function withQueryKeys(array $query_keys) {
$this->queryKeys = $query_keys;
return $this;
}
public function newResultObject() {
return new PhabricatorNamedQuery();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->engineClassNames !== null) {
$where[] = qsprintf(
$conn,
'engineClassName IN (%Ls)',
$this->engineClassNames);
}
if ($this->userPHIDs !== null) {
$where[] = qsprintf(
$conn,
'userPHID IN (%Ls)',
$this->userPHIDs);
}
if ($this->queryKeys !== null) {
$where[] = qsprintf(
$conn,
'queryKey IN (%Ls)',
$this->queryKeys);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorSearchApplication';
}
}
diff --git a/src/applications/search/query/PhabricatorProfileMenuItemConfigurationQuery.php b/src/applications/search/query/PhabricatorProfileMenuItemConfigurationQuery.php
index 16b5d793a4..9f26a81424 100644
--- a/src/applications/search/query/PhabricatorProfileMenuItemConfigurationQuery.php
+++ b/src/applications/search/query/PhabricatorProfileMenuItemConfigurationQuery.php
@@ -1,163 +1,159 @@
<?php
final class PhabricatorProfileMenuItemConfigurationQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $profilePHIDs;
private $customPHIDs;
private $includeGlobal;
private $affectedObjectPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withProfilePHIDs(array $phids) {
$this->profilePHIDs = $phids;
return $this;
}
public function withCustomPHIDs(array $phids, $include_global = false) {
$this->customPHIDs = $phids;
$this->includeGlobal = $include_global;
return $this;
}
public function withAffectedObjectPHIDs(array $phids) {
$this->affectedObjectPHIDs = $phids;
return $this;
}
public function newResultObject() {
return new PhabricatorProfileMenuItemConfiguration();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'config.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'config.phid IN (%Ls)',
$this->phids);
}
if ($this->profilePHIDs !== null) {
$where[] = qsprintf(
$conn,
'config.profilePHID IN (%Ls)',
$this->profilePHIDs);
}
if ($this->customPHIDs !== null) {
if ($this->customPHIDs && $this->includeGlobal) {
$where[] = qsprintf(
$conn,
'config.customPHID IN (%Ls) OR config.customPHID IS NULL',
$this->customPHIDs);
} else if ($this->customPHIDs) {
$where[] = qsprintf(
$conn,
'config.customPHID IN (%Ls)',
$this->customPHIDs);
} else {
$where[] = qsprintf(
$conn,
'config.customPHID IS NULL');
}
}
if ($this->affectedObjectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'affected.dst IN (%Ls)',
$this->affectedObjectPHIDs);
}
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->affectedObjectPHIDs !== null) {
$joins[] = qsprintf(
$conn,
'JOIN %T affected ON affected.src = config.phid
AND affected.type = %d',
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
PhabricatorProfileMenuItemAffectsObjectEdgeType::EDGECONST);
}
return $joins;
}
protected function willFilterPage(array $page) {
$items = PhabricatorProfileMenuItem::getAllMenuItems();
foreach ($page as $key => $item) {
$item_type = idx($items, $item->getMenuItemKey());
if (!$item_type) {
$this->didRejectResult($item);
unset($page[$key]);
continue;
}
$item_type = clone $item_type;
$item_type->setViewer($this->getViewer());
$item->attachMenuItem($item_type);
}
if (!$page) {
return array();
}
$profile_phids = mpull($page, 'getProfilePHID');
$profiles = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($profile_phids)
->execute();
$profiles = mpull($profiles, null, 'getPHID');
foreach ($page as $key => $item) {
$profile = idx($profiles, $item->getProfilePHID());
if (!$profile) {
$this->didRejectResult($item);
unset($page[$key]);
continue;
}
$item->attachProfileObject($profile);
}
return $page;
}
public function getQueryApplicationClass() {
return 'PhabricatorSearchApplication';
}
protected function getPrimaryTableAlias() {
return 'config';
}
}
diff --git a/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php b/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php
index 8f7f633e7e..a888bb3175 100644
--- a/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php
@@ -1,414 +1,415 @@
<?php
final class PhabricatorEmailAddressesSettingsPanel
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'email';
}
public function getPanelName() {
return pht('Email Addresses');
}
public function getPanelMenuIcon() {
return 'fa-at';
}
public function getPanelGroupKey() {
return PhabricatorSettingsEmailPanelGroup::PANELGROUPKEY;
}
public function isEditableByAdministrators() {
if ($this->getUser()->getIsMailingList()) {
return true;
}
return false;
}
public function processRequest(AphrontRequest $request) {
$user = $this->getUser();
$editable = PhabricatorEnv::getEnvConfig('account.editable');
$uri = new PhutilURI($request->getPath());
if ($editable) {
$new = $request->getStr('new');
if ($new) {
return $this->returnNewAddressResponse($request, $uri, $new);
}
$delete = $request->getInt('delete');
if ($delete) {
return $this->returnDeleteAddressResponse($request, $uri, $delete);
}
}
$verify = $request->getInt('verify');
if ($verify) {
return $this->returnVerifyAddressResponse($request, $uri, $verify);
}
$primary = $request->getInt('primary');
if ($primary) {
return $this->returnPrimaryAddressResponse($request, $uri, $primary);
}
$emails = id(new PhabricatorUserEmail())->loadAllWhere(
'userPHID = %s ORDER BY address',
$user->getPHID());
$rowc = array();
$rows = array();
foreach ($emails as $email) {
$button_verify = javelin_tag(
'a',
array(
'class' => 'button small button-grey',
'href' => $uri->alter('verify', $email->getID()),
'sigil' => 'workflow',
),
pht('Verify'));
$button_make_primary = javelin_tag(
'a',
array(
'class' => 'button small button-grey',
'href' => $uri->alter('primary', $email->getID()),
'sigil' => 'workflow',
),
pht('Make Primary'));
$button_remove = javelin_tag(
'a',
array(
'class' => 'button small button-grey',
'href' => $uri->alter('delete', $email->getID()),
'sigil' => 'workflow',
),
pht('Remove'));
$button_primary = phutil_tag(
'a',
array(
'class' => 'button small disabled',
),
pht('Primary'));
if (!$email->getIsVerified()) {
$action = $button_verify;
} else if ($email->getIsPrimary()) {
$action = $button_primary;
} else {
$action = $button_make_primary;
}
if ($email->getIsPrimary()) {
$remove = $button_primary;
$rowc[] = 'highlighted';
} else {
$remove = $button_remove;
$rowc[] = null;
}
$rows[] = array(
$email->getAddress(),
$action,
$remove,
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
pht('Email'),
pht('Status'),
pht('Remove'),
));
$table->setColumnClasses(
array(
'wide',
'action',
'action',
));
$table->setRowClasses($rowc);
$table->setColumnVisibility(
array(
true,
true,
$editable,
));
$buttons = array();
if ($editable) {
$buttons[] = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-plus')
->setText(pht('Add New Address'))
->setHref($uri->alter('new', 'true'))
->addSigil('workflow')
->setColor(PHUIButtonView::GREY);
}
return $this->newBox(pht('Email Addresses'), $table, $buttons);
}
private function returnNewAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
$new) {
$user = $this->getUser();
$viewer = $this->getViewer();
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
$request,
$this->getPanelURI());
$e_email = true;
$email = null;
$errors = array();
if ($request->isDialogFormPost()) {
$email = trim($request->getStr('email'));
if ($new == 'verify') {
// The user clicked "Done" from the "an email has been sent" dialog.
return id(new AphrontReloadResponse())->setURI($uri);
}
PhabricatorSystemActionEngine::willTakeAction(
array($viewer->getPHID()),
new PhabricatorSettingsAddEmailAction(),
1);
if (!strlen($email)) {
$e_email = pht('Required');
$errors[] = pht('Email is required.');
} else if (!PhabricatorUserEmail::isValidAddress($email)) {
$e_email = pht('Invalid');
$errors[] = PhabricatorUserEmail::describeValidAddresses();
} else if (!PhabricatorUserEmail::isAllowedAddress($email)) {
$e_email = pht('Disallowed');
$errors[] = PhabricatorUserEmail::describeAllowedAddresses();
}
if ($e_email === true) {
$application_email = id(new PhabricatorMetaMTAApplicationEmailQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withAddresses(array($email))
->executeOne();
if ($application_email) {
$e_email = pht('In Use');
$errors[] = $application_email->getInUseMessage();
}
}
if (!$errors) {
$object = id(new PhabricatorUserEmail())
->setAddress($email)
->setIsVerified(0);
// If an administrator is editing a mailing list, automatically verify
// the address.
if ($viewer->getPHID() != $user->getPHID()) {
if ($viewer->getIsAdmin()) {
$object->setIsVerified(1);
}
}
try {
id(new PhabricatorUserEditor())
->setActor($viewer)
->addEmail($user, $object);
if ($object->getIsVerified()) {
// If we autoverified the address, just reload the page.
return id(new AphrontReloadResponse())->setURI($uri);
}
$object->sendVerificationEmail($user);
$dialog = $this->newDialog()
->addHiddenInput('new', 'verify')
->setTitle(pht('Verification Email Sent'))
->appendChild(phutil_tag('p', array(), pht(
'A verification email has been sent. Click the link in the '.
'email to verify your address.')))
->setSubmitURI($uri)
->addSubmitButton(pht('Done'));
return id(new AphrontDialogResponse())->setDialog($dialog);
} catch (AphrontDuplicateKeyQueryException $ex) {
$e_email = pht('Duplicate');
$errors[] = pht('Another user already has this email.');
}
}
}
if ($errors) {
$errors = id(new PHUIInfoView())
->setErrors($errors);
}
$form = id(new PHUIFormLayoutView())
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Email'))
->setName('email')
->setValue($email)
->setCaption(PhabricatorUserEmail::describeAllowedAddresses())
->setError($e_email));
$dialog = $this->newDialog()
->addHiddenInput('new', 'true')
->setTitle(pht('New Address'))
->appendChild($errors)
->appendChild($form)
->addSubmitButton(pht('Save'))
->addCancelButton($uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function returnDeleteAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
$email_id) {
$user = $this->getUser();
$viewer = $this->getViewer();
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
$request,
$this->getPanelURI());
// NOTE: You can only delete your own email addresses, and you can not
// delete your primary address.
$email = id(new PhabricatorUserEmail())->loadOneWhere(
'id = %d AND userPHID = %s AND isPrimary = 0',
$email_id,
$user->getPHID());
if (!$email) {
return new Aphront404Response();
}
if ($request->isFormPost()) {
id(new PhabricatorUserEditor())
->setActor($viewer)
->removeEmail($user, $email);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$address = $email->getAddress();
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->addHiddenInput('delete', $email_id)
->setTitle(pht("Really delete address '%s'?", $address))
->appendParagraph(
pht(
'Are you sure you want to delete this address? You will no '.
'longer be able to use it to login.'))
->appendParagraph(
pht(
'Note: Removing an email address from your account will invalidate '.
'any outstanding password reset links.'))
->addSubmitButton(pht('Delete'))
->addCancelButton($uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function returnVerifyAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
$email_id) {
$user = $this->getUser();
$viewer = $this->getViewer();
// NOTE: You can only send more email for your unverified addresses.
$email = id(new PhabricatorUserEmail())->loadOneWhere(
'id = %d AND userPHID = %s AND isVerified = 0',
$email_id,
$user->getPHID());
if (!$email) {
return new Aphront404Response();
}
if ($request->isFormPost()) {
$email->sendVerificationEmail($user);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$address = $email->getAddress();
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->addHiddenInput('verify', $email_id)
->setTitle(pht('Send Another Verification Email?'))
->appendChild(phutil_tag('p', array(), pht(
'Send another copy of the verification email to %s?',
$address)))
->addSubmitButton(pht('Send Email'))
->addCancelButton($uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function returnPrimaryAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
$email_id) {
$user = $this->getUser();
$viewer = $this->getViewer();
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
$request,
$this->getPanelURI());
// NOTE: You can only make your own verified addresses primary.
$email = id(new PhabricatorUserEmail())->loadOneWhere(
'id = %d AND userPHID = %s AND isVerified = 1 AND isPrimary = 0',
$email_id,
$user->getPHID());
if (!$email) {
return new Aphront404Response();
}
if ($request->isFormPost()) {
id(new PhabricatorUserEditor())
->setActor($viewer)
->changePrimaryEmail($user, $email);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$address = $email->getAddress();
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->addHiddenInput('primary', $email_id)
->setTitle(pht('Change primary email address?'))
->appendParagraph(
pht(
- 'If you change your primary address, Phabricator will send all '.
+ 'If you change your primary address, %s will send all '.
'email to %s.',
+ PlatformSymbols::getPlatformServerName(),
$address))
->appendParagraph(
pht(
'Note: Changing your primary email address will invalidate any '.
'outstanding password reset links.'))
->addSubmitButton(pht('Change Primary Address'))
->addCancelButton($uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}
diff --git a/src/applications/settings/panel/PhabricatorExternalEditorSettingsPanel.php b/src/applications/settings/panel/PhabricatorExternalEditorSettingsPanel.php
index c331d3d762..3a49b6c3c3 100644
--- a/src/applications/settings/panel/PhabricatorExternalEditorSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorExternalEditorSettingsPanel.php
@@ -1,171 +1,171 @@
<?php
final class PhabricatorExternalEditorSettingsPanel
extends PhabricatorEditEngineSettingsPanel {
const PANELKEY = 'editor';
public function getPanelName() {
return pht('External Editor');
}
public function getPanelMenuIcon() {
return 'fa-i-cursor';
}
public function getPanelGroupKey() {
return PhabricatorSettingsApplicationsPanelGroup::PANELGROUPKEY;
}
public function isTemplatePanel() {
return true;
}
public function newSettingsPanelEditFormHeadContent(
PhabricatorEditEnginePageState $state) {
// The "Editor" setting stored in the database may be invalidated by
// configuration or software changes. If a saved URI becomes invalid
// (for example, its protocol is removed from the list of allowed
// protocols), it will stop working.
// If the stored value has a problem like this, show a static error
// message without requiring the user to save changes.
if ($state->getIsSubmit()) {
return null;
}
$viewer = $this->getViewer();
$pattern = $viewer->getUserSetting(PhabricatorEditorSetting::SETTINGKEY);
if (!strlen($pattern)) {
return null;
}
$caught = null;
try {
id(new PhabricatorEditorURIEngine())
->setPattern($pattern)
->validatePattern();
} catch (PhabricatorEditorURIParserException $ex) {
$caught = $ex;
}
if (!$caught) {
return null;
}
return id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->appendChild($caught->getMessage());
}
public function newSettingsPanelEditFormTailContent(
PhabricatorEditEnginePageState $state) {
$viewer = $this->getViewer();
$variables = PhabricatorEditorURIEngine::getVariableDefinitions();
$rows = array();
foreach ($variables as $key => $variable) {
$rows[] = array(
phutil_tag('tt', array(), '%'.$key),
$variable['name'],
$variable['example'],
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Variable'),
pht('Replaced With'),
pht('Example'),
))
->setColumnClasses(
array(
'center',
'pri',
'wide',
));
$variables_box = id(new PHUIObjectBoxView())
->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->setHeaderText(pht('External Editor URI Variables'))
->setTable($table);
$label_map = array(
'http' => pht('Hypertext Transfer Protocol'),
'https' => pht('Hypertext Transfer Protocol over SSL'),
'txmt' => pht('TextMate'),
'mvim' => pht('MacVim'),
'subl' => pht('Sublime Text'),
'vim' => pht('Vim'),
'emacs' => pht('Emacs'),
'vscode' => pht('Visual Studio Code'),
'editor' => pht('Generic Editor'),
'idea' => pht('IntelliJ IDEA'),
);
$default_label = phutil_tag('em', array(), pht('Supported Protocol'));
$config_key = 'uri.allowed-editor-protocols';
$protocols = PhabricatorEnv::getEnvConfig($config_key);
$protocols = array_keys($protocols);
sort($protocols);
$protocol_rows = array();
foreach ($protocols as $protocol) {
$label = idx($label_map, $protocol, $default_label);
$protocol_rows[] = array(
$protocol.'://',
$label,
);
}
$protocol_table = id(new AphrontTableView($protocol_rows))
->setNoDataString(
pht(
- 'Phabricator is not configured to allow any editor protocols.'))
+ 'No allowed editor protocols are configured.'))
->setHeaders(
array(
pht('Protocol'),
pht('Description'),
))
->setColumnClasses(
array(
'pri',
'wide',
));
$is_admin = $viewer->getIsAdmin();
$configure_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('View Configuration'))
->setIcon('fa-sliders')
->setHref(
urisprintf(
'/config/edit/%s/',
$config_key))
->setDisabled(!$is_admin);
$protocol_header = id(new PHUIHeaderView())
->setHeader(pht('Supported Editor Protocols'))
->addActionLink($configure_button);
$protocols_box = id(new PHUIObjectBoxView())
->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->setHeader($protocol_header)
->setTable($protocol_table);
return array(
$variables_box,
$protocols_box,
);
}
}
diff --git a/src/applications/settings/panel/PhabricatorNotificationsSettingsPanel.php b/src/applications/settings/panel/PhabricatorNotificationsSettingsPanel.php
index d0165dc3f1..ae5679d9dd 100644
--- a/src/applications/settings/panel/PhabricatorNotificationsSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorNotificationsSettingsPanel.php
@@ -1,183 +1,184 @@
<?php
final class PhabricatorNotificationsSettingsPanel
extends PhabricatorSettingsPanel {
public function isEnabled() {
$servers = PhabricatorNotificationServerRef::getEnabledAdminServers();
if (!$servers) {
return false;
}
return PhabricatorApplication::isClassInstalled(
'PhabricatorNotificationsApplication');
}
public function getPanelKey() {
return 'notifications';
}
public function getPanelName() {
return pht('Notifications');
}
public function getPanelMenuIcon() {
return 'fa-bell-o';
}
public function getPanelGroupKey() {
return PhabricatorSettingsApplicationsPanelGroup::PANELGROUPKEY;
}
public function processRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$preferences = $this->getPreferences();
$notifications_key = PhabricatorNotificationsSetting::SETTINGKEY;
$notifications_value = $preferences->getSettingValue($notifications_key);
if ($request->isFormPost()) {
$this->writeSetting(
$preferences,
$notifications_key,
$request->getInt($notifications_key));
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI('?saved=true'));
}
$title = pht('Notifications');
$control_id = celerity_generate_unique_node_id();
$status_id = celerity_generate_unique_node_id();
$browser_status_id = celerity_generate_unique_node_id();
$cancel_ask = pht(
'The dialog asking for permission to send desktop notifications was '.
'closed without granting permission. Only application notifications '.
'will be sent.');
$accept_ask = pht(
'Click "Save Preference" to persist these changes.');
$reject_ask = pht(
'Permission for desktop notifications was denied. Only application '.
'notifications will be sent.');
$no_support = pht(
'This web browser does not support desktop notifications. Only '.
'application notifications will be sent for this browser regardless of '.
'this preference.');
$default_status = phutil_tag(
'span',
array(),
array(
- pht('This browser has not yet granted permission to send desktop '.
- 'notifications for this Phabricator instance.'),
+ pht(
+ 'Your browser has not yet granted this server permission to send '.
+ 'desktop notifications.'),
phutil_tag('br'),
phutil_tag('br'),
javelin_tag(
'button',
array(
'sigil' => 'desktop-notifications-permission-button',
'class' => 'green',
),
pht('Grant Permission')),
));
$granted_status = phutil_tag(
'span',
array(),
- pht('This browser has been granted permission to send desktop '.
- 'notifications for this Phabricator instance.'));
+ pht('Your browser has granted this server permission to send desktop '.
+ 'notifications.'));
$denied_status = phutil_tag(
'span',
array(),
pht('This browser has denied permission to send desktop notifications '.
- 'for this Phabricator instance. Consult your browser settings / '.
+ 'to this server. Consult your browser settings / '.
'documentation to figure out how to clear this setting, do so, '.
'and then re-visit this page to grant permission.'));
$message_id = celerity_generate_unique_node_id();
$message_container = phutil_tag(
'span',
array(
'id' => $message_id,
));
$saved_box = null;
if ($request->getBool('saved')) {
$saved_box = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->appendChild(pht('Changes saved.'));
}
$status_box = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->setID($status_id)
->setIsHidden(true)
->appendChild($message_container);
$status_box = id(new PHUIBoxView())
->addClass('mll mlr')
->appendChild($status_box);
$control_config = array(
'controlID' => $control_id,
'statusID' => $status_id,
'messageID' => $message_id,
'browserStatusID' => $browser_status_id,
'defaultMode' => 0,
'desktop' => 1,
'desktopOnly' => 2,
'cancelAsk' => $cancel_ask,
'grantedAsk' => $accept_ask,
'deniedAsk' => $reject_ask,
'defaultStatus' => $default_status,
'deniedStatus' => $denied_status,
'grantedStatus' => $granted_status,
'noSupport' => $no_support,
);
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild(
id(new AphrontFormSelectControl())
->setLabel($title)
->setControlID($control_id)
->setName($notifications_key)
->setValue($notifications_value)
->setOptions(PhabricatorNotificationsSetting::getOptionsMap())
->setCaption(
pht(
- 'Phabricator can send real-time notifications to your web browser '.
+ 'This server can send real-time notifications to your web browser '.
'or to your desktop. Select where you want to receive these '.
'real-time updates.'))
->initBehavior(
'desktop-notifications-control',
$control_config))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save Preference')));
$button = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-send-o')
->setWorkflow(true)
->setText(pht('Send Test Notification'))
->setHref('/notification/test/')
->setColor(PHUIButtonView::GREY);
$form_content = array($saved_box, $status_box, $form);
$form_box = $this->newBox(
pht('Notifications'), $form_content, array($button));
$browser_status_box = id(new PHUIInfoView())
->setID($browser_status_id)
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->setIsHidden(true)
->appendChild($default_status);
return array(
$form_box,
$browser_status_box,
);
}
}
diff --git a/src/applications/settings/setting/PhabricatorAccessibilitySetting.php b/src/applications/settings/setting/PhabricatorAccessibilitySetting.php
index 774bfcd895..226feeae5f 100644
--- a/src/applications/settings/setting/PhabricatorAccessibilitySetting.php
+++ b/src/applications/settings/setting/PhabricatorAccessibilitySetting.php
@@ -1,47 +1,47 @@
<?php
final class PhabricatorAccessibilitySetting
extends PhabricatorSelectSetting {
const SETTINGKEY = 'resource-postprocessor';
public function getSettingName() {
return pht('Accessibility');
}
public function getSettingPanelKey() {
return PhabricatorDisplayPreferencesSettingsPanel::PANELKEY;
}
protected function getSettingOrder() {
return 100;
}
protected function getControlInstructions() {
return pht(
- 'If you have difficulty reading the Phabricator UI, this setting '.
- 'may make Phabricator more accessible.');
+ 'If you have difficulty reading the UI, this setting '.
+ 'may help.');
}
public function getSettingDefaultValue() {
return CelerityDefaultPostprocessor::POSTPROCESSOR_KEY;
}
protected function getSelectOptions() {
$postprocessor_map = CelerityPostprocessor::getAllPostprocessors();
$postprocessor_map = mpull($postprocessor_map, 'getPostprocessorName');
asort($postprocessor_map);
$postprocessor_order = array(
CelerityDefaultPostprocessor::POSTPROCESSOR_KEY,
);
$postprocessor_map = array_select_keys(
$postprocessor_map,
$postprocessor_order) + $postprocessor_map;
return $postprocessor_map;
}
}
diff --git a/src/applications/settings/setting/PhabricatorDarkConsoleSetting.php b/src/applications/settings/setting/PhabricatorDarkConsoleSetting.php
index 28ee90788e..7851bbdb76 100644
--- a/src/applications/settings/setting/PhabricatorDarkConsoleSetting.php
+++ b/src/applications/settings/setting/PhabricatorDarkConsoleSetting.php
@@ -1,58 +1,58 @@
<?php
final class PhabricatorDarkConsoleSetting
extends PhabricatorSelectSetting {
const SETTINGKEY = 'dark_console';
const VALUE_DARKCONSOLE_DISABLED = '0';
const VALUE_DARKCONSOLE_ENABLED = '1';
public function getSettingName() {
return pht('DarkConsole');
}
public function getSettingPanelKey() {
return PhabricatorDeveloperPreferencesSettingsPanel::PANELKEY;
}
protected function getSettingOrder() {
return 100;
}
protected function isEnabledForViewer(PhabricatorUser $viewer) {
return PhabricatorEnv::getEnvConfig('darkconsole.enabled');
}
protected function getControlInstructions() {
return pht(
'DarkConsole is a debugging console for developing and troubleshooting '.
- 'Phabricator applications. After enabling DarkConsole, press the '.
+ 'applications. After enabling DarkConsole, press the '.
'{nav `} key on your keyboard to toggle it on or off.');
}
public function getSettingDefaultValue() {
return self::VALUE_DARKCONSOLE_DISABLED;
}
protected function getSelectOptions() {
return array(
self::VALUE_DARKCONSOLE_DISABLED => pht('Disable DarkConsole'),
self::VALUE_DARKCONSOLE_ENABLED => pht('Enable DarkConsole'),
);
}
public function expandSettingTransaction($object, $xaction) {
// If the user has hidden the DarkConsole UI, forget their setting when
// they enable or disable it.
return array(
$xaction,
$this->newSettingTransaction(
$object,
PhabricatorDarkConsoleVisibleSetting::SETTINGKEY,
1),
);
}
}
diff --git a/src/applications/settings/setting/PhabricatorEditorSetting.php b/src/applications/settings/setting/PhabricatorEditorSetting.php
index a0f1b43c95..bafc2ae518 100644
--- a/src/applications/settings/setting/PhabricatorEditorSetting.php
+++ b/src/applications/settings/setting/PhabricatorEditorSetting.php
@@ -1,54 +1,54 @@
<?php
final class PhabricatorEditorSetting
extends PhabricatorStringSetting {
const SETTINGKEY = 'editor';
public function getSettingName() {
return pht('Editor Link');
}
public function getSettingPanelKey() {
return PhabricatorExternalEditorSettingsPanel::PANELKEY;
}
protected function getSettingOrder() {
return 300;
}
protected function getControlInstructions() {
return pht(
"Many text editors can be configured as URI handlers for special ".
"protocols like `editor://`. If you have installed and configured ".
- "such an editor, Phabricator can generate links that you can click ".
- "to open files locally.".
+ "such an editor, some applications can generate links that you can ".
+ "click to open files locally.".
"\n\n".
"Provide a URI pattern for building external editor URIs in your ".
"environment. For example, if you use TextMate on macOS, the pattern ".
"for your machine may look something like this:".
"\n\n".
"```name=\"Example: TextMate on macOS\"\n".
"%s\n".
"```\n".
"\n\n".
"For complete instructions on editor configuration, ".
"see **[[ %s | %s ]]**.".
"\n\n".
"See the tables below for a list of supported variables and protocols.",
'txmt://open/?url=file:///Users/alincoln/editor_links/%n/%f&line=%l',
PhabricatorEnv::getDoclink('User Guide: Configuring an External Editor'),
pht('User Guide: Configuring an External Editor'));
}
public function validateTransactionValue($value) {
- if (!strlen($value)) {
+ if (!phutil_nonempty_string($value)) {
return;
}
id(new PhabricatorEditorURIEngine())
->setPattern($value)
->validatePattern();
}
}
diff --git a/src/applications/settings/setting/PhabricatorEmailFormatSetting.php b/src/applications/settings/setting/PhabricatorEmailFormatSetting.php
index 333d85c6f4..a8a0b07d94 100644
--- a/src/applications/settings/setting/PhabricatorEmailFormatSetting.php
+++ b/src/applications/settings/setting/PhabricatorEmailFormatSetting.php
@@ -1,40 +1,40 @@
<?php
final class PhabricatorEmailFormatSetting
extends PhabricatorSelectSetting {
const SETTINGKEY = 'html-emails';
const VALUE_HTML_EMAIL = 'html';
const VALUE_TEXT_EMAIL = 'text';
public function getSettingName() {
return pht('HTML Email');
}
public function getSettingPanelKey() {
return PhabricatorEmailFormatSettingsPanel::PANELKEY;
}
protected function getSettingOrder() {
return 100;
}
protected function getControlInstructions() {
return pht(
- 'You can opt to receive plain text email from Phabricator instead '.
- 'of HTML email. Plain text email works better with some clients.');
+ 'You can opt to receive plain text email instead of HTML email. '.
+ 'Plain text email works better with some clients.');
}
public function getSettingDefaultValue() {
return self::VALUE_HTML_EMAIL;
}
protected function getSelectOptions() {
return array(
self::VALUE_HTML_EMAIL => pht('Send HTML Email'),
self::VALUE_TEXT_EMAIL => pht('Send Plain Text Email'),
);
}
}
diff --git a/src/applications/settings/setting/PhabricatorEmailNotificationsSetting.php b/src/applications/settings/setting/PhabricatorEmailNotificationsSetting.php
index dfbedb3a12..2c63e01d72 100644
--- a/src/applications/settings/setting/PhabricatorEmailNotificationsSetting.php
+++ b/src/applications/settings/setting/PhabricatorEmailNotificationsSetting.php
@@ -1,44 +1,44 @@
<?php
final class PhabricatorEmailNotificationsSetting
extends PhabricatorSelectSetting {
const SETTINGKEY = 'no-mail';
const VALUE_SEND_MAIL = '0';
const VALUE_NO_MAIL = '1';
public function getSettingName() {
return pht('Email Notifications');
}
public function getSettingPanelKey() {
return PhabricatorEmailDeliverySettingsPanel::PANELKEY;
}
protected function getSettingOrder() {
return 100;
}
protected function getControlInstructions() {
return pht(
- 'If you disable **Email Notifications**, Phabricator will never '.
+ 'If you disable **Email Notifications**, this server will never '.
'send email to notify you about events. This preference overrides '.
'all your other settings.'.
"\n\n".
"//You will still receive some administrative email, like password ".
"reset email.//");
}
public function getSettingDefaultValue() {
return self::VALUE_SEND_MAIL;
}
protected function getSelectOptions() {
return array(
self::VALUE_SEND_MAIL => pht('Enable Email Notifications'),
self::VALUE_NO_MAIL => pht('Disable Email Notifications'),
);
}
}
diff --git a/src/applications/settings/setting/PhabricatorEmailSelfActionsSetting.php b/src/applications/settings/setting/PhabricatorEmailSelfActionsSetting.php
index f910c2b039..1c64cbdf32 100644
--- a/src/applications/settings/setting/PhabricatorEmailSelfActionsSetting.php
+++ b/src/applications/settings/setting/PhabricatorEmailSelfActionsSetting.php
@@ -1,40 +1,40 @@
<?php
final class PhabricatorEmailSelfActionsSetting
extends PhabricatorSelectSetting {
const SETTINGKEY = 'self-mail';
const VALUE_SEND_SELF = '0';
const VALUE_NO_SELF = '1';
public function getSettingName() {
return pht('Self Actions');
}
public function getSettingPanelKey() {
return PhabricatorEmailDeliverySettingsPanel::PANELKEY;
}
protected function getSettingOrder() {
return 200;
}
protected function getControlInstructions() {
return pht(
- 'If you disable **Self Actions**, Phabricator will not notify '.
+ 'If you disable **Self Actions**, this server will not notify '.
'you about actions you take.');
}
public function getSettingDefaultValue() {
return self::VALUE_SEND_SELF;
}
protected function getSelectOptions() {
return array(
self::VALUE_SEND_SELF => pht('Enable Self Action Mail'),
self::VALUE_NO_SELF => pht('Disable Self Action Mail'),
);
}
}
diff --git a/src/applications/settings/setting/PhabricatorEmailStampsSetting.php b/src/applications/settings/setting/PhabricatorEmailStampsSetting.php
index 39403f40a0..52497f3c42 100644
--- a/src/applications/settings/setting/PhabricatorEmailStampsSetting.php
+++ b/src/applications/settings/setting/PhabricatorEmailStampsSetting.php
@@ -1,47 +1,47 @@
<?php
final class PhabricatorEmailStampsSetting
extends PhabricatorSelectSetting {
const SETTINGKEY = 'stamps';
const VALUE_BODY_STAMPS = 'body';
const VALUE_HEADER_STAMPS = 'header';
public function getSettingName() {
return pht('Send Stamps');
}
public function getSettingPanelKey() {
return PhabricatorEmailFormatSettingsPanel::PANELKEY;
}
protected function getSettingOrder() {
return 400;
}
protected function getControlInstructions() {
return pht(<<<EOREMARKUP
-Phabricator stamps mail with labels like `actor(alice)` which can be used to
+Outgoing mail is stamped with labels like `actor(alice)` which can be used to
write client mail rules to organize mail. By default, these stamps are sent
in an `X-Phabricator-Stamps` header.
If you use a client which can not use headers to route mail (like Gmail),
you can also include the stamps in the message body so mail rules based on
body content can route messages.
EOREMARKUP
);
}
public function getSettingDefaultValue() {
return self::VALUE_HEADER_STAMPS;
}
protected function getSelectOptions() {
return array(
self::VALUE_HEADER_STAMPS => pht('Mail Headers'),
self::VALUE_BODY_STAMPS => pht('Mail Headers and Body'),
);
}
}
diff --git a/src/applications/settings/setting/PhabricatorOlderInlinesSetting.php b/src/applications/settings/setting/PhabricatorOlderInlinesSetting.php
index ac1f57f457..e7f7ab3dd1 100644
--- a/src/applications/settings/setting/PhabricatorOlderInlinesSetting.php
+++ b/src/applications/settings/setting/PhabricatorOlderInlinesSetting.php
@@ -1,43 +1,43 @@
<?php
final class PhabricatorOlderInlinesSetting
extends PhabricatorSelectSetting {
const SETTINGKEY = 'diff-ghosts';
const VALUE_GHOST_INLINES_ENABLED = 'default';
const VALUE_GHOST_INLINES_DISABLED = 'disabled';
public function getSettingName() {
return pht('Show Older Inlines');
}
protected function getSettingOrder() {
return 200;
}
public function getSettingPanelKey() {
return PhabricatorDiffPreferencesSettingsPanel::PANELKEY;
}
protected function getControlInstructions() {
return pht(
- 'When a revision is updated, Phabricator attempts to bring inline '.
+ 'When a revision is updated, this software attempts to bring inline '.
'comments on the older version forward to the new changes. You can '.
'disable this behavior if you prefer comments stay anchored in one '.
'place.');
}
public function getSettingDefaultValue() {
return self::VALUE_GHOST_INLINES_ENABLED;
}
protected function getSelectOptions() {
return array(
self::VALUE_GHOST_INLINES_ENABLED => pht('Enabled'),
self::VALUE_GHOST_INLINES_DISABLED => pht('Disabled'),
);
}
}
diff --git a/src/applications/settings/setting/PhabricatorSelectSetting.php b/src/applications/settings/setting/PhabricatorSelectSetting.php
index bccf450454..fb3cb2135f 100644
--- a/src/applications/settings/setting/PhabricatorSelectSetting.php
+++ b/src/applications/settings/setting/PhabricatorSelectSetting.php
@@ -1,76 +1,79 @@
<?php
abstract class PhabricatorSelectSetting
extends PhabricatorSetting {
abstract protected function getSelectOptions();
final protected function newCustomEditField($object) {
$setting_key = $this->getSettingKey();
$default_value = $object->getDefaultValue($setting_key);
$options = $this->getSelectOptions();
if (isset($options[$default_value])) {
$default_label = pht('Default (%s)', $options[$default_value]);
} else {
$default_label = pht('Default (Unknown, "%s")', $default_value);
}
if (empty($options[''])) {
$options = array(
'' => $default_label,
) + $options;
}
return $this->newEditField($object, new PhabricatorSelectEditField())
->setOptions($options);
}
public function assertValidValue($value) {
// This is a slightly stricter check than the transaction check. It's
// OK for empty string to go through transactions because it gets converted
// to null later, but we shouldn't be reading the empty string from
// storage.
if ($value === null) {
return;
}
if (!strlen($value)) {
throw new Exception(
pht(
'Empty string is not a valid setting for "%s".',
$this->getSettingName()));
}
$this->validateTransactionValue($value);
}
final public function validateTransactionValue($value) {
+ $value = phutil_string_cast($value);
if (!strlen($value)) {
return;
}
$options = $this->getSelectOptions();
if (!isset($options[$value])) {
throw new Exception(
pht(
'Value "%s" is not valid for setting "%s": valid values are %s.',
$value,
$this->getSettingName(),
implode(', ', array_keys($options))));
}
return;
}
public function getTransactionNewValue($value) {
+ $value = phutil_string_cast($value);
+
if (!strlen($value)) {
return null;
}
- return (string)$value;
+ return $value;
}
}
diff --git a/src/applications/settings/setting/PhabricatorTitleGlyphsSetting.php b/src/applications/settings/setting/PhabricatorTitleGlyphsSetting.php
index 04efbcb0f0..e5947c4875 100644
--- a/src/applications/settings/setting/PhabricatorTitleGlyphsSetting.php
+++ b/src/applications/settings/setting/PhabricatorTitleGlyphsSetting.php
@@ -1,41 +1,42 @@
<?php
final class PhabricatorTitleGlyphsSetting
extends PhabricatorSelectSetting {
const SETTINGKEY = 'titles';
const VALUE_TITLE_GLYPHS = 'glyph';
const VALUE_TITLE_TEXT = 'text';
public function getSettingName() {
return pht('Page Titles');
}
public function getSettingPanelKey() {
return PhabricatorDisplayPreferencesSettingsPanel::PANELKEY;
}
protected function getSettingOrder() {
return 200;
}
protected function getControlInstructions() {
return pht(
- 'Phabricator uses unicode glyphs in page titles to provide a compact '.
- 'representation of the current application. You can substitute plain '.
- 'text instead if these glyphs do not display on your system.');
+ 'Some applications use unicode glyphs in page titles to provide a '.
+ 'compact representation of the current application. You can '.
+ 'substitute plain text instead if these glyphs do not display on '.
+ 'your system.');
}
public function getSettingDefaultValue() {
return self::VALUE_TITLE_GLYPHS;
}
protected function getSelectOptions() {
return array(
self::VALUE_TITLE_GLYPHS => pht("Use Unicode Glyphs: \xE2\x9A\x99"),
self::VALUE_TITLE_TEXT => pht('Use Plain Text: [Differential]'),
);
}
}
diff --git a/src/applications/settings/setting/PhabricatorTranslationSetting.php b/src/applications/settings/setting/PhabricatorTranslationSetting.php
index 09f77c2ba4..42657e396c 100644
--- a/src/applications/settings/setting/PhabricatorTranslationSetting.php
+++ b/src/applications/settings/setting/PhabricatorTranslationSetting.php
@@ -1,119 +1,119 @@
<?php
final class PhabricatorTranslationSetting
extends PhabricatorOptionGroupSetting {
const SETTINGKEY = 'translation';
public function getSettingName() {
return pht('Translation');
}
public function getSettingPanelKey() {
return PhabricatorLanguageSettingsPanel::PANELKEY;
}
protected function getSettingOrder() {
return 100;
}
public function getSettingDefaultValue() {
return 'en_US';
}
protected function getControlInstructions() {
return pht(
- 'Choose which language you would like the Phabricator UI to use.');
+ 'Choose which language you would like the UI to use.');
}
public function assertValidValue($value) {
$locales = PhutilLocale::loadAllLocales();
return isset($locales[$value]);
}
protected function getSelectOptionGroups() {
$locales = PhutilLocale::loadAllLocales();
$group_labels = array(
'normal' => pht('Translations'),
'limited' => pht('Limited Translations'),
'silly' => pht('Silly Translations'),
'test' => pht('Developer/Test Translations'),
);
$groups = array_fill_keys(array_keys($group_labels), array());
$translations = array();
foreach ($locales as $locale) {
$code = $locale->getLocaleCode();
// Get the locale's localized name if it's available. For example,
// "Deutsch" instead of "German". This helps users who do not speak the
// current language to find the correct setting.
$raw_scope = PhabricatorEnv::beginScopedLocale($code);
$name = $locale->getLocaleName();
unset($raw_scope);
if ($locale->isSillyLocale()) {
$groups['silly'][$code] = $name;
continue;
}
if ($locale->isTestLocale()) {
$groups['test'][$code] = $name;
continue;
}
$strings = PhutilTranslation::getTranslationMapForLocale($code);
$size = count($strings);
// If a translation is English, assume it can fall back to the default
// strings and don't caveat its completeness.
$is_english = (substr($code, 0, 3) == 'en_');
// Arbitrarily pick some number of available strings to promote a
// translation out of the "limited" group. The major goal is just to
// keep locales with very few strings out of the main group, so users
// aren't surprised if a locale has no upstream translations available.
if ($size > 512 || $is_english) {
$type = 'normal';
} else {
$type = 'limited';
}
$groups[$type][$code] = $name;
}
// Omit silly locales on serious business installs.
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
if ($is_serious) {
unset($groups['silly']);
}
// Omit limited and test translations if Phabricator is not in developer
// mode.
$is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode');
if (!$is_dev) {
unset($groups['limited']);
unset($groups['test']);
}
$results = array();
foreach ($groups as $key => $group) {
$label = $group_labels[$key];
if (!$group) {
continue;
}
asort($group);
$results[] = array(
'label' => $label,
'options' => $group,
);
}
return $results;
}
}
diff --git a/src/applications/settings/setting/PhabricatorUnifiedDiffsSetting.php b/src/applications/settings/setting/PhabricatorUnifiedDiffsSetting.php
index 5a94397418..50ac8b3042 100644
--- a/src/applications/settings/setting/PhabricatorUnifiedDiffsSetting.php
+++ b/src/applications/settings/setting/PhabricatorUnifiedDiffsSetting.php
@@ -1,43 +1,43 @@
<?php
final class PhabricatorUnifiedDiffsSetting
extends PhabricatorSelectSetting {
const SETTINGKEY = 'diff-unified';
const VALUE_ON_SMALL_SCREENS = 'default';
const VALUE_ALWAYS_UNIFIED = 'unified';
public function getSettingName() {
return pht('Show Unified Diffs');
}
protected function getSettingOrder() {
return 100;
}
public function getSettingPanelKey() {
return PhabricatorDiffPreferencesSettingsPanel::PANELKEY;
}
protected function getControlInstructions() {
return pht(
- 'Phabricator normally shows diffs in a side-by-side layout on large '.
- 'screens and automatically switches to a unified view on small '.
+ 'Diffs are normally shown in a side-by-side layout on large '.
+ 'screens and automatically switched to a unified view on small '.
'screens (like mobile phones). If you prefer unified diffs even on '.
'large screens, you can select them for use on all displays.');
}
public function getSettingDefaultValue() {
return self::VALUE_ON_SMALL_SCREENS;
}
protected function getSelectOptions() {
return array(
self::VALUE_ON_SMALL_SCREENS => pht('On Small Screens'),
self::VALUE_ALWAYS_UNIFIED => pht('Always'),
);
}
}
diff --git a/src/applications/settings/storage/PhabricatorUserPreferences.php b/src/applications/settings/storage/PhabricatorUserPreferences.php
index 0af6b4553c..6cceff57de 100644
--- a/src/applications/settings/storage/PhabricatorUserPreferences.php
+++ b/src/applications/settings/storage/PhabricatorUserPreferences.php
@@ -1,252 +1,256 @@
<?php
final class PhabricatorUserPreferences
extends PhabricatorUserDAO
implements
PhabricatorPolicyInterface,
PhabricatorDestructibleInterface,
PhabricatorApplicationTransactionInterface {
const BUILTIN_GLOBAL_DEFAULT = 'global';
protected $userPHID;
protected $preferences = array();
protected $builtinKey;
private $user = self::ATTACHABLE;
private $defaultSettings;
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'preferences' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'userPHID' => 'phid?',
'builtinKey' => 'text32?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_user' => array(
'columns' => array('userPHID'),
'unique' => true,
),
'key_builtin' => array(
'columns' => array('builtinKey'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorUserPreferencesPHIDType::TYPECONST);
}
public function getPreference($key, $default = null) {
return idx($this->preferences, $key, $default);
}
public function setPreference($key, $value) {
$this->preferences[$key] = $value;
return $this;
}
public function unsetPreference($key) {
unset($this->preferences[$key]);
return $this;
}
public function getDefaultValue($key) {
if ($this->defaultSettings) {
return $this->defaultSettings->getSettingValue($key);
}
$setting = self::getSettingObject($key);
if (!$setting) {
return null;
}
$setting = id(clone $setting)
->setViewer($this->getUser());
return $setting->getSettingDefaultValue();
}
public function getSettingValue($key) {
if (array_key_exists($key, $this->preferences)) {
return $this->preferences[$key];
}
return $this->getDefaultValue($key);
}
private static function getSettingObject($key) {
$settings = PhabricatorSetting::getAllSettings();
return idx($settings, $key);
}
public function attachDefaultSettings(PhabricatorUserPreferences $settings) {
$this->defaultSettings = $settings;
return $this;
}
public function attachUser(PhabricatorUser $user = null) {
$this->user = $user;
return $this;
}
public function getUser() {
return $this->assertAttached($this->user);
}
public function hasManagedUser() {
$user_phid = $this->getUserPHID();
if (!$user_phid) {
return false;
}
$user = $this->getUser();
if ($user->getIsSystemAgent() || $user->getIsMailingList()) {
return true;
}
return false;
}
/**
* Load or create a preferences object for the given user.
*
* @param PhabricatorUser User to load or create preferences for.
*/
public static function loadUserPreferences(PhabricatorUser $user) {
return id(new PhabricatorUserPreferencesQuery())
->setViewer($user)
->withUsers(array($user))
->needSyntheticPreferences(true)
->executeOne();
}
/**
* Load or create a global preferences object.
*
* If no global preferences exist, an empty preferences object is returned.
*
* @param PhabricatorUser Viewing user.
*/
public static function loadGlobalPreferences(PhabricatorUser $viewer) {
$global = id(new PhabricatorUserPreferencesQuery())
->setViewer($viewer)
->withBuiltinKeys(
array(
self::BUILTIN_GLOBAL_DEFAULT,
))
->executeOne();
if (!$global) {
$global = id(new self())
->attachUser(new PhabricatorUser());
}
return $global;
}
public function newTransaction($key, $value) {
$setting_property = PhabricatorUserPreferencesTransaction::PROPERTY_SETTING;
$xaction_type = PhabricatorUserPreferencesTransaction::TYPE_SETTING;
return id(clone $this->getApplicationTransactionTemplate())
->setTransactionType($xaction_type)
->setMetadataValue($setting_property, $key)
->setNewValue($value);
}
public function getEditURI() {
if ($this->getUser()) {
return '/settings/user/'.$this->getUser()->getUsername().'/';
} else {
return '/settings/builtin/'.$this->getBuiltinKey().'/';
}
}
public function getDisplayName() {
if ($this->getBuiltinKey()) {
return pht('Global Default Settings');
}
return pht('Personal Settings');
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
$user_phid = $this->getUserPHID();
if ($user_phid) {
return $user_phid;
}
return PhabricatorPolicies::getMostOpenPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
if ($this->hasManagedUser()) {
return PhabricatorPolicies::POLICY_ADMIN;
}
$user_phid = $this->getUserPHID();
if ($user_phid) {
return $user_phid;
}
return PhabricatorPolicies::POLICY_ADMIN;
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
if ($this->hasManagedUser()) {
if ($viewer->getIsAdmin()) {
return true;
}
}
- switch ($this->getBuiltinKey()) {
- case self::BUILTIN_GLOBAL_DEFAULT:
- // NOTE: Without this policy exception, the logged-out viewer can not
- // see global preferences.
- return true;
+ $builtin_key = $this->getBuiltinKey();
+
+ $is_global = ($builtin_key === self::BUILTIN_GLOBAL_DEFAULT);
+ $is_view = ($capability === PhabricatorPolicyCapability::CAN_VIEW);
+
+ if ($is_global && $is_view) {
+ // NOTE: Without this policy exception, the logged-out viewer can not
+ // see global preferences.
+ return true;
}
return false;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->delete();
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorUserPreferencesEditor();
}
public function getApplicationTransactionTemplate() {
return new PhabricatorUserPreferencesTransaction();
}
}
diff --git a/src/applications/slowvote/constants/SlowvotePollResponseVisibility.php b/src/applications/slowvote/constants/SlowvotePollResponseVisibility.php
new file mode 100644
index 0000000000..1daf237e72
--- /dev/null
+++ b/src/applications/slowvote/constants/SlowvotePollResponseVisibility.php
@@ -0,0 +1,75 @@
+<?php
+
+final class SlowvotePollResponseVisibility
+ extends Phobject {
+
+ const RESPONSES_VISIBLE = 'visible';
+ const RESPONSES_VOTERS = 'voters';
+ const RESPONSES_OWNER = 'owner';
+
+ private $key;
+
+ public static function newResponseVisibilityObject($key) {
+ $object = new self();
+ $object->key = $key;
+ return $object;
+ }
+
+ public function getKey() {
+ return $this->key;
+ }
+
+ public static function getAll() {
+ $map = self::getMap();
+
+ $result = array();
+ foreach ($map as $key => $spec) {
+ $result[$key] = self::newResponseVisibilityObject($key);
+ }
+
+ return $result;
+ }
+
+ public function getName() {
+ $name = $this->getProperty('name');
+
+ if ($name === null) {
+ $name = pht('Unknown ("%s")', $this->getKey());
+ }
+
+ return $name;
+ }
+
+ public function getNameForEdit() {
+ $name = $this->getProperty('name.edit');
+
+ if ($name === null) {
+ $name = pht('Unknown ("%s")', $this->getKey());
+ }
+
+ return $name;
+ }
+
+ private function getProperty($key, $default = null) {
+ $spec = idx(self::getMap(), $this->getKey(), array());
+ return idx($spec, $key, $default);
+ }
+
+ private static function getMap() {
+ return array(
+ self::RESPONSES_VISIBLE => array(
+ 'name' => pht('Always Visible'),
+ 'name.edit' => pht('Anyone can see the responses'),
+ ),
+ self::RESPONSES_VOTERS => array(
+ 'name' => pht('Voters'),
+ 'name.edit' => pht('Require a vote to see the responses'),
+ ),
+ self::RESPONSES_OWNER => array(
+ 'name' => pht('Owner'),
+ 'name.edit' => pht('Only the poll owner can see the responses'),
+ ),
+ );
+ }
+
+}
diff --git a/src/applications/slowvote/constants/SlowvotePollStatus.php b/src/applications/slowvote/constants/SlowvotePollStatus.php
new file mode 100644
index 0000000000..dd2153feec
--- /dev/null
+++ b/src/applications/slowvote/constants/SlowvotePollStatus.php
@@ -0,0 +1,76 @@
+<?php
+
+final class SlowvotePollStatus
+ extends Phobject {
+
+ const STATUS_OPEN = 'open';
+ const STATUS_CLOSED = 'closed';
+
+ private $key;
+
+ public static function newStatusObject($key) {
+ $object = new self();
+ $object->key = $key;
+ return $object;
+ }
+
+ public function getKey() {
+ return $this->key;
+ }
+
+ public static function getAll() {
+ $map = self::getMap();
+
+ $result = array();
+ foreach ($map as $key => $spec) {
+ $result[$key] = self::newStatusObject($key);
+ }
+
+ return $result;
+ }
+
+ public function getName() {
+ $name = $this->getProperty('name');
+
+ if ($name === null) {
+ $name = pht('Unknown ("%s")', $this->getKey());
+ }
+
+ return $name;
+ }
+
+ public function getHeaderTagIcon() {
+ return $this->getProperty('header.tag.icon');
+ }
+
+ public function getHeaderTagColor() {
+ return $this->getProperty('header.tag.color');
+ }
+
+ public function getTransactionIcon() {
+ return $this->getProperty('transaction.icon');
+ }
+
+ private function getProperty($key, $default = null) {
+ $spec = idx(self::getMap(), $this->getKey(), array());
+ return idx($spec, $key, $default);
+ }
+
+ private static function getMap() {
+ return array(
+ self::STATUS_OPEN => array(
+ 'name' => pht('Open'),
+ 'header.tag.icon' => 'fa-square-o',
+ 'header.tag.color' => 'bluegrey',
+ 'transaction.icon' => 'fa-pencil',
+ ),
+ self::STATUS_CLOSED => array(
+ 'name' => pht('Closed'),
+ 'header.tag.icon' => 'fa-ban',
+ 'header.tag.color' => 'indigo',
+ 'transaction.icon' => 'fa-ban',
+ ),
+ );
+ }
+
+}
diff --git a/src/applications/slowvote/constants/SlowvotePollVotingMethod.php b/src/applications/slowvote/constants/SlowvotePollVotingMethod.php
new file mode 100644
index 0000000000..f0f18641e3
--- /dev/null
+++ b/src/applications/slowvote/constants/SlowvotePollVotingMethod.php
@@ -0,0 +1,70 @@
+<?php
+
+final class SlowvotePollVotingMethod
+ extends Phobject {
+
+ const METHOD_PLURALITY = 'plurality';
+ const METHOD_APPROVAL = 'approval';
+
+ private $key;
+
+ public static function newVotingMethodObject($key) {
+ $object = new self();
+ $object->key = $key;
+ return $object;
+ }
+
+ public function getKey() {
+ return $this->key;
+ }
+
+ public static function getAll() {
+ $map = self::getMap();
+
+ $result = array();
+ foreach ($map as $key => $spec) {
+ $result[$key] = self::newVotingMethodObject($key);
+ }
+
+ return $result;
+ }
+
+ public function getName() {
+ $name = $this->getProperty('name');
+
+ if ($name === null) {
+ $name = pht('Unknown ("%s")', $this->getKey());
+ }
+
+ return $name;
+ }
+
+ public function getNameForEdit() {
+ $name = $this->getProperty('name.edit');
+
+ if ($name === null) {
+ $name = pht('Unknown ("%s")', $this->getKey());
+ }
+
+ return $name;
+ }
+
+ private function getProperty($key, $default = null) {
+ $spec = idx(self::getMap(), $this->getKey(), array());
+ return idx($spec, $key, $default);
+ }
+
+ private static function getMap() {
+ return array(
+ self::METHOD_PLURALITY => array(
+ 'name' => pht('Plurality'),
+ 'name.edit' => pht('Plurality (Single Choice)'),
+ ),
+ self::METHOD_APPROVAL => array(
+ 'name' => pht('Approval'),
+ 'name.edit' => pht('Approval (Multiple Choice)'),
+ ),
+ );
+ }
+
+}
diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php
index a4da0f7f7d..733c201aae 100644
--- a/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php
+++ b/src/applications/slowvote/controller/PhabricatorSlowvoteCloseController.php
@@ -1,66 +1,66 @@
<?php
final class PhabricatorSlowvoteCloseController
extends PhabricatorSlowvoteController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$poll = id(new PhabricatorSlowvoteQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$poll) {
return new Aphront404Response();
}
- $close_uri = '/V'.$poll->getID();
+ $close_uri = $poll->getURI();
if ($request->isFormPost()) {
- if ($poll->getIsClosed()) {
- $new_status = 0;
+ if ($poll->isClosed()) {
+ $new_status = SlowvotePollStatus::STATUS_OPEN;
} else {
- $new_status = 1;
+ $new_status = SlowvotePollStatus::STATUS_CLOSED;
}
$xactions = array();
$xactions[] = id(new PhabricatorSlowvoteTransaction())
->setTransactionType(
- PhabricatorSlowvoteCloseTransaction::TRANSACTIONTYPE)
+ PhabricatorSlowvoteStatusTransaction::TRANSACTIONTYPE)
->setNewValue($new_status);
id(new PhabricatorSlowvoteEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($poll, $xactions);
return id(new AphrontRedirectResponse())->setURI($close_uri);
}
- if ($poll->getIsClosed()) {
+ if ($poll->isClosed()) {
$title = pht('Reopen Poll');
$content = pht('Are you sure you want to reopen the poll?');
$submit = pht('Reopen');
} else {
$title = pht('Close Poll');
$content = pht('Are you sure you want to close the poll?');
$submit = pht('Close');
}
return $this->newDialog()
->setTitle($title)
->appendChild($content)
->addSubmitButton($submit)
->addCancelButton($close_uri);
}
}
diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php
index 44927fedea..48639ffde6 100644
--- a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php
+++ b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php
@@ -1,289 +1,301 @@
<?php
final class PhabricatorSlowvoteEditController
extends PhabricatorSlowvoteController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
if ($id) {
$poll = id(new PhabricatorSlowvoteQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$poll) {
return new Aphront404Response();
}
$is_new = false;
} else {
$poll = PhabricatorSlowvotePoll::initializeNewPoll($viewer);
$is_new = true;
}
if ($is_new) {
$v_projects = array();
} else {
$v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
$poll->getPHID(),
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
$v_projects = array_reverse($v_projects);
}
$e_question = true;
$e_response = true;
$errors = array();
$v_question = $poll->getQuestion();
$v_description = $poll->getDescription();
$v_responses = $poll->getResponseVisibility();
$v_shuffle = $poll->getShuffle();
$v_space = $poll->getSpacePHID();
$responses = $request->getArr('response');
if ($request->isFormPost()) {
$v_question = $request->getStr('question');
$v_description = $request->getStr('description');
- $v_responses = (int)$request->getInt('responses');
+ $v_responses = $request->getStr('responses');
$v_shuffle = (int)$request->getBool('shuffle');
$v_view_policy = $request->getStr('viewPolicy');
$v_projects = $request->getArr('projects');
$v_space = $request->getStr('spacePHID');
if ($is_new) {
- $poll->setMethod($request->getInt('method'));
+ $poll->setMethod($request->getStr('method'));
}
if (!strlen($v_question)) {
$e_question = pht('Required');
$errors[] = pht('You must ask a poll question.');
} else {
$e_question = null;
}
if ($is_new) {
// NOTE: Make sure common and useful response "0" is preserved.
foreach ($responses as $key => $response) {
if (!strlen($response)) {
unset($responses[$key]);
}
}
if (empty($responses)) {
$errors[] = pht('You must offer at least one response.');
$e_response = pht('Required');
} else {
$e_response = null;
}
}
$template = id(new PhabricatorSlowvoteTransaction());
$xactions = array();
if ($is_new) {
$xactions[] = id(new PhabricatorSlowvoteTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_CREATE);
}
$xactions[] = id(clone $template)
->setTransactionType(
PhabricatorSlowvoteQuestionTransaction::TRANSACTIONTYPE)
->setNewValue($v_question);
$xactions[] = id(clone $template)
->setTransactionType(
PhabricatorSlowvoteDescriptionTransaction::TRANSACTIONTYPE)
->setNewValue($v_description);
$xactions[] = id(clone $template)
->setTransactionType(
PhabricatorSlowvoteResponsesTransaction::TRANSACTIONTYPE)
->setNewValue($v_responses);
$xactions[] = id(clone $template)
->setTransactionType(
PhabricatorSlowvoteShuffleTransaction::TRANSACTIONTYPE)
->setNewValue($v_shuffle);
$xactions[] = id(clone $template)
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
->setNewValue($v_view_policy);
$xactions[] = id(clone $template)
->setTransactionType(PhabricatorTransactions::TYPE_SPACE)
->setNewValue($v_space);
if (empty($errors)) {
$proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
$xactions[] = id(new PhabricatorSlowvoteTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $proj_edge_type)
->setNewValue(array('=' => array_fuse($v_projects)));
$editor = id(new PhabricatorSlowvoteEditor())
->setActor($viewer)
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request);
$xactions = $editor->applyTransactions($poll, $xactions);
if ($is_new) {
$poll->save();
foreach ($responses as $response) {
$option = new PhabricatorSlowvoteOption();
$option->setName($response);
$option->setPollID($poll->getID());
$option->save();
}
}
return id(new AphrontRedirectResponse())
->setURI($poll->getURI());
} else {
$poll->setViewPolicy($v_view_policy);
}
}
$form = id(new AphrontFormView())
->setAction($request->getrequestURI())
->setUser($viewer)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Question'))
->setName('question')
->setValue($v_question)
->setError($e_question))
->appendChild(
id(new PhabricatorRemarkupControl())
->setUser($viewer)
->setLabel(pht('Description'))
->setName('description')
->setValue($v_description))
->appendControl(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Tags'))
->setName('projects')
->setValue($v_projects)
->setDatasource(new PhabricatorProjectDatasource()));
if ($is_new) {
for ($ii = 0; $ii < 10; $ii++) {
$n = ($ii + 1);
$response = id(new AphrontFormTextControl())
->setLabel(pht('Response %d', $n))
->setName('response[]')
->setValue(idx($responses, $ii, ''));
if ($ii == 0) {
$response->setError($e_response);
}
$form->appendChild($response);
}
}
- $poll_type_options = array(
- PhabricatorSlowvotePoll::METHOD_PLURALITY =>
- pht('Plurality (Single Choice)'),
- PhabricatorSlowvotePoll::METHOD_APPROVAL =>
- pht('Approval (Multiple Choice)'),
- );
-
- $response_type_options = array(
- PhabricatorSlowvotePoll::RESPONSES_VISIBLE
- => pht('Allow anyone to see the responses'),
- PhabricatorSlowvotePoll::RESPONSES_VOTERS
- => pht('Require a vote to see the responses'),
- PhabricatorSlowvotePoll::RESPONSES_OWNER
- => pht('Only I can see the responses'),
- );
+ $vote_type_map = SlowvotePollVotingMethod::getAll();
+ $vote_type_options = mpull($vote_type_map, 'getNameForEdit');
+
+ $method = $poll->getMethod();
+ if (!isset($vote_type_options[$method])) {
+ $method_object =
+ SlowvotePollVotingMethod::newVotingMethodObject(
+ $method);
+
+ $vote_type_options = array(
+ $method => $method_object->getNameForEdit(),
+ ) + $vote_type_options;
+ }
+
+ $response_type_map = SlowvotePollResponseVisibility::getAll();
+ $response_type_options = mpull($response_type_map, 'getNameForEdit');
+
+ $visibility = $poll->getResponseVisibility();
+ if (!isset($response_type_options[$visibility])) {
+ $visibility_object =
+ SlowvotePollResponseVisibility::newResponseVisibilityObject(
+ $visibility);
+
+ $response_type_options = array(
+ $visibility => $visibility_object->getNameForEdit(),
+ ) + $response_type_options;
+ }
if ($is_new) {
$form->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Vote Type'))
->setName('method')
->setValue($poll->getMethod())
- ->setOptions($poll_type_options));
+ ->setOptions($vote_type_options));
} else {
$form->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Vote Type'))
- ->setValue(idx($poll_type_options, $poll->getMethod())));
+ ->setValue(idx($vote_type_options, $poll->getMethod())));
}
if ($is_new) {
$title = pht('Create Slowvote');
$button = pht('Create');
$cancel_uri = $this->getApplicationURI();
$header_icon = 'fa-plus-square';
} else {
$title = pht('Edit Poll: %s', $poll->getQuestion());
$button = pht('Save Changes');
$cancel_uri = '/V'.$poll->getID();
$header_icon = 'fa-pencil';
}
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($poll)
->execute();
$form
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Responses'))
->setName('responses')
->setValue($v_responses)
->setOptions($response_type_options))
->appendChild(
id(new AphrontFormCheckboxControl())
->setLabel(pht('Shuffle'))
->addCheckbox(
'shuffle',
1,
pht('Show choices in random order.'),
$v_shuffle))
->appendChild(
id(new AphrontFormPolicyControl())
->setUser($viewer)
->setName('viewPolicy')
->setPolicyObject($poll)
->setPolicies($policies)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setSpacePHID($v_space))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue($button)
->addCancelButton($cancel_uri));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($title);
$crumbs->setBorder(true);
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setFormErrors($errors)
->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->setForm($form);
$view = id(new PHUITwoColumnView())
->setFooter($form_box);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild(
array(
$view,
));
}
}
diff --git a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php
index 1ffab17791..7ed83fb13f 100644
--- a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php
+++ b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php
@@ -1,158 +1,164 @@
<?php
final class PhabricatorSlowvotePollController
extends PhabricatorSlowvoteController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$poll = id(new PhabricatorSlowvoteQuery())
->setViewer($viewer)
->withIDs(array($id))
->needOptions(true)
->needChoices(true)
->needViewerChoices(true)
->executeOne();
if (!$poll) {
return new Aphront404Response();
}
$poll_view = id(new SlowvoteEmbedView())
->setUser($viewer)
->setPoll($poll);
if ($request->isAjax()) {
return id(new AphrontAjaxResponse())
->setContent(
array(
'pollID' => $poll->getID(),
'contentHTML' => $poll_view->render(),
));
}
- $header_icon = $poll->getIsClosed() ? 'fa-ban' : 'fa-square-o';
- $header_name = $poll->getIsClosed() ? pht('Closed') : pht('Open');
- $header_color = $poll->getIsClosed() ? 'indigo' : 'bluegrey';
+ $status = $poll->getStatusObject();
+
+ $header_icon = $status->getHeaderTagIcon();
+ $header_color = $status->getHeaderTagColor();
+ $header_name = $status->getName();
$header = id(new PHUIHeaderView())
->setHeader($poll->getQuestion())
->setUser($viewer)
->setStatus($header_icon, $header_color, $header_name)
->setPolicyObject($poll)
->setHeaderIcon('fa-bar-chart');
$curtain = $this->buildCurtain($poll);
$subheader = $this->buildSubheaderView($poll);
$crumbs = $this->buildApplicationCrumbs();
- $crumbs->addTextCrumb('V'.$poll->getID());
+ $crumbs->addTextCrumb($poll->getMonogram());
$crumbs->setBorder(true);
$timeline = $this->buildTransactionTimeline(
$poll,
new PhabricatorSlowvoteTransactionQuery());
$add_comment = $this->buildCommentForm($poll);
$poll_content = array(
$poll_view,
$timeline,
$add_comment,
);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setSubheader($subheader)
->setCurtain($curtain)
->setMainColumn($poll_content);
return $this->newPage()
- ->setTitle('V'.$poll->getID().' '.$poll->getQuestion())
+ ->setTitle(
+ pht(
+ '%s %s',
+ $poll->getMonogram(),
+ $poll->getQuestion()))
->setCrumbs($crumbs)
->setPageObjectPHIDs(array($poll->getPHID()))
->appendChild($view);
}
private function buildCurtain(PhabricatorSlowvotePoll $poll) {
$viewer = $this->getViewer();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$poll,
PhabricatorPolicyCapability::CAN_EDIT);
$curtain = $this->newCurtainView($poll);
- $is_closed = $poll->getIsClosed();
+ $is_closed = $poll->isClosed();
$close_poll_text = $is_closed ? pht('Reopen Poll') : pht('Close Poll');
$close_poll_icon = $is_closed ? 'fa-check' : 'fa-ban';
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Poll'))
->setIcon('fa-pencil')
->setHref($this->getApplicationURI('edit/'.$poll->getID().'/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$curtain->addAction(
id(new PhabricatorActionView())
->setName($close_poll_text)
->setIcon($close_poll_icon)
->setHref($this->getApplicationURI('close/'.$poll->getID().'/'))
->setDisabled(!$can_edit)
->setWorkflow(true));
return $curtain;
}
private function buildSubheaderView(
PhabricatorSlowvotePoll $poll) {
$viewer = $this->getViewer();
$author = $viewer->renderHandle($poll->getAuthorPHID())->render();
$date = phabricator_datetime($poll->getDateCreated(), $viewer);
$author = phutil_tag('strong', array(), $author);
$person = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withPHIDs(array($poll->getAuthorPHID()))
->needProfileImage(true)
->executeOne();
$image_uri = $person->getProfileImageURI();
$image_href = '/p/'.$person->getUsername();
$content = pht('Asked by %s on %s.', $author, $date);
return id(new PHUIHeadThingView())
->setImage($image_uri)
->setImageHref($image_href)
->setContent($content);
}
private function buildCommentForm(PhabricatorSlowvotePoll $poll) {
$viewer = $this->getRequest()->getUser();
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$add_comment_header = $is_serious
? pht('Add Comment')
: pht('Enter Deliberations');
$draft = PhabricatorDraft::newFromUserAndKey($viewer, $poll->getPHID());
return id(new PhabricatorApplicationTransactionCommentView())
->setUser($viewer)
->setObjectPHID($poll->getPHID())
->setDraft($draft)
->setHeaderText($add_comment_header)
->setAction($this->getApplicationURI('/comment/'.$poll->getID().'/'))
->setSubmitButtonName(pht('Add Comment'));
}
}
diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteVoteController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteVoteController.php
index e1a1b9df34..8c70c2833b 100644
--- a/src/applications/slowvote/controller/PhabricatorSlowvoteVoteController.php
+++ b/src/applications/slowvote/controller/PhabricatorSlowvoteVoteController.php
@@ -1,106 +1,106 @@
<?php
final class PhabricatorSlowvoteVoteController
extends PhabricatorSlowvoteController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
if (!$request->isFormPost()) {
return id(new Aphront404Response());
}
$poll = id(new PhabricatorSlowvoteQuery())
->setViewer($viewer)
->withIDs(array($id))
->needOptions(true)
->needViewerChoices(true)
->executeOne();
if (!$poll) {
return new Aphront404Response();
}
- if ($poll->getIsClosed()) {
+ if ($poll->isClosed()) {
return new Aphront400Response();
}
$options = $poll->getOptions();
$options = mpull($options, null, 'getID');
$old_votes = $poll->getViewerChoices($viewer);
$old_votes = mpull($old_votes, null, 'getOptionID');
$votes = $request->getArr('vote');
$votes = array_fuse($votes);
$method = $poll->getMethod();
- $is_plurality = ($method == PhabricatorSlowvotePoll::METHOD_PLURALITY);
+ $is_plurality = ($method == SlowvotePollVotingMethod::METHOD_PLURALITY);
if (!$votes) {
if ($is_plurality) {
$message = pht('You must vote for something.');
} else {
$message = pht('You must vote for at least one option.');
}
return $this->newDialog()
->setTitle(pht('Stand For Something'))
->appendParagraph($message)
->addCancelButton($poll->getURI());
}
if ($is_plurality && count($votes) > 1) {
throw new Exception(
pht('In this poll, you may only vote for one option.'));
}
foreach ($votes as $vote) {
if (!isset($options[$vote])) {
throw new Exception(
pht(
'Option ("%s") is not a valid poll option. You may only '.
'vote for valid options.',
$vote));
}
}
$poll->openTransaction();
$poll->beginReadLocking();
$poll->reload();
$old_votes = id(new PhabricatorSlowvoteChoice())->loadAllWhere(
'pollID = %d AND authorPHID = %s',
$poll->getID(),
$viewer->getPHID());
$old_votes = mpull($old_votes, null, 'getOptionID');
foreach ($old_votes as $old_vote) {
if (idx($votes, $old_vote->getOptionID())) {
continue;
}
$old_vote->delete();
}
foreach ($votes as $vote) {
if (idx($old_votes, $vote)) {
continue;
}
id(new PhabricatorSlowvoteChoice())
->setAuthorPHID($viewer->getPHID())
->setPollID($poll->getID())
->setOptionID($vote)
->save();
}
$poll->endReadLocking();
$poll->saveTransaction();
return id(new AphrontRedirectResponse())
->setURI($poll->getURI());
}
}
diff --git a/src/applications/slowvote/query/PhabricatorSlowvoteQuery.php b/src/applications/slowvote/query/PhabricatorSlowvoteQuery.php
index 0b7335326c..d2ae9f1a35 100644
--- a/src/applications/slowvote/query/PhabricatorSlowvoteQuery.php
+++ b/src/applications/slowvote/query/PhabricatorSlowvoteQuery.php
@@ -1,174 +1,171 @@
<?php
final class PhabricatorSlowvoteQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $authorPHIDs;
private $withVotesByViewer;
- private $isClosed;
+ private $statuses;
private $needOptions;
private $needChoices;
private $needViewerChoices;
public function withIDs($ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs($phids) {
$this->phids = $phids;
return $this;
}
public function withAuthorPHIDs($author_phids) {
$this->authorPHIDs = $author_phids;
return $this;
}
public function withVotesByViewer($with_vote) {
$this->withVotesByViewer = $with_vote;
return $this;
}
- public function withIsClosed($with_closed) {
- $this->isClosed = $with_closed;
+ public function withStatuses(array $statuses) {
+ $this->statuses = $statuses;
return $this;
}
public function needOptions($need_options) {
$this->needOptions = $need_options;
return $this;
}
public function needChoices($need_choices) {
$this->needChoices = $need_choices;
return $this;
}
public function needViewerChoices($need_viewer_choices) {
$this->needViewerChoices = $need_viewer_choices;
return $this;
}
public function newResultObject() {
return new PhabricatorSlowvotePoll();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $polls) {
assert_instances_of($polls, 'PhabricatorSlowvotePoll');
$ids = mpull($polls, 'getID');
$viewer = $this->getViewer();
if ($this->needOptions) {
$options = id(new PhabricatorSlowvoteOption())->loadAllWhere(
'pollID IN (%Ld)',
$ids);
$options = mgroup($options, 'getPollID');
foreach ($polls as $poll) {
$poll->attachOptions(idx($options, $poll->getID(), array()));
}
}
if ($this->needChoices) {
$choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere(
'pollID IN (%Ld)',
$ids);
$choices = mgroup($choices, 'getPollID');
foreach ($polls as $poll) {
$poll->attachChoices(idx($choices, $poll->getID(), array()));
}
// If we need the viewer's choices, we can just fill them from the data
// we already loaded.
if ($this->needViewerChoices) {
foreach ($polls as $poll) {
$poll->attachViewerChoices(
$viewer,
idx(
mgroup($poll->getChoices(), 'getAuthorPHID'),
$viewer->getPHID(),
array()));
}
}
} else if ($this->needViewerChoices) {
$choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere(
'pollID IN (%Ld) AND authorPHID = %s',
$ids,
$viewer->getPHID());
$choices = mgroup($choices, 'getPollID');
foreach ($polls as $poll) {
$poll->attachViewerChoices(
$viewer,
idx($choices, $poll->getID(), array()));
}
}
return $polls;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'p.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'p.phid IN (%Ls)',
$this->phids);
}
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'p.authorPHID IN (%Ls)',
$this->authorPHIDs);
}
- if ($this->isClosed !== null) {
+ if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
- 'p.isClosed = %d',
- (int)$this->isClosed);
+ 'p.status IN (%Ls)',
+ $this->statuses);
}
+
return $where;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->withVotesByViewer !== null) {
$joins[] = qsprintf(
$conn,
'JOIN %T vv ON vv.pollID = p.id AND vv.authorPHID = %s',
id(new PhabricatorSlowvoteChoice())->getTableName(),
$this->getViewer()->getPHID());
}
return $joins;
}
protected function getPrimaryTableAlias() {
return 'p';
}
public function getQueryApplicationClass() {
return 'PhabricatorSlowvoteApplication';
}
}
diff --git a/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php b/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php
index 44bfb917c2..8b93f75faf 100644
--- a/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php
+++ b/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php
@@ -1,192 +1,185 @@
<?php
final class PhabricatorSlowvoteSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
return pht('Slowvotes');
}
public function getApplicationClassName() {
return 'PhabricatorSlowvoteApplication';
}
public function newQuery() {
return new PhabricatorSlowvoteQuery();
}
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
if ($map['voted']) {
$query->withVotesByViewer(true);
}
if ($map['authorPHIDs']) {
$query->withAuthorPHIDs($map['authorPHIDs']);
}
- $statuses = $map['statuses'];
- if (count($statuses) == 1) {
- $status = head($statuses);
- if ($status == 'open') {
- $query->withIsClosed(false);
- } else {
- $query->withIsClosed(true);
- }
+ if ($map['statuses']) {
+ $query->withStatuses($map['statuses']);
}
return $query;
}
protected function buildCustomSearchFields() {
+ $status_options = SlowvotePollStatus::getAll();
+ $status_options = mpull($status_options, 'getName');
+
return array(
id(new PhabricatorUsersSearchField())
->setKey('authorPHIDs')
->setAliases(array('authors'))
->setLabel(pht('Authors')),
id(new PhabricatorSearchCheckboxesField())
->setKey('voted')
->setLabel(pht('Voted'))
// TODO: This should probably become a list of "voterPHIDs", so hide
// the field from Conduit to avoid a backward compatibility break when
// this changes.
->setEnableForConduit(false)
->setOptions(array(
'voted' => pht("Show only polls I've voted in."),
)),
id(new PhabricatorSearchCheckboxesField())
->setKey('statuses')
->setLabel(pht('Statuses'))
- ->setOptions(
- array(
- 'open' => pht('Open'),
- 'closed' => pht('Closed'),
- )),
+ ->setOptions($status_options),
);
}
protected function getURI($path) {
return '/vote/'.$path;
}
protected function getBuiltinQueryNames() {
$names = array(
'open' => pht('Open Polls'),
'all' => pht('All Polls'),
);
if ($this->requireViewer()->isLoggedIn()) {
$names['authored'] = pht('Authored');
$names['voted'] = pht('Voted In');
}
return $names;
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
switch ($query_key) {
case 'open':
return $query->setParameter('statuses', array('open'));
case 'all':
return $query;
case 'authored':
return $query->setParameter(
'authorPHIDs',
array($this->requireViewer()->getPHID()));
case 'voted':
return $query->setParameter('voted', array('voted'));
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
protected function getRequiredHandlePHIDsForResultList(
array $polls,
PhabricatorSavedQuery $query) {
return mpull($polls, 'getAuthorPHID');
}
protected function renderResultList(
array $polls,
PhabricatorSavedQuery $query,
array $handles) {
assert_instances_of($polls, 'PhabricatorSlowvotePoll');
$viewer = $this->requireViewer();
$list = id(new PHUIObjectItemListView())
->setUser($viewer);
$phids = mpull($polls, 'getAuthorPHID');
foreach ($polls as $poll) {
$date_created = phabricator_datetime($poll->getDateCreated(), $viewer);
if ($poll->getAuthorPHID()) {
$author = $handles[$poll->getAuthorPHID()]->renderLink();
} else {
$author = null;
}
$item = id(new PHUIObjectItemView())
->setUser($viewer)
->setObject($poll)
- ->setObjectName('V'.$poll->getID())
+ ->setObjectName($poll->getMonogram())
->setHeader($poll->getQuestion())
- ->setHref('/V'.$poll->getID())
+ ->setHref($poll->getURI())
->addIcon('none', $date_created);
- if ($poll->getIsClosed()) {
+ if ($poll->isClosed()) {
$item->setStatusIcon('fa-ban grey');
$item->setDisabled(true);
} else {
$item->setStatusIcon('fa-bar-chart');
}
$description = $poll->getDescription();
if (strlen($description)) {
$item->addAttribute(id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs(120)
->truncateString($poll->getDescription()));
}
if ($author) {
$item->addByline(pht('Author: %s', $author));
}
$list->addItem($item);
}
$result = new PhabricatorApplicationSearchResultView();
$result->setObjectList($list);
$result->setNoDataString(pht('No polls found.'));
return $result;
}
protected function getNewUserBody() {
$create_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('Create a Poll'))
->setHref('/vote/create/')
->setColor(PHUIButtonView::GREEN);
$icon = $this->getApplication()->getIcon();
$app_name = $this->getApplication()->getName();
$view = id(new PHUIBigInfoView())
->setIcon($icon)
->setTitle(pht('Welcome to %s', $app_name))
->setDescription(
pht('Poll other users to help facilitate decision making.'))
->addAction($create_button);
return $view;
}
}
diff --git a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php
index 215549f7db..9ee33c81bb 100644
--- a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php
+++ b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php
@@ -1,238 +1,231 @@
<?php
-final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO
+final class PhabricatorSlowvotePoll
+ extends PhabricatorSlowvoteDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface,
PhabricatorSubscribableInterface,
PhabricatorFlaggableInterface,
PhabricatorTokenReceiverInterface,
PhabricatorProjectInterface,
PhabricatorDestructibleInterface,
PhabricatorSpacesInterface,
PhabricatorConduitResultInterface {
- const RESPONSES_VISIBLE = 0;
- const RESPONSES_VOTERS = 1;
- const RESPONSES_OWNER = 2;
-
- const METHOD_PLURALITY = 0;
- const METHOD_APPROVAL = 1;
-
protected $question;
protected $description;
protected $authorPHID;
- protected $responseVisibility = 0;
+ protected $responseVisibility;
protected $shuffle = 0;
protected $method;
- protected $mailKey;
protected $viewPolicy;
- protected $isClosed = 0;
+ protected $status;
protected $spacePHID;
private $options = self::ATTACHABLE;
private $choices = self::ATTACHABLE;
private $viewerChoices = self::ATTACHABLE;
public static function initializeNewPoll(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
->withClasses(array('PhabricatorSlowvoteApplication'))
->executeOne();
$view_policy = $app->getPolicy(
PhabricatorSlowvoteDefaultViewCapability::CAPABILITY);
+ $default_responses = SlowvotePollResponseVisibility::RESPONSES_VISIBLE;
+ $default_method = SlowvotePollVotingMethod::METHOD_PLURALITY;
+
return id(new PhabricatorSlowvotePoll())
->setAuthorPHID($actor->getPHID())
->setViewPolicy($view_policy)
- ->setSpacePHID($actor->getDefaultSpacePHID());
+ ->setSpacePHID($actor->getDefaultSpacePHID())
+ ->setStatus(SlowvotePollStatus::STATUS_OPEN)
+ ->setMethod($default_method)
+ ->setResponseVisibility($default_responses);
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'question' => 'text255',
- 'responseVisibility' => 'uint32',
+ 'responseVisibility' => 'text32',
'shuffle' => 'bool',
- 'method' => 'uint32',
+ 'method' => 'text32',
'description' => 'text',
- 'isClosed' => 'bool',
- 'mailKey' => 'bytes20',
+ 'status' => 'text32',
),
self::CONFIG_KEY_SCHEMA => array(
- 'key_phid' => null,
- 'phid' => array(
- 'columns' => array('phid'),
- 'unique' => true,
- ),
),
) + parent::getConfiguration();
}
- public function generatePHID() {
- return PhabricatorPHID::generateNewPHID(
- PhabricatorSlowvotePollPHIDType::TYPECONST);
+ public function getPHIDType() {
+ return PhabricatorSlowvotePollPHIDType::TYPECONST;
+ }
+
+ public function getStatusObject() {
+ return SlowvotePollStatus::newStatusObject($this->getStatus());
+ }
+
+ public function isClosed() {
+ return ($this->getStatus() == SlowvotePollStatus::STATUS_CLOSED);
}
public function getOptions() {
return $this->assertAttached($this->options);
}
public function attachOptions(array $options) {
assert_instances_of($options, 'PhabricatorSlowvoteOption');
$this->options = $options;
return $this;
}
public function getChoices() {
return $this->assertAttached($this->choices);
}
public function attachChoices(array $choices) {
assert_instances_of($choices, 'PhabricatorSlowvoteChoice');
$this->choices = $choices;
return $this;
}
public function getViewerChoices(PhabricatorUser $viewer) {
return $this->assertAttachedKey($this->viewerChoices, $viewer->getPHID());
}
public function attachViewerChoices(PhabricatorUser $viewer, array $choices) {
if ($this->viewerChoices === self::ATTACHABLE) {
$this->viewerChoices = array();
}
assert_instances_of($choices, 'PhabricatorSlowvoteChoice');
$this->viewerChoices[$viewer->getPHID()] = $choices;
return $this;
}
public function getMonogram() {
return 'V'.$this->getID();
}
public function getURI() {
return '/'.$this->getMonogram();
}
- public function save() {
- if (!$this->getMailKey()) {
- $this->setMailKey(Filesystem::readRandomCharacters(20));
- }
- return parent::save();
- }
-
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorSlowvoteEditor();
}
public function getApplicationTransactionTemplate() {
return new PhabricatorSlowvoteTransaction();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->viewPolicy;
case PhabricatorPolicyCapability::CAN_EDIT:
return PhabricatorPolicies::POLICY_NOONE;
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return ($viewer->getPHID() == $this->getAuthorPHID());
}
public function describeAutomaticCapability($capability) {
return pht('The author of a poll can always view and edit it.');
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
public function isAutomaticallySubscribed($phid) {
return ($phid == $this->getAuthorPHID());
}
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
public function getUsersToNotifyOfTokenGiven() {
return array($this->getAuthorPHID());
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere(
'pollID = %d',
$this->getID());
foreach ($choices as $choice) {
$choice->delete();
}
$options = id(new PhabricatorSlowvoteOption())->loadAllWhere(
'pollID = %d',
$this->getID());
foreach ($options as $option) {
$option->delete();
}
$this->delete();
$this->saveTransaction();
}
/* -( PhabricatorSpacesInterface )----------------------------------------- */
public function getSpacePHID() {
return $this->spacePHID;
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('name')
->setType('string')
->setDescription(pht('The name of the poll.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('authorPHID')
->setType('string')
->setDescription(pht('The author of the poll.')),
);
}
public function getFieldValuesForConduit() {
return array(
'name' => $this->getQuestion(),
'authorPHID' => $this->getAuthorPHID(),
);
}
public function getConduitSearchAttachments() {
return array();
}
}
diff --git a/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php b/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php
index 1781733acf..ef86a95760 100644
--- a/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php
+++ b/src/applications/slowvote/storage/PhabricatorSlowvoteTransaction.php
@@ -1,48 +1,48 @@
<?php
final class PhabricatorSlowvoteTransaction
extends PhabricatorModularTransaction {
const MAILTAG_DETAILS = 'vote:details';
const MAILTAG_RESPONSES = 'vote:responses';
const MAILTAG_OTHER = 'vote:vote';
public function getApplicationName() {
return 'slowvote';
}
public function getApplicationTransactionType() {
return PhabricatorSlowvotePollPHIDType::TYPECONST;
}
public function getApplicationTransactionCommentObject() {
return new PhabricatorSlowvoteTransactionComment();
}
public function getBaseTransactionClass() {
return 'PhabricatorSlowvoteTransactionType';
}
public function getMailTags() {
$tags = parent::getMailTags();
switch ($this->getTransactionType()) {
case PhabricatorSlowvoteQuestionTransaction::TRANSACTIONTYPE:
case PhabricatorSlowvoteDescriptionTransaction::TRANSACTIONTYPE:
case PhabricatorSlowvoteShuffleTransaction::TRANSACTIONTYPE:
- case PhabricatorSlowvoteCloseTransaction::TRANSACTIONTYPE:
+ case PhabricatorSlowvoteStatusTransaction::TRANSACTIONTYPE:
$tags[] = self::MAILTAG_DETAILS;
break;
case PhabricatorSlowvoteResponsesTransaction::TRANSACTIONTYPE:
$tags[] = self::MAILTAG_RESPONSES;
break;
default:
$tags[] = self::MAILTAG_OTHER;
break;
}
return $tags;
}
}
diff --git a/src/applications/slowvote/view/SlowvoteEmbedView.php b/src/applications/slowvote/view/SlowvoteEmbedView.php
index 1183f3b1cd..323801de47 100644
--- a/src/applications/slowvote/view/SlowvoteEmbedView.php
+++ b/src/applications/slowvote/view/SlowvoteEmbedView.php
@@ -1,335 +1,343 @@
<?php
final class SlowvoteEmbedView extends AphrontView {
private $poll;
private $handles;
public function setPoll(PhabricatorSlowvotePoll $poll) {
$this->poll = $poll;
return $this;
}
public function getPoll() {
return $this->poll;
}
public function render() {
if (!$this->poll) {
throw new PhutilInvalidStateException('setPoll');
}
$poll = $this->poll;
$phids = array();
foreach ($poll->getChoices() as $choice) {
$phids[] = $choice->getAuthorPHID();
}
$phids[] = $poll->getAuthorPHID();
$this->handles = id(new PhabricatorHandleQuery())
->setViewer($this->getUser())
->withPHIDs($phids)
->execute();
$options = $poll->getOptions();
if ($poll->getShuffle()) {
shuffle($options);
}
require_celerity_resource('phabricator-slowvote-css');
$user_choices = $poll->getViewerChoices($this->getUser());
$user_choices = mpull($user_choices, 'getOptionID', 'getOptionID');
$out = array();
foreach ($options as $option) {
$is_selected = isset($user_choices[$option->getID()]);
$out[] = $this->renderLabel($option, $is_selected);
}
$link_to_slowvote = phutil_tag(
'a',
array(
'href' => '/V'.$poll->getID(),
),
$poll->getQuestion());
$header = id(new PHUIHeaderView())
->setHeader($link_to_slowvote);
$description = $poll->getDescription();
if (strlen($description)) {
$description = new PHUIRemarkupView($this->getUser(), $description);
$description = phutil_tag(
'div',
array(
'class' => 'slowvote-description',
),
$description);
}
$header = array(
$header,
$description,
);
+ $quip = pht('Voting improves cardiovascular endurance.');
+
$vis = $poll->getResponseVisibility();
if ($this->areResultsVisible()) {
- if ($vis == PhabricatorSlowvotePoll::RESPONSES_OWNER) {
+ if ($vis == SlowvotePollResponseVisibility::RESPONSES_OWNER) {
$quip = pht('Only you can see the results.');
- } else {
- $quip = pht('Voting improves cardiovascular endurance.');
}
- } else if ($vis == PhabricatorSlowvotePoll::RESPONSES_VOTERS) {
+ } else if ($vis == SlowvotePollResponseVisibility::RESPONSES_VOTERS) {
$quip = pht('You must vote to see the results.');
- } else if ($vis == PhabricatorSlowvotePoll::RESPONSES_OWNER) {
+ } else if ($vis == SlowvotePollResponseVisibility::RESPONSES_OWNER) {
$quip = pht('Only the author can see the results.');
}
$hint = phutil_tag(
'span',
array(
'class' => 'slowvote-hint',
),
$quip);
- if ($poll->getIsClosed()) {
+ if ($poll->isClosed()) {
$submit = null;
} else {
$submit = phutil_tag(
'div',
array(
'class' => 'slowvote-footer',
),
phutil_tag(
'div',
array(
'class' => 'slowvote-footer-content',
),
array(
$hint,
phutil_tag(
'button',
array(
),
pht('Engage in Deliberations')),
)));
}
$body = phabricator_form(
$this->getUser(),
array(
'action' => '/vote/'.$poll->getID().'/',
'method' => 'POST',
'class' => 'slowvote-body',
),
array(
phutil_tag(
'div',
array(
'class' => 'slowvote-body-content',
),
$out),
$submit,
));
$embed = javelin_tag(
'div',
array(
'class' => 'slowvote-embed',
'sigil' => 'slowvote-embed',
'meta' => array(
'pollID' => $poll->getID(),
),
),
array($body));
return id(new PHUIObjectBoxView())
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setHeader($header)
->appendChild($embed)
->addClass('slowvote-poll-view');
}
private function renderLabel(PhabricatorSlowvoteOption $option, $selected) {
$classes = array();
$classes[] = 'slowvote-option-label';
$status = $this->renderStatus($option);
$voters = $this->renderVoters($option);
return phutil_tag(
'div',
array(
'class' => 'slowvote-option-label-group',
),
array(
phutil_tag(
'label',
array(
'class' => implode(' ', $classes),
),
array(
phutil_tag(
'div',
array(
'class' => 'slowvote-control-offset',
),
$option->getName()),
$this->renderBar($option),
phutil_tag(
'div',
array(
'class' => 'slowvote-above-the-bar',
),
array(
$this->renderControl($option, $selected),
$status,
)),
)),
$voters,
));
}
private function renderBar(PhabricatorSlowvoteOption $option) {
if (!$this->areResultsVisible()) {
return null;
}
$poll = $this->getPoll();
$choices = mgroup($poll->getChoices(), 'getOptionID');
$choices = count(idx($choices, $option->getID(), array()));
$count = count(mgroup($poll->getChoices(), 'getAuthorPHID'));
return phutil_tag(
'div',
array(
'class' => 'slowvote-bar',
'style' => sprintf(
'width: %.1f%%;',
$count ? 100 * ($choices / $count) : 0),
),
array(
phutil_tag(
'div',
array(
'class' => 'slowvote-control-offset',
),
$option->getName()),
));
}
private function renderControl(PhabricatorSlowvoteOption $option, $selected) {
$types = array(
- PhabricatorSlowvotePoll::METHOD_PLURALITY => 'radio',
- PhabricatorSlowvotePoll::METHOD_APPROVAL => 'checkbox',
+ SlowvotePollVotingMethod::METHOD_PLURALITY => 'radio',
+ SlowvotePollVotingMethod::METHOD_APPROVAL => 'checkbox',
);
- $closed = $this->getPoll()->getIsClosed();
+ $closed = $this->getPoll()->isClosed();
return phutil_tag(
'input',
array(
'type' => idx($types, $this->getPoll()->getMethod()),
'name' => 'vote[]',
'value' => $option->getID(),
'checked' => ($selected ? 'checked' : null),
'disabled' => ($closed ? 'disabled' : null),
));
}
private function renderVoters(PhabricatorSlowvoteOption $option) {
if (!$this->areResultsVisible()) {
return null;
}
$poll = $this->getPoll();
$choices = mgroup($poll->getChoices(), 'getOptionID');
$choices = idx($choices, $option->getID(), array());
if (!$choices) {
return null;
}
$handles = $this->handles;
$authors = mpull($choices, 'getAuthorPHID', 'getAuthorPHID');
$viewer_phid = $this->getUser()->getPHID();
// Put the viewer first if they've voted for this option.
$authors = array_select_keys($authors, array($viewer_phid))
+ $authors;
$voters = array();
foreach ($authors as $author_phid) {
$handle = $handles[$author_phid];
$voters[] = javelin_tag(
'div',
array(
'class' => 'slowvote-voter',
'style' => 'background-image: url('.$handle->getImageURI().')',
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $handle->getName(),
),
));
}
return phutil_tag(
'div',
array(
'class' => 'slowvote-voters',
),
$voters);
}
private function renderStatus(PhabricatorSlowvoteOption $option) {
if (!$this->areResultsVisible()) {
return null;
}
$poll = $this->getPoll();
$choices = mgroup($poll->getChoices(), 'getOptionID');
$choices = count(idx($choices, $option->getID(), array()));
$count = count(mgroup($poll->getChoices(), 'getAuthorPHID'));
$percent = sprintf('%d%%', $count ? 100 * $choices / $count : 0);
- switch ($poll->getMethod()) {
- case PhabricatorSlowvotePoll::METHOD_PLURALITY:
+ $method = $poll->getMethod();
+ switch ($method) {
+ case SlowvotePollVotingMethod::METHOD_PLURALITY:
$status = pht('%s (%d / %d)', $percent, $choices, $count);
break;
- case PhabricatorSlowvotePoll::METHOD_APPROVAL:
+ case SlowvotePollVotingMethod::METHOD_APPROVAL:
$status = pht('%s Approval (%d / %d)', $percent, $choices, $count);
break;
+ default:
+ $status = pht('Unknown ("%s")', $method);
+ break;
}
return phutil_tag(
'div',
array(
'class' => 'slowvote-status',
),
$status);
}
private function areResultsVisible() {
$poll = $this->getPoll();
- $vis = $poll->getResponseVisibility();
- if ($vis == PhabricatorSlowvotePoll::RESPONSES_VISIBLE) {
+ $visibility = $poll->getResponseVisibility();
+ if ($visibility == SlowvotePollResponseVisibility::RESPONSES_VISIBLE) {
return true;
- } else if ($vis == PhabricatorSlowvotePoll::RESPONSES_OWNER) {
- return ($poll->getAuthorPHID() == $this->getUser()->getPHID());
- } else {
- $choices = mgroup($poll->getChoices(), 'getAuthorPHID');
- return (bool)idx($choices, $this->getUser()->getPHID());
}
+
+ $viewer = $this->getViewer();
+
+ if ($visibility == SlowvotePollResponseVisibility::RESPONSES_OWNER) {
+ return ($poll->getAuthorPHID() === $viewer->getPHID());
+ }
+
+ $choices = mgroup($poll->getChoices(), 'getAuthorPHID');
+ return (bool)idx($choices, $viewer->getPHID());
}
}
diff --git a/src/applications/slowvote/xaction/PhabricatorSlowvoteCloseTransaction.php b/src/applications/slowvote/xaction/PhabricatorSlowvoteCloseTransaction.php
deleted file mode 100644
index ce3eaf5879..0000000000
--- a/src/applications/slowvote/xaction/PhabricatorSlowvoteCloseTransaction.php
+++ /dev/null
@@ -1,60 +0,0 @@
-<?php
-
-final class PhabricatorSlowvoteCloseTransaction
- extends PhabricatorSlowvoteTransactionType {
-
- const TRANSACTIONTYPE = 'vote:close';
-
- public function generateOldValue($object) {
- return (bool)$object->getIsClosed();
- }
-
- public function generateNewValue($object, $value) {
- return (bool)$value;
- }
-
- public function applyInternalEffects($object, $value) {
- $object->setIsClosed((int)$value);
- }
-
- public function getTitle() {
- $new = $this->getNewValue();
-
- if ($new) {
- return pht(
- '%s closed this poll.',
- $this->renderAuthor());
- } else {
- return pht(
- '%s reopened this poll.',
- $this->renderAuthor());
- }
- }
-
- public function getTitleForFeed() {
- $new = $this->getNewValue();
-
- if ($new) {
- return pht(
- '%s closed %s.',
- $this->renderAuthor(),
- $this->renderObject());
- } else {
- return pht(
- '%s reopened %s.',
- $this->renderAuthor(),
- $this->renderObject());
- }
- }
-
- public function getIcon() {
- $new = $this->getNewValue();
-
- if ($new) {
- return 'fa-ban';
- } else {
- return 'fa-pencil';
- }
- }
-
-}
diff --git a/src/applications/slowvote/xaction/PhabricatorSlowvoteResponsesTransaction.php b/src/applications/slowvote/xaction/PhabricatorSlowvoteResponsesTransaction.php
index 93035cbd6a..ad18d1055e 100644
--- a/src/applications/slowvote/xaction/PhabricatorSlowvoteResponsesTransaction.php
+++ b/src/applications/slowvote/xaction/PhabricatorSlowvoteResponsesTransaction.php
@@ -1,31 +1,55 @@
<?php
final class PhabricatorSlowvoteResponsesTransaction
extends PhabricatorSlowvoteTransactionType {
const TRANSACTIONTYPE = 'vote:responses';
public function generateOldValue($object) {
- return (int)$object->getResponseVisibility();
+ return (string)$object->getResponseVisibility();
+ }
+
+ public function generateNewValue($object, $value) {
+ return (string)$value;
}
public function applyInternalEffects($object, $value) {
$object->setResponseVisibility($value);
}
public function getTitle() {
- // TODO: This could be more detailed
+ $old_name = $this->getOldResponseVisibilityObject()->getName();
+ $new_name = $this->getNewResponseVisibilityObject()->getName();
+
return pht(
- '%s changed who can see the responses.',
- $this->renderAuthor());
+ '%s changed who can see the responses from %s to %s.',
+ $this->renderAuthor(),
+ $this->renderValue($old_name),
+ $this->renderValue($new_name));
}
public function getTitleForFeed() {
- // TODO: This could be more detailed
+ $old_name = $this->getOldResponseVisibilityObject()->getName();
+ $new_name = $this->getNewResponseVisibilityObject()->getName();
+
return pht(
- '%s changed who can see the responses of %s.',
+ '%s changed who can see the responses of %s from %s to %s.',
$this->renderAuthor(),
- $this->renderObject());
+ $this->renderObject(),
+ $this->renderValue($old_name),
+ $this->renderValue($new_name));
+ }
+
+ private function getOldResponseVisibilityObject() {
+ return $this->newResponseVisibilityObject($this->getOldValue());
+ }
+
+ private function getNewResponseVisibilityObject() {
+ return $this->newResponseVisibilityObject($this->getNewValue());
+ }
+
+ private function newResponseVisibilityObject($value) {
+ return SlowvotePollResponseVisibility::newResponseVisibilityObject($value);
}
}
diff --git a/src/applications/slowvote/xaction/PhabricatorSlowvoteStatusTransaction.php b/src/applications/slowvote/xaction/PhabricatorSlowvoteStatusTransaction.php
new file mode 100644
index 0000000000..033d2ba2ab
--- /dev/null
+++ b/src/applications/slowvote/xaction/PhabricatorSlowvoteStatusTransaction.php
@@ -0,0 +1,60 @@
+<?php
+
+final class PhabricatorSlowvoteStatusTransaction
+ extends PhabricatorSlowvoteTransactionType {
+
+ const TRANSACTIONTYPE = 'vote:status';
+
+ public function generateOldValue($object) {
+ return (string)$object->getStatus();
+ }
+
+ public function generateNewValue($object, $value) {
+ return (string)$value;
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $object->setStatus($value);
+ }
+
+ public function getTitle() {
+ $old_name = $this->getOldStatusObject()->getName();
+ $new_name = $this->getNewStatusObject()->getName();
+
+ return pht(
+ '%s changed the status of this poll from %s to %s.',
+ $this->renderAuthor(),
+ $this->renderValue($old_name),
+ $this->renderValue($new_name));
+ }
+
+ public function getTitleForFeed() {
+ $old_name = $this->getOldStatusObject()->getName();
+ $new_name = $this->getNewStatusObject()->getName();
+
+
+ return pht(
+ '%s changed the status of %s from %s to %s.',
+ $this->renderAuthor(),
+ $this->renderObject(),
+ $this->renderValue($old_name),
+ $this->renderValue($new_name));
+ }
+
+ public function getIcon() {
+ return $this->getNewStatusObject()->getTransactionIcon();
+ }
+
+ private function getOldStatusObject() {
+ return $this->newStatusObject($this->getOldValue());
+ }
+
+ private function getNewStatusObject() {
+ return $this->newStatusObject($this->getNewValue());
+ }
+
+ private function newStatusObject($value) {
+ return SlowvotePollStatus::newStatusObject($value);
+ }
+
+}
diff --git a/src/applications/slowvote/xaction/PhabricatorSlowvoteVotingMethodTransaction.php b/src/applications/slowvote/xaction/PhabricatorSlowvoteVotingMethodTransaction.php
new file mode 100644
index 0000000000..ac8eaa15dc
--- /dev/null
+++ b/src/applications/slowvote/xaction/PhabricatorSlowvoteVotingMethodTransaction.php
@@ -0,0 +1,55 @@
+<?php
+
+final class PhabricatorSlowvoteVotingMethodTransaction
+ extends PhabricatorSlowvoteTransactionType {
+
+ const TRANSACTIONTYPE = 'vote:method';
+
+ public function generateOldValue($object) {
+ return (string)$object->getMethod();
+ }
+
+ public function generateNewValue($object, $value) {
+ return (string)$value;
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $object->setMethod($value);
+ }
+
+ public function getTitle() {
+ $old_name = $this->getOldVotingMethodObject()->getName();
+ $new_name = $this->getNewVotingMethodObject()->getName();
+
+ return pht(
+ '%s changed the voting method from %s to %s.',
+ $this->renderAuthor(),
+ $this->renderValue($old_name),
+ $this->renderValue($new_name));
+ }
+
+ public function getTitleForFeed() {
+ $old_name = $this->getOldVotingMethodObject()->getName();
+ $new_name = $this->getNewVotingMethodObject()->getName();
+
+ return pht(
+ '%s changed the voting method of %s from %s to %s.',
+ $this->renderAuthor(),
+ $this->renderObject(),
+ $this->renderValue($old_name),
+ $this->renderValue($new_name));
+ }
+
+ private function getOldVotingMethodObject() {
+ return $this->newVotingMethodObject($this->getOldValue());
+ }
+
+ private function getNewVotingMethodObject() {
+ return $this->newVotingMethodObject($this->getNewValue());
+ }
+
+ private function newVotingMethodObject($value) {
+ return SlowvotePollVotingMethod::newVotingMethodObject($value);
+ }
+
+}
diff --git a/src/applications/spaces/query/PhabricatorSpacesNamespaceQuery.php b/src/applications/spaces/query/PhabricatorSpacesNamespaceQuery.php
index ee11dcdd06..388b6ab4d8 100644
--- a/src/applications/spaces/query/PhabricatorSpacesNamespaceQuery.php
+++ b/src/applications/spaces/query/PhabricatorSpacesNamespaceQuery.php
@@ -1,238 +1,238 @@
<?php
final class PhabricatorSpacesNamespaceQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
const KEY_ALL = 'spaces.all';
const KEY_DEFAULT = 'spaces.default';
const KEY_VIEWER = 'spaces.viewer';
private $ids;
private $phids;
private $isDefaultNamespace;
private $isArchived;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withIsDefaultNamespace($default) {
$this->isDefaultNamespace = $default;
return $this;
}
public function withIsArchived($archived) {
$this->isArchived = $archived;
return $this;
}
public function getQueryApplicationClass() {
return 'PhabricatorSpacesApplication';
}
- protected function loadPage() {
- return $this->loadStandardPage(new PhabricatorSpacesNamespace());
+ public function newResultObject() {
+ return new PhabricatorSpacesNamespace();
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->isDefaultNamespace !== null) {
if ($this->isDefaultNamespace) {
$where[] = qsprintf(
$conn,
'isDefaultNamespace = 1');
} else {
$where[] = qsprintf(
$conn,
'isDefaultNamespace IS NULL');
}
}
if ($this->isArchived !== null) {
$where[] = qsprintf(
$conn,
'isArchived = %d',
(int)$this->isArchived);
}
return $where;
}
public static function destroySpacesCache() {
$cache = PhabricatorCaches::getRequestCache();
$cache->deleteKeys(
array(
self::KEY_ALL,
self::KEY_DEFAULT,
));
}
public static function getSpacesExist() {
return (bool)self::getAllSpaces();
}
public static function getViewerSpacesExist(PhabricatorUser $viewer) {
if (!self::getSpacesExist()) {
return false;
}
// If the viewer has access to only one space, pretend spaces simply don't
// exist.
$spaces = self::getViewerSpaces($viewer);
return (count($spaces) > 1);
}
public static function getAllSpaces() {
$cache = PhabricatorCaches::getRequestCache();
$cache_key = self::KEY_ALL;
$spaces = $cache->getKey($cache_key);
if ($spaces === null) {
$spaces = id(new PhabricatorSpacesNamespaceQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->execute();
$spaces = mpull($spaces, null, 'getPHID');
$cache->setKey($cache_key, $spaces);
}
return $spaces;
}
public static function getDefaultSpace() {
$cache = PhabricatorCaches::getRequestCache();
$cache_key = self::KEY_DEFAULT;
$default_space = $cache->getKey($cache_key, false);
if ($default_space === false) {
$default_space = null;
$spaces = self::getAllSpaces();
foreach ($spaces as $space) {
if ($space->getIsDefaultNamespace()) {
$default_space = $space;
break;
}
}
$cache->setKey($cache_key, $default_space);
}
return $default_space;
}
public static function getViewerSpaces(PhabricatorUser $viewer) {
$cache = PhabricatorCaches::getRequestCache();
$cache_key = self::KEY_VIEWER.'('.$viewer->getCacheFragment().')';
$result = $cache->getKey($cache_key);
if ($result === null) {
$spaces = self::getAllSpaces();
$result = array();
foreach ($spaces as $key => $space) {
$can_see = PhabricatorPolicyFilter::hasCapability(
$viewer,
$space,
PhabricatorPolicyCapability::CAN_VIEW);
if ($can_see) {
$result[$key] = $space;
}
}
$cache->setKey($cache_key, $result);
}
return $result;
}
public static function getViewerActiveSpaces(PhabricatorUser $viewer) {
$spaces = self::getViewerSpaces($viewer);
foreach ($spaces as $key => $space) {
if ($space->getIsArchived()) {
unset($spaces[$key]);
}
}
return $spaces;
}
public static function getSpaceOptionsForViewer(
PhabricatorUser $viewer,
$space_phid) {
$viewer_spaces = self::getViewerSpaces($viewer);
$viewer_spaces = msort($viewer_spaces, 'getNamespaceName');
$map = array();
foreach ($viewer_spaces as $space) {
// Skip archived spaces, unless the object is already in that space.
if ($space->getIsArchived()) {
if ($space->getPHID() != $space_phid) {
continue;
}
}
$map[$space->getPHID()] = pht(
'Space %s: %s',
$space->getMonogram(),
$space->getNamespaceName());
}
return $map;
}
/**
* Get the Space PHID for an object, if one exists.
*
* This is intended to simplify performing a bunch of redundant checks; you
* can intentionally pass any value in (including `null`).
*
* @param wild
* @return phid|null
*/
public static function getObjectSpacePHID($object) {
if (!$object) {
return null;
}
if (!($object instanceof PhabricatorSpacesInterface)) {
return null;
}
$space_phid = $object->getSpacePHID();
if ($space_phid === null) {
$default_space = self::getDefaultSpace();
if ($default_space) {
$space_phid = $default_space->getPHID();
}
}
return $space_phid;
}
}
diff --git a/src/applications/subscriptions/command/PhabricatorSubscriptionsSubscribeEmailCommand.php b/src/applications/subscriptions/command/PhabricatorSubscriptionsSubscribeEmailCommand.php
index 0c361ad2c6..226fba34e5 100644
--- a/src/applications/subscriptions/command/PhabricatorSubscriptionsSubscribeEmailCommand.php
+++ b/src/applications/subscriptions/command/PhabricatorSubscriptionsSubscribeEmailCommand.php
@@ -1,75 +1,74 @@
<?php
final class PhabricatorSubscriptionsSubscribeEmailCommand
extends MetaMTAEmailTransactionCommand {
public function getCommand() {
return 'subscribe';
}
public function getCommandSyntax() {
return '**!subscribe** //username #project ...//';
}
public function getCommandSummary() {
return pht('Add users or projects as subscribers.');
}
public function getCommandDescription() {
return pht(
'Add one or more subscribers to the object. You can add users by '.
'providing their usernames, or add projects by adding their hashtags. '.
'For example, use `%s` to add the user `alincoln` and the project with '.
'hashtag `#ios` as subscribers.'.
"\n\n".
'Subscribers which are invalid or unrecognized will be ignored. This '.
'command has no effect if you do not specify any subscribers.'.
"\n\n".
'Users who are CC\'d on the email itself are also automatically '.
- 'subscribed if Phabricator knows which accounts are linked to their '.
- 'email addresses.',
+ 'subscribed if their addresses are associated with a known account.',
'!subscribe alincoln #ios');
}
public function getCommandAliases() {
return array(
'cc',
);
}
public function isCommandSupportedForObject(
PhabricatorApplicationTransactionInterface $object) {
return ($object instanceof PhabricatorSubscribableInterface);
}
public function buildTransactions(
PhabricatorUser $viewer,
PhabricatorApplicationTransactionInterface $object,
PhabricatorMetaMTAReceivedMail $mail,
$command,
array $argv) {
$subscriber_phids = id(new PhabricatorObjectListQuery())
->setViewer($viewer)
->setAllowedTypes(
array(
PhabricatorPeopleUserPHIDType::TYPECONST,
PhabricatorProjectProjectPHIDType::TYPECONST,
))
->setObjectList(implode(' ', $argv))
->setAllowPartialResults(true)
->execute();
$xactions = array();
$xactions[] = $object->getApplicationTransactionTemplate()
->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)
->setNewValue(
array(
'+' => array_fuse($subscriber_phids),
));
return $xactions;
}
}
diff --git a/src/applications/system/controller/PhabricatorSystemReadOnlyController.php b/src/applications/system/controller/PhabricatorSystemReadOnlyController.php
index 7541e9adf4..e83fe67b0e 100644
--- a/src/applications/system/controller/PhabricatorSystemReadOnlyController.php
+++ b/src/applications/system/controller/PhabricatorSystemReadOnlyController.php
@@ -1,134 +1,135 @@
<?php
final class PhabricatorSystemReadOnlyController
extends PhabricatorController {
public function shouldRequireLogin() {
return false;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$reason = $request->getURIData('reason');
$body = array();
switch ($reason) {
case PhabricatorEnv::READONLY_CONFIG:
$title = pht('Administrative Read-Only Mode');
$body[] = pht(
- 'An administrator has placed Phabricator into read-only mode.');
+ 'An administrator has placed this server into read-only mode.');
$body[] = pht(
'This mode may be used to perform temporary maintenance, test '.
'configuration, or archive an installation permanently.');
$body[] = pht(
'Read-only mode was enabled by the explicit action of a human '.
'administrator, so you can get more information about why it '.
'has been turned on by rolling your chair away from your desk and '.
- 'yelling "Hey! Why is Phabricator in read-only mode??!" using '.
- 'your very loudest outside voice.');
+ 'yelling "Hey! Why is %s in read-only mode??!" using '.
+ 'your very loudest outside voice.',
+ PlatformSymbols::getPlatformServerSymbol());
$body[] = pht(
'This mode is active because it is enabled in the configuration '.
'option "%s".',
phutil_tag('tt', array(), 'cluster.read-only'));
$button = pht('Wait Patiently');
break;
case PhabricatorEnv::READONLY_MASTERLESS:
$title = pht('No Writable Database');
$body[] = pht(
- 'Phabricator is currently configured with no writable ("master") '.
+ 'This server is currently configured with no writable ("master") '.
'database, so it can not write new information anywhere. '.
- 'Phabricator will run in read-only mode until an administrator '.
+ 'This server will run in read-only mode until an administrator '.
'reconfigures it with a writable database.');
$body[] = pht(
'This usually occurs when an administrator is actively working on '.
'fixing a temporary configuration or deployment problem.');
$body[] = pht(
'This mode is active because no database has a "%s" role in '.
'the configuration option "%s".',
phutil_tag('tt', array(), 'master'),
phutil_tag('tt', array(), 'cluster.databases'));
$button = pht('Wait Patiently');
break;
case PhabricatorEnv::READONLY_UNREACHABLE:
$title = pht('Unable to Reach Master');
$body[] = pht(
- 'Phabricator was unable to connect to the writable ("master") '.
+ 'This server was unable to connect to the writable ("master") '.
'database while handling this request, and automatically degraded '.
'into read-only mode.');
$body[] = pht(
'This may happen if there is a temporary network anomaly on the '.
'server side, like cosmic radiation or spooky ghosts. If this '.
'failure was caused by a transient service interruption, '.
- 'Phabricator will recover momentarily.');
+ 'this server will recover momentarily.');
$body[] = pht(
'This may also indicate that a more serious failure has occurred. '.
- 'If this interruption does not resolve on its own, Phabricator '.
+ 'If this interruption does not resolve on its own, this server '.
'will soon detect the persistent disruption and degrade into '.
'read-only mode until the issue is resolved.');
$button = pht('Quite Unsettling');
break;
case PhabricatorEnv::READONLY_SEVERED:
$title = pht('Severed From Master');
$body[] = pht(
- 'Phabricator has consistently been unable to reach the writable '.
+ 'This server has consistently been unable to reach the writable '.
'("master") database while processing recent requests.');
$body[] = pht(
'This likely indicates a severe misconfiguration or major service '.
'interruption.');
$body[] = pht(
- 'Phabricator will periodically retry the connection and recover '.
+ 'This server will periodically retry the connection and recover '.
'once service is restored. Most causes of persistent service '.
'interruption will require administrative intervention in order '.
'to restore service.');
$body[] = pht(
'Although this may be the result of a misconfiguration or '.
'operational error, this is also the state you reach if a '.
'meteor recently obliterated a datacenter.');
$button = pht('Panic!');
break;
default:
return new Aphront404Response();
}
switch ($reason) {
case PhabricatorEnv::READONLY_UNREACHABLE:
case PhabricatorEnv::READONLY_SEVERED:
$body[] = pht(
'This request was served from a replica database. Replica '.
'databases may lag behind the master, so very recent activity '.
'may not be reflected in the UI. This data will be restored if '.
'the master database is restored, but may have been lost if the '.
'master database has been reduced to a pile of ash.');
break;
}
$body[] = pht(
'In read-only mode you can read existing information, but you will not '.
'be able to edit objects or create new objects until this mode is '.
'disabled.');
if ($viewer->getIsAdmin()) {
$body[] = pht(
'As an administrator, you can review status information from the '.
'%s control panel. This may provide more information about the '.
'current state of affairs.',
phutil_tag(
'a',
array(
'href' => '/config/cluster/databases/',
),
pht('Cluster Database Status')));
}
$dialog = $this->newDialog()
->setTitle($title)
->setWidth(AphrontDialogView::WIDTH_FORM)
->addCancelButton('/', $button);
foreach ($body as $paragraph) {
$dialog->appendParagraph($paragraph);
}
return $dialog;
}
}
diff --git a/src/applications/system/management/PhabricatorSystemRemoveDestroyWorkflow.php b/src/applications/system/management/PhabricatorSystemRemoveDestroyWorkflow.php
index bd6b4e361a..b66ad82b80 100644
--- a/src/applications/system/management/PhabricatorSystemRemoveDestroyWorkflow.php
+++ b/src/applications/system/management/PhabricatorSystemRemoveDestroyWorkflow.php
@@ -1,181 +1,180 @@
<?php
final class PhabricatorSystemRemoveDestroyWorkflow
extends PhabricatorSystemRemoveWorkflow {
protected function didConstruct() {
$this
->setName('destroy')
->setSynopsis(pht('Permanently destroy objects.'))
->setExamples('**destroy** [__options__] __object__ ...')
->setArguments(
array(
array(
'name' => 'force',
'help' => pht('Destroy objects without prompting.'),
),
array(
'name' => 'objects',
'wildcard' => true,
),
));
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$object_names = $args->getArg('objects');
if (!$object_names) {
throw new PhutilArgumentUsageException(
pht('Specify one or more objects to destroy.'));
}
$object_query = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withNames($object_names);
$object_query->execute();
$named_objects = $object_query->getNamedResults();
foreach ($object_names as $object_name) {
if (empty($named_objects[$object_name])) {
throw new PhutilArgumentUsageException(
pht('No such object "%s" exists!', $object_name));
}
}
foreach ($named_objects as $object_name => $object) {
if (!($object instanceof PhabricatorDestructibleInterface)) {
throw new PhutilArgumentUsageException(
pht(
'Object "%s" can not be destroyed (it does not implement %s).',
$object_name,
'PhabricatorDestructibleInterface'));
}
}
$banner = <<<EOBANNER
uuuuuuu
uu###########uu
uu#################uu
u#####################u
u#######################u
u#########################u
u#########################u
u######" "###" "######u
"####" u#u ####"
###u u#u u###
###u u###u u###
"####uu### ###uu####"
"#######" "#######"
u#######u#######u
u#"#"#"#"#"#"#u
uuu ##u# # # # #u## uuu
u#### #####u#u#u### u####
#####uu "#########" uu######
u###########uu """"" uuuu##########
####"""##########uuu uu#########"""###"
""" ""###########uu ""#"""
uuuu ""##########uuu
u###uuu#########uu ""###########uuu###
##########"""" ""###########"
"#####" ""####""
###" ####"
EOBANNER;
$console->writeOut("\n\n<fg:red>%s</fg>\n\n", $banner);
$console->writeOut(
"<bg:red>** %s **</bg> %s\n\n%s\n\n".
"<bg:red>** %s **</bg> %s\n\n%s\n\n",
pht('IMPORTANT'),
pht('DATA WILL BE PERMANENTLY DESTROYED'),
phutil_console_wrap(
pht(
'Objects will be permanently destroyed. There is no way to '.
'undo this operation or ever retrieve this data unless you '.
'maintain external backups.')),
pht('IMPORTANT'),
pht('DELETING OBJECTS OFTEN BREAKS THINGS'),
phutil_console_wrap(
pht(
'Destroying objects may cause related objects to stop working, '.
'and may leave scattered references to objects which no longer '.
'exist. In most cases, it is much better to disable or archive '.
'objects instead of destroying them. This risk is greatest when '.
'deleting complex or highly connected objects like repositories, '.
'projects and users.'.
"\n\n".
'These tattered edges are an expected consequence of destroying '.
- 'objects, and the Phabricator upstream will not help you fix '.
- 'them. We strongly recommend disabling or archiving objects '.
- 'instead.')));
+ 'objects, and the upstream will not help you fix them. We '.
+ 'strongly recommend disabling or archiving objects instead.')));
$phids = mpull($named_objects, 'getPHID');
$handles = PhabricatorUser::getOmnipotentUser()->loadHandles($phids);
$console->writeOut(
pht(
'These %s object(s) will be destroyed forever:',
phutil_count($named_objects))."\n\n");
foreach ($named_objects as $object_name => $object) {
$phid = $object->getPHID();
$console->writeOut(
" - %s (%s) %s\n",
$object_name,
get_class($object),
$handles[$phid]->getFullName());
}
$force = $args->getArg('force');
if (!$force) {
$ok = $console->confirm(
pht(
'Are you absolutely certain you want to destroy these %s object(s)?',
phutil_count($named_objects)));
if (!$ok) {
throw new PhutilArgumentUsageException(
pht('Aborted, your objects are safe.'));
}
}
$console->writeOut("%s\n", pht('Destroying objects...'));
$notes = array();
foreach ($named_objects as $object_name => $object) {
$console->writeOut(
pht(
"Destroying %s **%s**...\n",
get_class($object),
$object_name));
$engine = id(new PhabricatorDestructionEngine())
->setCollectNotes(true);
$engine->destroyObject($object);
foreach ($engine->getNotes() as $note) {
$notes[] = $note;
}
}
$console->writeOut(
"%s\n",
pht(
'Permanently destroyed %s object(s).',
phutil_count($named_objects)));
if ($notes) {
id(new PhutilConsoleList())
->addItems($notes)
->draw();
}
return 0;
}
}
diff --git a/src/applications/tokens/query/PhabricatorTokenGivenQuery.php b/src/applications/tokens/query/PhabricatorTokenGivenQuery.php
index b4a86428c1..0e6ad9eb54 100644
--- a/src/applications/tokens/query/PhabricatorTokenGivenQuery.php
+++ b/src/applications/tokens/query/PhabricatorTokenGivenQuery.php
@@ -1,117 +1,113 @@
<?php
final class PhabricatorTokenGivenQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $authorPHIDs;
private $objectPHIDs;
private $tokenPHIDs;
public function withTokenPHIDs(array $token_phids) {
$this->tokenPHIDs = $token_phids;
return $this;
}
public function withObjectPHIDs(array $object_phids) {
$this->objectPHIDs = $object_phids;
return $this;
}
public function withAuthorPHIDs(array $author_phids) {
$this->authorPHIDs = $author_phids;
return $this;
}
public function newResultObject() {
return new PhabricatorTokenGiven();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'authorPHID IN (%Ls)',
$this->authorPHIDs);
}
if ($this->objectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'objectPHID IN (%Ls)',
$this->objectPHIDs);
}
if ($this->tokenPHIDs !== null) {
$where[] = qsprintf(
$conn,
'tokenPHID IN (%Ls)',
$this->tokenPHIDs);
}
return $where;
}
protected function willFilterPage(array $results) {
$viewer = $this->getViewer();
$object_phids = mpull($results, 'getObjectPHID');
$objects = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs($object_phids)
->execute();
$objects = mpull($objects, null, 'getPHID');
foreach ($results as $key => $result) {
$object = idx($objects, $result->getObjectPHID());
if ($object) {
if ($object instanceof PhabricatorTokenReceiverInterface) {
$result->attachObject($object);
continue;
}
}
$this->didRejectResult($result);
unset($results[$key]);
}
if (!$results) {
return $results;
}
$token_phids = mpull($results, 'getTokenPHID');
$tokens = id(new PhabricatorTokenQuery())
->setViewer($viewer)
->withPHIDs($token_phids)
->execute();
$tokens = mpull($tokens, null, 'getPHID');
foreach ($results as $key => $result) {
$token_phid = $result->getTokenPHID();
$token = idx($tokens, $token_phid);
if (!$token) {
$this->didRejectResult($result);
unset($results[$key]);
continue;
}
$result->attachToken($token);
}
return $results;
}
public function getQueryApplicationClass() {
return 'PhabricatorTokensApplication';
}
}
diff --git a/src/applications/transactions/conduit/TransactionSearchConduitAPIMethod.php b/src/applications/transactions/conduit/TransactionSearchConduitAPIMethod.php
index 422d570bd0..82eaf08e83 100644
--- a/src/applications/transactions/conduit/TransactionSearchConduitAPIMethod.php
+++ b/src/applications/transactions/conduit/TransactionSearchConduitAPIMethod.php
@@ -1,417 +1,417 @@
<?php
final class TransactionSearchConduitAPIMethod
extends ConduitAPIMethod {
public function getAPIMethodName() {
return 'transaction.search';
}
public function getMethodDescription() {
return pht(
'Read transactions and comments for a particular object '.
'or an entire object type.');
}
protected function newDocumentationPages(PhabricatorUser $viewer) {
$markup = pht(<<<EOREMARKUP
-When an object (like a task) is edited, Phabricator creates a "transaction"
-and applies it. This list of transactions on each object is the basis for
-essentially all edits and comments in Phabricator. Reviewing the transaction
+When an object (like a task) is edited, the relevant application creates a
+"transaction" and applies it. This list of transactions on each object is the
+basis for essentially all edits and comments. Reviewing the transaction
record allows you to see who edited an object, when, and how their edit changed
things.
One common reason to call this method is that you're implmenting a webhook and
just received a notification that an object has changed. See the Webhooks
documentation for more detailed discussion of this use case.
One Object Type at a Time
=========================
This API method can query transactions for any type of object which supports
transactions, but only one type of object can be queried per call. For example:
you can retrieve transactions affecting Tasks, or you can retrieve transactions
affecting Revisions, but a single call can not retrieve both.
This is a technical limitation arising because (among other reasons) there is
no global ordering on transactions.
To find transactions for a specific object (like a particular task), pass the
object PHID or an appropriate object identifier (like `T123`) as an
`objectIdentifier`.
To find all transactions for an object type, pass the object type constant as
an `objectType`. For example, the correct identifier for tasks is `TASK`. (You
can quickly find an unknown type constant by looking at the PHID of an object
of that type.)
Constraints
===========
These constraints are supported:
- `phids` //Optional list<phid>.// Find specific transactions by PHID. This
is most likely to be useful if you're responding to a webhook notification
and want to inspect only the related events.
- `authorPHIDs` //Optional list<phid>.// Find transactions with particular
authors.
Transaction Format
==================
Each transaction has custom data describing what the transaction did. The
format varies from transaction to transaction. The easiest way to figure out
exactly what a particular transaction looks like is to make the associated kind
of edit to a test object, then query that object.
Not all transactions have data: by default, transactions have a `null` "type"
and no additional data. This API does not expose raw transaction data because
some of it is internal, oddly named, misspelled, confusing, not useful, or
could create security or policy problems to expose directly.
New transactions are exposed (with correctly spelled, comprehensible types and
useful, reasonable fields) as we become aware of use cases for them.
EOREMARKUP
);
$markup = $this->newRemarkupDocumentationView($markup);
return array(
$this->newDocumentationBoxPage($viewer, pht('Method Details'), $markup)
->setAnchor('details'),
);
}
protected function defineParamTypes() {
return array(
'objectIdentifier' => 'optional phid|string',
'objectType' => 'optional string',
'constraints' => 'optional map<string, wild>',
) + $this->getPagerParamTypes();
}
protected function defineReturnType() {
return 'list<dict>';
}
protected function defineErrorTypes() {
return array();
}
protected function execute(ConduitAPIRequest $request) {
$viewer = $request->getUser();
$pager = $this->newPager($request);
$object = $this->loadTemplateObject($request);
$xaction_query = PhabricatorApplicationTransactionQuery::newQueryForObject(
$object);
$xaction_query
->needHandles(false)
->setViewer($viewer);
if ($object->getPHID()) {
$xaction_query->withObjectPHIDs(array($object->getPHID()));
}
$constraints = $request->getValue('constraints', array());
$xaction_query = $this->applyConstraints($constraints, $xaction_query);
$xactions = $xaction_query->executeWithCursorPager($pager);
$comment_map = array();
if ($xactions) {
$template = head($xactions)->getApplicationTransactionCommentObject();
if ($template) {
$query = new PhabricatorApplicationTransactionTemplatedCommentQuery();
$comment_map = $query
->setViewer($viewer)
->setTemplate($template)
->withTransactionPHIDs(mpull($xactions, 'getPHID'))
->execute();
$comment_map = msort($comment_map, 'getCommentVersion');
$comment_map = array_reverse($comment_map);
$comment_map = mgroup($comment_map, 'getTransactionPHID');
}
}
$modular_classes = array();
$modular_objects = array();
$modular_xactions = array();
foreach ($xactions as $xaction) {
if (!$xaction instanceof PhabricatorModularTransaction) {
continue;
}
// TODO: Hack things so certain transactions which don't have a modular
// type yet can use a pseudotype until they modularize. Some day, we'll
// modularize everything and remove this.
switch ($xaction->getTransactionType()) {
case DifferentialTransaction::TYPE_INLINE:
$modular_template = new DifferentialRevisionInlineTransaction();
break;
default:
$modular_template = $xaction->getModularType();
break;
}
$modular_class = get_class($modular_template);
if (!isset($modular_objects[$modular_class])) {
try {
$modular_object = newv($modular_class, array());
$modular_objects[$modular_class] = $modular_object;
} catch (Exception $ex) {
continue;
}
}
$modular_classes[$xaction->getPHID()] = $modular_class;
$modular_xactions[$modular_class][] = $xaction;
}
$modular_data_map = array();
foreach ($modular_objects as $class => $modular_type) {
$modular_data_map[$class] = $modular_type
->setViewer($viewer)
->loadTransactionTypeConduitData($modular_xactions[$class]);
}
$data = array();
foreach ($xactions as $xaction) {
$comments = idx($comment_map, $xaction->getPHID());
$comment_data = array();
if ($comments) {
$removed = head($comments)->getIsDeleted();
foreach ($comments as $comment) {
if ($removed) {
// If the most recent version of the comment has been removed,
// don't show the history. This is for consistency with the web
// UI, which also prevents users from retrieving the content of
// removed comments.
$content = array(
'raw' => '',
);
} else {
$content = array(
'raw' => (string)$comment->getContent(),
);
}
$comment_data[] = array(
'id' => (int)$comment->getID(),
'phid' => (string)$comment->getPHID(),
'version' => (int)$comment->getCommentVersion(),
'authorPHID' => (string)$comment->getAuthorPHID(),
'dateCreated' => (int)$comment->getDateCreated(),
'dateModified' => (int)$comment->getDateModified(),
'removed' => (bool)$comment->getIsDeleted(),
'content' => $content,
);
}
}
$fields = array();
$type = null;
if (isset($modular_classes[$xaction->getPHID()])) {
$modular_class = $modular_classes[$xaction->getPHID()];
$modular_object = $modular_objects[$modular_class];
$modular_data = $modular_data_map[$modular_class];
$type = $modular_object->getTransactionTypeForConduit($xaction);
$fields = $modular_object->getFieldValuesForConduit(
$xaction,
$modular_data);
}
if (!$fields) {
$fields = (object)$fields;
}
// If we haven't found a modular type, fallback for some simple core
// types. Ideally, we'll modularize everything some day.
if ($type === null) {
switch ($xaction->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$type = 'comment';
break;
case PhabricatorTransactions::TYPE_CREATE:
$type = 'create';
break;
case PhabricatorTransactions::TYPE_EDGE:
switch ($xaction->getMetadataValue('edge:type')) {
case PhabricatorProjectObjectHasProjectEdgeType::EDGECONST:
$type = 'projects';
$fields = $this->newEdgeTransactionFields($xaction);
break;
}
break;
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
$type = 'subscribers';
$fields = $this->newEdgeTransactionFields($xaction);
break;
}
}
$group_id = $xaction->getTransactionGroupID();
if (!strlen($group_id)) {
$group_id = null;
} else {
$group_id = (string)$group_id;
}
$data[] = array(
'id' => (int)$xaction->getID(),
'phid' => (string)$xaction->getPHID(),
'type' => $type,
'authorPHID' => (string)$xaction->getAuthorPHID(),
'objectPHID' => (string)$xaction->getObjectPHID(),
'dateCreated' => (int)$xaction->getDateCreated(),
'dateModified' => (int)$xaction->getDateModified(),
'groupID' => $group_id,
'comments' => $comment_data,
'fields' => $fields,
);
}
$results = array(
'data' => $data,
);
return $this->addPagerResults($results, $pager);
}
private function applyConstraints(
array $constraints,
PhabricatorApplicationTransactionQuery $query) {
PhutilTypeSpec::checkMap(
$constraints,
array(
'phids' => 'optional list<string>',
'authorPHIDs' => 'optional list<string>',
));
$with_phids = idx($constraints, 'phids');
if ($with_phids === array()) {
throw new Exception(
pht(
'Constraint "phids" to "transaction.search" requires nonempty list, '.
'empty list provided.'));
}
if ($with_phids) {
$query->withPHIDs($with_phids);
}
$with_authors = idx($constraints, 'authorPHIDs');
if ($with_authors === array()) {
throw new Exception(
pht(
'Constraint "authorPHIDs" to "transaction.search" requires '.
'nonempty list, empty list provided.'));
}
if ($with_authors) {
$query->withAuthorPHIDs($with_authors);
}
return $query;
}
private function newEdgeTransactionFields(
PhabricatorApplicationTransaction $xaction) {
$record = PhabricatorEdgeChangeRecord::newFromTransaction($xaction);
$operations = array();
foreach ($record->getAddedPHIDs() as $phid) {
$operations[] = array(
'operation' => 'add',
'phid' => $phid,
);
}
foreach ($record->getRemovedPHIDs() as $phid) {
$operations[] = array(
'operation' => 'remove',
'phid' => $phid,
);
}
return array(
'operations' => $operations,
);
}
private function loadTemplateObject(ConduitAPIRequest $request) {
$viewer = $request->getUser();
$object_identifier = $request->getValue('objectIdentifier');
$object_type = $request->getValue('objectType');
$has_identifier = ($object_identifier !== null);
$has_type = ($object_type !== null);
if (!$has_type && !$has_identifier) {
throw new Exception(
pht(
'Calls to "transaction.search" must specify either an "objectType" '.
'or an "objectIdentifier"'));
} else if ($has_type && $has_identifier) {
throw new Exception(
pht(
'Calls to "transaction.search" must not specify both an '.
'"objectType" and an "objectIdentifier".'));
}
if ($has_type) {
$all_types = PhabricatorPHIDType::getAllTypes();
if (!isset($all_types[$object_type])) {
ksort($all_types);
throw new Exception(
pht(
'In call to "transaction.search", specified "objectType" ("%s") '.
'is unknown. Valid object types are: %s.',
$object_type,
implode(', ', array_keys($all_types))));
}
$object = $all_types[$object_type]->newObject();
} else {
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withNames(array($object_identifier))
->executeOne();
if (!$object) {
throw new Exception(
pht(
'In call to "transaction.search", specified "objectIdentifier" '.
'("%s") does not exist.',
$object_identifier));
}
}
if (!($object instanceof PhabricatorApplicationTransactionInterface)) {
throw new Exception(
pht(
'In call to "transaction.search", selected object (of type "%s") '.
'does not implement "%s", so transactions can not be loaded for it.',
get_class($object),
'PhabricatorApplicationTransactionInterface'));
}
return $object;
}
}
diff --git a/src/applications/transactions/constants/PhabricatorTransactions.php b/src/applications/transactions/constants/PhabricatorTransactions.php
index db827c9091..3e31bdb1db 100644
--- a/src/applications/transactions/constants/PhabricatorTransactions.php
+++ b/src/applications/transactions/constants/PhabricatorTransactions.php
@@ -1,43 +1,44 @@
<?php
final class PhabricatorTransactions extends Phobject {
const TYPE_COMMENT = 'core:comment';
const TYPE_SUBSCRIBERS = 'core:subscribers';
const TYPE_VIEW_POLICY = 'core:view-policy';
const TYPE_EDIT_POLICY = 'core:edit-policy';
const TYPE_JOIN_POLICY = 'core:join-policy';
const TYPE_INTERACT_POLICY = 'core:interact-policy';
const TYPE_EDGE = 'core:edge';
const TYPE_CUSTOMFIELD = 'core:customfield';
const TYPE_TOKEN = 'token:give';
const TYPE_INLINESTATE = 'core:inlinestate';
const TYPE_SPACE = 'core:space';
const TYPE_CREATE = 'core:create';
const TYPE_COLUMNS = 'core:columns';
const TYPE_SUBTYPE = 'core:subtype';
const TYPE_HISTORY = 'core:history';
const TYPE_MFA = 'core:mfa';
+ const TYPE_FILE = 'core:file';
const COLOR_RED = 'red';
const COLOR_ORANGE = 'orange';
const COLOR_YELLOW = 'yellow';
const COLOR_GREEN = 'green';
const COLOR_SKY = 'sky';
const COLOR_BLUE = 'blue';
const COLOR_INDIGO = 'indigo';
const COLOR_VIOLET = 'violet';
const COLOR_GREY = 'grey';
const COLOR_BLACK = 'black';
public static function getInlineStateMap() {
return array(
PhabricatorInlineComment::STATE_DRAFT =>
PhabricatorInlineComment::STATE_DONE,
PhabricatorInlineComment::STATE_UNDRAFT =>
PhabricatorInlineComment::STATE_UNDONE,
);
}
}
diff --git a/src/applications/transactions/data/PhabricatorTransactionChange.php b/src/applications/transactions/data/PhabricatorTransactionChange.php
index 2fc59ce5e5..11aa5876a5 100644
--- a/src/applications/transactions/data/PhabricatorTransactionChange.php
+++ b/src/applications/transactions/data/PhabricatorTransactionChange.php
@@ -1,37 +1,47 @@
<?php
abstract class PhabricatorTransactionChange extends Phobject {
private $transaction;
+ private $metadata = array();
private $oldValue;
private $newValue;
final public function setTransaction(
PhabricatorApplicationTransaction $xaction) {
$this->transaction = $xaction;
return $this;
}
final public function getTransaction() {
return $this->transaction;
}
final public function setOldValue($old_value) {
$this->oldValue = $old_value;
return $this;
}
final public function getOldValue() {
return $this->oldValue;
}
final public function setNewValue($new_value) {
$this->newValue = $new_value;
return $this;
}
final public function getNewValue() {
return $this->newValue;
}
+ final public function setMetadata(array $metadata) {
+ $this->metadata = $metadata;
+ return $this;
+ }
+
+ final public function getMetadata() {
+ return $this->metadata;
+ }
+
}
diff --git a/src/applications/transactions/edges/PhabricatorObjectHasFileEdgeType.php b/src/applications/transactions/edges/PhabricatorObjectHasFileEdgeType.php
deleted file mode 100644
index 6ed89ff91b..0000000000
--- a/src/applications/transactions/edges/PhabricatorObjectHasFileEdgeType.php
+++ /dev/null
@@ -1,115 +0,0 @@
-<?php
-
-final class PhabricatorObjectHasFileEdgeType extends PhabricatorEdgeType {
-
- const EDGECONST = 25;
-
- public function getInverseEdgeConstant() {
- return PhabricatorFileHasObjectEdgeType::EDGECONST;
- }
-
- public function shouldWriteInverseTransactions() {
- return true;
- }
-
- public function getConduitKey() {
- return 'object.attached-files';
- }
-
- public function getConduitName() {
- return pht('Object Has Files');
- }
-
- public function getConduitDescription() {
- return pht('The source object is associated with the destination file.');
- }
-
- public function getTransactionAddString(
- $actor,
- $add_count,
- $add_edges) {
-
- return pht(
- '%s added %s file(s): %s.',
- $actor,
- $add_count,
- $add_edges);
- }
-
- public function getTransactionRemoveString(
- $actor,
- $rem_count,
- $rem_edges) {
-
- return pht(
- '%s removed %s file(s): %s.',
- $actor,
- $rem_count,
- $rem_edges);
- }
-
- public function getTransactionEditString(
- $actor,
- $total_count,
- $add_count,
- $add_edges,
- $rem_count,
- $rem_edges) {
-
- return pht(
- '%s edited file(s), added %s: %s; removed %s: %s.',
- $actor,
- $add_count,
- $add_edges,
- $rem_count,
- $rem_edges);
- }
-
- public function getFeedAddString(
- $actor,
- $object,
- $add_count,
- $add_edges) {
-
- return pht(
- '%s added %s file(s) for %s: %s.',
- $actor,
- $add_count,
- $object,
- $add_edges);
- }
-
- public function getFeedRemoveString(
- $actor,
- $object,
- $rem_count,
- $rem_edges) {
-
- return pht(
- '%s removed %s file(s) for %s: %s.',
- $actor,
- $rem_count,
- $object,
- $rem_edges);
- }
-
- public function getFeedEditString(
- $actor,
- $object,
- $total_count,
- $add_count,
- $add_edges,
- $rem_count,
- $rem_edges) {
-
- return pht(
- '%s edited file(s) for %s, added %s: %s; removed %s: %s.',
- $actor,
- $object,
- $add_count,
- $add_edges,
- $rem_count,
- $rem_edges);
- }
-
-}
diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php
index d0bb1972b6..60ccdaa401 100644
--- a/src/applications/transactions/editengine/PhabricatorEditEngine.php
+++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php
@@ -1,2748 +1,2753 @@
<?php
/**
* @task fields Managing Fields
* @task text Display Text
* @task config Edit Engine Configuration
* @task uri Managing URIs
* @task load Creating and Loading Objects
* @task web Responding to Web Requests
* @task edit Responding to Edit Requests
* @task http Responding to HTTP Parameter Requests
* @task conduit Responding to Conduit Requests
*/
abstract class PhabricatorEditEngine
extends Phobject
implements PhabricatorPolicyInterface {
const EDITENGINECONFIG_DEFAULT = 'default';
const SUBTYPE_DEFAULT = 'default';
private $viewer;
private $controller;
private $isCreate;
private $editEngineConfiguration;
private $contextParameters = array();
private $targetObject;
private $page;
private $pages;
private $navigation;
final public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
final public function getViewer() {
return $this->viewer;
}
final public function setController(PhabricatorController $controller) {
$this->controller = $controller;
$this->setViewer($controller->getViewer());
return $this;
}
final public function getController() {
return $this->controller;
}
final public function getEngineKey() {
$key = $this->getPhobjectClassConstant('ENGINECONST', 64);
if (strpos($key, '/') !== false) {
throw new Exception(
pht(
'EditEngine ("%s") contains an invalid key character "/".',
get_class($this)));
}
return $key;
}
final public function getApplication() {
$app_class = $this->getEngineApplicationClass();
return PhabricatorApplication::getByClass($app_class);
}
final public function addContextParameter($key) {
$this->contextParameters[] = $key;
return $this;
}
public function isEngineConfigurable() {
return true;
}
public function isEngineExtensible() {
return true;
}
public function isDefaultQuickCreateEngine() {
return false;
}
public function getDefaultQuickCreateFormKeys() {
$keys = array();
if ($this->isDefaultQuickCreateEngine()) {
$keys[] = self::EDITENGINECONFIG_DEFAULT;
}
foreach ($keys as $idx => $key) {
$keys[$idx] = $this->getEngineKey().'/'.$key;
}
return $keys;
}
public static function splitFullKey($full_key) {
return explode('/', $full_key, 2);
}
public function getQuickCreateOrderVector() {
return id(new PhutilSortVector())
->addString($this->getObjectCreateShortText());
}
/**
* Force the engine to edit a particular object.
*/
public function setTargetObject($target_object) {
$this->targetObject = $target_object;
return $this;
}
public function getTargetObject() {
return $this->targetObject;
}
public function setNavigation(AphrontSideNavFilterView $navigation) {
$this->navigation = $navigation;
return $this;
}
public function getNavigation() {
return $this->navigation;
}
/* -( Managing Fields )---------------------------------------------------- */
abstract public function getEngineApplicationClass();
abstract protected function buildCustomEditFields($object);
public function getFieldsForConfig(
PhabricatorEditEngineConfiguration $config) {
$object = $this->newEditableObject();
$this->editEngineConfiguration = $config;
// This is mostly making sure that we fill in default values.
$this->setIsCreate(true);
return $this->buildEditFields($object);
}
final protected function buildEditFields($object) {
$viewer = $this->getViewer();
$fields = $this->buildCustomEditFields($object);
foreach ($fields as $field) {
$field
->setViewer($viewer)
->setObject($object);
}
$fields = mpull($fields, null, 'getKey');
if ($this->isEngineExtensible()) {
$extensions = PhabricatorEditEngineExtension::getAllEnabledExtensions();
} else {
$extensions = array();
}
// See T13248. Create a template object to provide to extensions. We
// adjust the template to have the intended subtype, so that extensions
// may change behavior based on the form subtype.
$template_object = clone $object;
if ($this->getIsCreate()) {
if ($this->supportsSubtypes()) {
$config = $this->getEditEngineConfiguration();
$subtype = $config->getSubtype();
$template_object->setSubtype($subtype);
}
}
foreach ($extensions as $extension) {
$extension->setViewer($viewer);
if (!$extension->supportsObject($this, $template_object)) {
continue;
}
$extension_fields = $extension->buildCustomEditFields(
$this,
$template_object);
// TODO: Validate this in more detail with a more tailored error.
assert_instances_of($extension_fields, 'PhabricatorEditField');
foreach ($extension_fields as $field) {
$field
->setViewer($viewer)
->setObject($object);
$group_key = $field->getBulkEditGroupKey();
if ($group_key === null) {
$field->setBulkEditGroupKey('extension');
}
}
$extension_fields = mpull($extension_fields, null, 'getKey');
foreach ($extension_fields as $key => $field) {
$fields[$key] = $field;
}
}
$config = $this->getEditEngineConfiguration();
$fields = $this->willConfigureFields($object, $fields);
$fields = $config->applyConfigurationToFields($this, $object, $fields);
$fields = $this->applyPageToFields($object, $fields);
return $fields;
}
protected function willConfigureFields($object, array $fields) {
return $fields;
}
final public function supportsSubtypes() {
try {
$object = $this->newEditableObject();
} catch (Exception $ex) {
return false;
}
return ($object instanceof PhabricatorEditEngineSubtypeInterface);
}
final public function newSubtypeMap() {
return $this->newEditableObject()->newEditEngineSubtypeMap();
}
/* -( Display Text )------------------------------------------------------- */
/**
* @task text
*/
abstract public function getEngineName();
/**
* @task text
*/
abstract protected function getObjectCreateTitleText($object);
/**
* @task text
*/
protected function getFormHeaderText($object) {
$config = $this->getEditEngineConfiguration();
return $config->getName();
}
/**
* @task text
*/
abstract protected function getObjectEditTitleText($object);
/**
* @task text
*/
abstract protected function getObjectCreateShortText();
/**
* @task text
*/
abstract protected function getObjectName();
/**
* @task text
*/
abstract protected function getObjectEditShortText($object);
/**
* @task text
*/
protected function getObjectCreateButtonText($object) {
return $this->getObjectCreateTitleText($object);
}
/**
* @task text
*/
protected function getObjectEditButtonText($object) {
return pht('Save Changes');
}
/**
* @task text
*/
protected function getCommentViewSeriousHeaderText($object) {
return pht('Take Action');
}
/**
* @task text
*/
protected function getCommentViewSeriousButtonText($object) {
return pht('Submit');
}
/**
* @task text
*/
protected function getCommentViewHeaderText($object) {
return $this->getCommentViewSeriousHeaderText($object);
}
/**
* @task text
*/
protected function getCommentViewButtonText($object) {
return $this->getCommentViewSeriousButtonText($object);
}
/**
* @task text
*/
protected function getPageHeader($object) {
return null;
}
/**
* Return a human-readable header describing what this engine is used to do,
* like "Configure Maniphest Task Forms".
*
* @return string Human-readable description of the engine.
* @task text
*/
abstract public function getSummaryHeader();
/**
* Return a human-readable summary of what this engine is used to do.
*
* @return string Human-readable description of the engine.
* @task text
*/
abstract public function getSummaryText();
/* -( Edit Engine Configuration )------------------------------------------ */
protected function supportsEditEngineConfiguration() {
return true;
}
final protected function getEditEngineConfiguration() {
return $this->editEngineConfiguration;
}
public function newConfigurationQuery() {
return id(new PhabricatorEditEngineConfigurationQuery())
->setViewer($this->getViewer())
->withEngineKeys(array($this->getEngineKey()));
}
private function loadEditEngineConfigurationWithQuery(
PhabricatorEditEngineConfigurationQuery $query,
$sort_method) {
if ($sort_method) {
$results = $query->execute();
$results = msort($results, $sort_method);
$result = head($results);
} else {
$result = $query->executeOne();
}
if (!$result) {
return null;
}
$this->editEngineConfiguration = $result;
return $result;
}
private function loadEditEngineConfigurationWithIdentifier($identifier) {
$query = $this->newConfigurationQuery()
->withIdentifiers(array($identifier));
return $this->loadEditEngineConfigurationWithQuery($query, null);
}
private function loadDefaultConfiguration() {
$query = $this->newConfigurationQuery()
->withIdentifiers(
array(
self::EDITENGINECONFIG_DEFAULT,
))
->withIgnoreDatabaseConfigurations(true);
return $this->loadEditEngineConfigurationWithQuery($query, null);
}
private function loadDefaultCreateConfiguration() {
$query = $this->newConfigurationQuery()
->withIsDefault(true)
->withIsDisabled(false);
return $this->loadEditEngineConfigurationWithQuery(
$query,
'getCreateSortKey');
}
public function loadDefaultEditConfiguration($object) {
$query = $this->newConfigurationQuery()
->withIsEdit(true)
->withIsDisabled(false);
// If this object supports subtyping, we edit it with a form of the same
// subtype: so "bug" tasks get edited with "bug" forms.
if ($object instanceof PhabricatorEditEngineSubtypeInterface) {
$query->withSubtypes(
array(
$object->getEditEngineSubtype(),
));
}
return $this->loadEditEngineConfigurationWithQuery(
$query,
'getEditSortKey');
}
final public function getBuiltinEngineConfigurations() {
$configurations = $this->newBuiltinEngineConfigurations();
if (!$configurations) {
throw new Exception(
pht(
'EditEngine ("%s") returned no builtin engine configurations, but '.
'an edit engine must have at least one configuration.',
get_class($this)));
}
assert_instances_of($configurations, 'PhabricatorEditEngineConfiguration');
$has_default = false;
foreach ($configurations as $config) {
if ($config->getBuiltinKey() == self::EDITENGINECONFIG_DEFAULT) {
$has_default = true;
}
}
if (!$has_default) {
$first = head($configurations);
if (!$first->getBuiltinKey()) {
$first
->setBuiltinKey(self::EDITENGINECONFIG_DEFAULT)
->setIsDefault(true)
->setIsEdit(true);
$first_name = $first->getName();
if ($first_name === null || $first_name === '') {
$first->setName($this->getObjectCreateShortText());
}
} else {
throw new Exception(
pht(
'EditEngine ("%s") returned builtin engine configurations, '.
'but none are marked as default and the first configuration has '.
'a different builtin key already. Mark a builtin as default or '.
'omit the key from the first configuration',
get_class($this)));
}
}
$builtins = array();
foreach ($configurations as $key => $config) {
$builtin_key = $config->getBuiltinKey();
if ($builtin_key === null) {
throw new Exception(
pht(
'EditEngine ("%s") returned builtin engine configurations, '.
'but one (with key "%s") is missing a builtin key. Provide a '.
'builtin key for each configuration (you can omit it from the '.
'first configuration in the list to automatically assign the '.
'default key).',
get_class($this),
$key));
}
if (isset($builtins[$builtin_key])) {
throw new Exception(
pht(
'EditEngine ("%s") returned builtin engine configurations, '.
'but at least two specify the same builtin key ("%s"). Engines '.
'must have unique builtin keys.',
get_class($this),
$builtin_key));
}
$builtins[$builtin_key] = $config;
}
return $builtins;
}
protected function newBuiltinEngineConfigurations() {
return array(
$this->newConfiguration(),
);
}
final protected function newConfiguration() {
return PhabricatorEditEngineConfiguration::initializeNewConfiguration(
$this->getViewer(),
$this);
}
/* -( Managing URIs )------------------------------------------------------ */
/**
* @task uri
*/
abstract protected function getObjectViewURI($object);
/**
* @task uri
*/
protected function getObjectCreateCancelURI($object) {
return $this->getApplication()->getApplicationURI();
}
/**
* @task uri
*/
protected function getEditorURI() {
return $this->getApplication()->getApplicationURI('edit/');
}
/**
* @task uri
*/
protected function getObjectEditCancelURI($object) {
return $this->getObjectViewURI($object);
}
/**
* @task uri
*/
public function getCreateURI($form_key) {
try {
$create_uri = $this->getEditURI(null, "form/{$form_key}/");
} catch (Exception $ex) {
$create_uri = null;
}
return $create_uri;
}
/**
* @task uri
*/
public function getEditURI($object = null, $path = null) {
$parts = array();
$parts[] = $this->getEditorURI();
if ($object && $object->getID()) {
$parts[] = $object->getID().'/';
}
if ($path !== null) {
$parts[] = $path;
}
return implode('', $parts);
}
public function getEffectiveObjectViewURI($object) {
if ($this->getIsCreate()) {
return $this->getObjectViewURI($object);
}
$page = $this->getSelectedPage();
if ($page) {
$view_uri = $page->getViewURI();
if ($view_uri !== null) {
return $view_uri;
}
}
return $this->getObjectViewURI($object);
}
public function getEffectiveObjectEditDoneURI($object) {
return $this->getEffectiveObjectViewURI($object);
}
public function getEffectiveObjectEditCancelURI($object) {
$page = $this->getSelectedPage();
if ($page) {
$view_uri = $page->getViewURI();
if ($view_uri !== null) {
return $view_uri;
}
}
return $this->getObjectEditCancelURI($object);
}
/* -( Creating and Loading Objects )--------------------------------------- */
/**
* Initialize a new object for creation.
*
* @return object Newly initialized object.
* @task load
*/
abstract protected function newEditableObject();
/**
* Build an empty query for objects.
*
* @return PhabricatorPolicyAwareQuery Query.
* @task load
*/
abstract protected function newObjectQuery();
/**
* Test if this workflow is creating a new object or editing an existing one.
*
* @return bool True if a new object is being created.
* @task load
*/
final public function getIsCreate() {
return $this->isCreate;
}
/**
* Initialize a new object for object creation via Conduit.
*
* @return object Newly initialized object.
* @param list<wild> Raw transactions.
* @task load
*/
protected function newEditableObjectFromConduit(array $raw_xactions) {
return $this->newEditableObject();
}
/**
* Initialize a new object for documentation creation.
*
* @return object Newly initialized object.
* @task load
*/
protected function newEditableObjectForDocumentation() {
return $this->newEditableObject();
}
/**
* Flag this workflow as a create or edit.
*
* @param bool True if this is a create workflow.
* @return this
* @task load
*/
private function setIsCreate($is_create) {
$this->isCreate = $is_create;
return $this;
}
/**
* Try to load an object by ID, PHID, or monogram. This is done primarily
* to make Conduit a little easier to use.
*
* @param wild ID, PHID, or monogram.
* @param list<const> List of required capability constants, or omit for
* defaults.
* @return object Corresponding editable object.
* @task load
*/
private function newObjectFromIdentifier(
$identifier,
array $capabilities = array()) {
if (is_int($identifier) || ctype_digit($identifier)) {
$object = $this->newObjectFromID($identifier, $capabilities);
if (!$object) {
throw new Exception(
pht(
'No object exists with ID "%s".',
$identifier));
}
return $object;
}
$type_unknown = PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN;
if (phid_get_type($identifier) != $type_unknown) {
$object = $this->newObjectFromPHID($identifier, $capabilities);
if (!$object) {
throw new Exception(
pht(
'No object exists with PHID "%s".',
$identifier));
}
return $object;
}
$target = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withNames(array($identifier))
->executeOne();
if (!$target) {
throw new Exception(
pht(
'Monogram "%s" does not identify a valid object.',
$identifier));
}
$expect = $this->newEditableObject();
$expect_class = get_class($expect);
$target_class = get_class($target);
if ($expect_class !== $target_class) {
throw new Exception(
pht(
'Monogram "%s" identifies an object of the wrong type. Loaded '.
'object has class "%s", but this editor operates on objects of '.
'type "%s".',
$identifier,
$target_class,
$expect_class));
}
// Load the object by PHID using this engine's standard query. This makes
// sure it's really valid, goes through standard policy check logic, and
// picks up any `need...()` clauses we want it to load with.
$object = $this->newObjectFromPHID($target->getPHID(), $capabilities);
if (!$object) {
throw new Exception(
pht(
'Failed to reload object identified by monogram "%s" when '.
'querying by PHID.',
$identifier));
}
return $object;
}
/**
* Load an object by ID.
*
* @param int Object ID.
* @param list<const> List of required capability constants, or omit for
* defaults.
* @return object|null Object, or null if no such object exists.
* @task load
*/
private function newObjectFromID($id, array $capabilities = array()) {
$query = $this->newObjectQuery()
->withIDs(array($id));
return $this->newObjectFromQuery($query, $capabilities);
}
/**
* Load an object by PHID.
*
* @param phid Object PHID.
* @param list<const> List of required capability constants, or omit for
* defaults.
* @return object|null Object, or null if no such object exists.
* @task load
*/
private function newObjectFromPHID($phid, array $capabilities = array()) {
$query = $this->newObjectQuery()
->withPHIDs(array($phid));
return $this->newObjectFromQuery($query, $capabilities);
}
/**
* Load an object given a configured query.
*
* @param PhabricatorPolicyAwareQuery Configured query.
* @param list<const> List of required capability constants, or omit for
* defaults.
* @return object|null Object, or null if no such object exists.
* @task load
*/
private function newObjectFromQuery(
PhabricatorPolicyAwareQuery $query,
array $capabilities = array()) {
$viewer = $this->getViewer();
if (!$capabilities) {
$capabilities = array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
$object = $query
->setViewer($viewer)
->requireCapabilities($capabilities)
->executeOne();
if (!$object) {
return null;
}
return $object;
}
/**
* Verify that an object is appropriate for editing.
*
* @param wild Loaded value.
* @return void
* @task load
*/
private function validateObject($object) {
if (!$object || !is_object($object)) {
throw new Exception(
pht(
'EditEngine "%s" created or loaded an invalid object: object must '.
'actually be an object, but is of some other type ("%s").',
get_class($this),
gettype($object)));
}
if (!($object instanceof PhabricatorApplicationTransactionInterface)) {
throw new Exception(
pht(
'EditEngine "%s" created or loaded an invalid object: object (of '.
'class "%s") must implement "%s", but does not.',
get_class($this),
get_class($object),
'PhabricatorApplicationTransactionInterface'));
}
}
/* -( Responding to Web Requests )----------------------------------------- */
final public function buildResponse() {
$viewer = $this->getViewer();
$controller = $this->getController();
$request = $controller->getRequest();
$action = $this->getEditAction();
$capabilities = array();
$use_default = false;
$require_create = true;
switch ($action) {
case 'comment':
$capabilities = array(
PhabricatorPolicyCapability::CAN_VIEW,
);
$use_default = true;
break;
case 'parameters':
$use_default = true;
break;
case 'nodefault':
case 'nocreate':
case 'nomanage':
$require_create = false;
break;
default:
break;
}
$object = $this->getTargetObject();
if (!$object) {
$id = $request->getURIData('id');
if ($id) {
$this->setIsCreate(false);
$object = $this->newObjectFromID($id, $capabilities);
if (!$object) {
return new Aphront404Response();
}
} else {
// Make sure the viewer has permission to create new objects of
// this type if we're going to create a new object.
if ($require_create) {
$this->requireCreateCapability();
}
$this->setIsCreate(true);
$object = $this->newEditableObject();
}
} else {
$id = $object->getID();
}
$this->validateObject($object);
if ($use_default) {
$config = $this->loadDefaultConfiguration();
if (!$config) {
return new Aphront404Response();
}
} else {
$form_key = $request->getURIData('formKey');
if (strlen($form_key)) {
$config = $this->loadEditEngineConfigurationWithIdentifier($form_key);
if (!$config) {
return new Aphront404Response();
}
if ($id && !$config->getIsEdit()) {
return $this->buildNotEditFormRespose($object, $config);
}
} else {
if ($id) {
$config = $this->loadDefaultEditConfiguration($object);
if (!$config) {
return $this->buildNoEditResponse($object);
}
} else {
$config = $this->loadDefaultCreateConfiguration();
if (!$config) {
return $this->buildNoCreateResponse($object);
}
}
}
}
if ($config->getIsDisabled()) {
return $this->buildDisabledFormResponse($object, $config);
}
$page_key = $request->getURIData('pageKey');
if (!strlen($page_key)) {
$pages = $this->getPages($object);
if ($pages) {
$page_key = head_key($pages);
}
}
if (strlen($page_key)) {
$page = $this->selectPage($object, $page_key);
if (!$page) {
return new Aphront404Response();
}
}
switch ($action) {
case 'parameters':
return $this->buildParametersResponse($object);
case 'nodefault':
return $this->buildNoDefaultResponse($object);
case 'nocreate':
return $this->buildNoCreateResponse($object);
case 'nomanage':
return $this->buildNoManageResponse($object);
case 'comment':
return $this->buildCommentResponse($object);
default:
return $this->buildEditResponse($object);
}
}
private function buildCrumbs($object, $final = false) {
$controller = $this->getController();
$crumbs = $controller->buildApplicationCrumbsForEditEngine();
if ($this->getIsCreate()) {
$create_text = $this->getObjectCreateShortText();
if ($final) {
$crumbs->addTextCrumb($create_text);
} else {
$edit_uri = $this->getEditURI($object);
$crumbs->addTextCrumb($create_text, $edit_uri);
}
} else {
$crumbs->addTextCrumb(
$this->getObjectEditShortText($object),
$this->getEffectiveObjectViewURI($object));
$edit_text = pht('Edit');
if ($final) {
$crumbs->addTextCrumb($edit_text);
} else {
$edit_uri = $this->getEditURI($object);
$crumbs->addTextCrumb($edit_text, $edit_uri);
}
}
return $crumbs;
}
private function buildEditResponse($object) {
$viewer = $this->getViewer();
$controller = $this->getController();
$request = $controller->getRequest();
$fields = $this->buildEditFields($object);
$template = $object->getApplicationTransactionTemplate();
$page_state = new PhabricatorEditEnginePageState();
if ($this->getIsCreate()) {
$cancel_uri = $this->getObjectCreateCancelURI($object);
$submit_button = $this->getObjectCreateButtonText($object);
$page_state->setIsCreate(true);
} else {
$cancel_uri = $this->getEffectiveObjectEditCancelURI($object);
$submit_button = $this->getObjectEditButtonText($object);
}
$config = $this->getEditEngineConfiguration()
->attachEngine($this);
// NOTE: Don't prompt users to override locks when creating objects,
// even if the default settings would create a locked object.
$can_interact = PhabricatorPolicyFilter::canInteract($viewer, $object);
if (!$can_interact &&
!$this->getIsCreate() &&
!$request->getBool('editEngine') &&
!$request->getBool('overrideLock')) {
$lock = PhabricatorEditEngineLock::newForObject($viewer, $object);
$dialog = $this->getController()
->newDialog()
->addHiddenInput('overrideLock', true)
->setDisableWorkflowOnSubmit(true)
->addCancelButton($cancel_uri);
return $lock->willPromptUserForLockOverrideWithDialog($dialog);
}
$validation_exception = null;
if ($request->isFormOrHisecPost() && $request->getBool('editEngine')) {
$page_state->setIsSubmit(true);
$submit_fields = $fields;
foreach ($submit_fields as $key => $field) {
if (!$field->shouldGenerateTransactionsFromSubmit()) {
unset($submit_fields[$key]);
continue;
}
}
// Before we read the submitted values, store a copy of what we would
// use if the form was empty so we can figure out which transactions are
// just setting things to their default values for the current form.
$defaults = array();
foreach ($submit_fields as $key => $field) {
$defaults[$key] = $field->getValueForTransaction();
}
foreach ($submit_fields as $key => $field) {
$field->setIsSubmittedForm(true);
if (!$field->shouldReadValueFromSubmit()) {
continue;
}
$field->readValueFromSubmit($request);
}
$xactions = array();
if ($this->getIsCreate()) {
$xactions[] = id(clone $template)
->setTransactionType(PhabricatorTransactions::TYPE_CREATE);
if ($this->supportsSubtypes()) {
$xactions[] = id(clone $template)
->setTransactionType(PhabricatorTransactions::TYPE_SUBTYPE)
->setNewValue($config->getSubtype());
}
}
foreach ($submit_fields as $key => $field) {
$field_value = $field->getValueForTransaction();
$type_xactions = $field->generateTransactions(
clone $template,
array(
'value' => $field_value,
));
foreach ($type_xactions as $type_xaction) {
$default = $defaults[$key];
if ($default === $field->getValueForTransaction()) {
$type_xaction->setIsDefaultTransaction(true);
}
$xactions[] = $type_xaction;
}
}
$editor = $object->getApplicationTransactionEditor()
->setActor($viewer)
->setContentSourceFromRequest($request)
->setCancelURI($cancel_uri)
->setContinueOnNoEffect(true);
try {
$xactions = $this->willApplyTransactions($object, $xactions);
$editor->applyTransactions($object, $xactions);
$this->didApplyTransactions($object, $xactions);
return $this->newEditResponse($request, $object, $xactions);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
foreach ($fields as $field) {
$message = $this->getValidationExceptionShortMessage($ex, $field);
if ($message === null) {
continue;
}
$field->setControlError($message);
}
$page_state->setIsError(true);
}
} else {
if ($this->getIsCreate()) {
$template = $request->getStr('template');
if (strlen($template)) {
$template_object = $this->newObjectFromIdentifier(
$template,
array(
PhabricatorPolicyCapability::CAN_VIEW,
));
if (!$template_object) {
return new Aphront404Response();
}
} else {
$template_object = null;
}
if ($template_object) {
$copy_fields = $this->buildEditFields($template_object);
$copy_fields = mpull($copy_fields, null, 'getKey');
foreach ($copy_fields as $copy_key => $copy_field) {
if (!$copy_field->getIsCopyable()) {
unset($copy_fields[$copy_key]);
}
}
} else {
$copy_fields = array();
}
foreach ($fields as $field) {
if (!$field->shouldReadValueFromRequest()) {
continue;
}
$field_key = $field->getKey();
if (isset($copy_fields[$field_key])) {
$field->readValueFromField($copy_fields[$field_key]);
}
$field->readValueFromRequest($request);
}
}
}
$action_button = $this->buildEditFormActionButton($object);
if ($this->getIsCreate()) {
$header_text = $this->getFormHeaderText($object);
} else {
$header_text = $this->getObjectEditTitleText($object);
}
$show_preview = !$request->isAjax();
if ($show_preview) {
$previews = array();
foreach ($fields as $field) {
$preview = $field->getPreviewPanel();
if (!$preview) {
continue;
}
$control_id = $field->getControlID();
$preview
->setControlID($control_id)
->setPreviewURI('/transactions/remarkuppreview/');
$previews[] = $preview;
}
} else {
$previews = array();
}
$form = $this->buildEditForm($object, $fields);
$crumbs = $this->buildCrumbs($object, $final = true);
$crumbs->setBorder(true);
if ($request->isAjax()) {
return $this->getController()
->newDialog()
->setWidth(AphrontDialogView::WIDTH_FULL)
->setTitle($header_text)
->setValidationException($validation_exception)
->appendForm($form)
->addCancelButton($cancel_uri)
->addSubmitButton($submit_button);
}
$box_header = id(new PHUIHeaderView())
->setHeader($header_text);
if ($action_button) {
$box_header->addActionLink($action_button);
}
$request_submit_key = $request->getSubmitKey();
$engine_submit_key = $this->getEditEngineSubmitKey();
if ($request_submit_key === $engine_submit_key) {
$page_state->setIsSubmit(true);
$page_state->setIsSave(true);
}
$head = $this->newEditFormHeadContent($page_state);
$tail = $this->newEditFormTailContent($page_state);
$box = id(new PHUIObjectBoxView())
->setUser($viewer)
->setHeader($box_header)
->setValidationException($validation_exception)
->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->appendChild($form);
$content = array(
$head,
$box,
$previews,
$tail,
);
$view = new PHUITwoColumnView();
$page_header = $this->getPageHeader($object);
if ($page_header) {
$view->setHeader($page_header);
}
$view->setFooter($content);
$page = $controller->newPage()
->setTitle($header_text)
->setCrumbs($crumbs)
->appendChild($view);
$navigation = $this->getNavigation();
if ($navigation) {
$page->setNavigation($navigation);
}
return $page;
}
protected function newEditFormHeadContent(
PhabricatorEditEnginePageState $state) {
return null;
}
protected function newEditFormTailContent(
PhabricatorEditEnginePageState $state) {
return null;
}
protected function newEditResponse(
AphrontRequest $request,
$object,
array $xactions) {
$submit_cookie = PhabricatorCookies::COOKIE_SUBMIT;
$submit_key = $this->getEditEngineSubmitKey();
$request->setTemporaryCookie($submit_cookie, $submit_key);
return id(new AphrontRedirectResponse())
->setURI($this->getEffectiveObjectEditDoneURI($object));
}
private function getEditEngineSubmitKey() {
return 'edit-engine/'.$this->getEngineKey();
}
private function buildEditForm($object, array $fields) {
$viewer = $this->getViewer();
$controller = $this->getController();
$request = $controller->getRequest();
$fields = $this->willBuildEditForm($object, $fields);
$request_path = $request->getPath();
$form = id(new AphrontFormView())
->setUser($viewer)
->setAction($request_path)
->addHiddenInput('editEngine', 'true');
foreach ($this->contextParameters as $param) {
$form->addHiddenInput($param, $request->getStr($param));
}
$requires_mfa = false;
if ($object instanceof PhabricatorEditEngineMFAInterface) {
$mfa_engine = PhabricatorEditEngineMFAEngine::newEngineForObject($object)
->setViewer($viewer);
$requires_mfa = $mfa_engine->shouldRequireMFA();
}
if ($requires_mfa) {
$message = pht(
'You will be required to provide multi-factor credentials to make '.
'changes.');
$form->appendChild(
id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_MFA)
->setErrors(array($message)));
// TODO: This should also set workflow on the form, so the user doesn't
// lose any form data if they "Cancel". However, Maniphest currently
// overrides "newEditResponse()" if the request is Ajax and returns a
// bag of view data. This can reasonably be cleaned up when workboards
// get their next iteration.
}
foreach ($fields as $field) {
if (!$field->getIsFormField()) {
continue;
}
$field->appendToForm($form);
}
if ($this->getIsCreate()) {
$cancel_uri = $this->getObjectCreateCancelURI($object);
$submit_button = $this->getObjectCreateButtonText($object);
} else {
$cancel_uri = $this->getEffectiveObjectEditCancelURI($object);
$submit_button = $this->getObjectEditButtonText($object);
}
if (!$request->isAjax()) {
$buttons = id(new AphrontFormSubmitControl())
->setValue($submit_button);
if ($cancel_uri) {
$buttons->addCancelButton($cancel_uri);
}
$form->appendControl($buttons);
}
return $form;
}
protected function willBuildEditForm($object, array $fields) {
return $fields;
}
private function buildEditFormActionButton($object) {
if (!$this->isEngineConfigurable()) {
return null;
}
$viewer = $this->getViewer();
$action_view = id(new PhabricatorActionListView())
->setUser($viewer);
foreach ($this->buildEditFormActions($object) as $action) {
$action_view->addAction($action);
}
$action_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('Configure Form'))
->setHref('#')
->setIcon('fa-gear')
->setDropdownMenu($action_view);
return $action_button;
}
private function buildEditFormActions($object) {
$actions = array();
if ($this->supportsEditEngineConfiguration()) {
$engine_key = $this->getEngineKey();
$config = $this->getEditEngineConfiguration();
$can_manage = PhabricatorPolicyFilter::hasCapability(
$this->getViewer(),
$config,
PhabricatorPolicyCapability::CAN_EDIT);
if ($can_manage) {
$manage_uri = $config->getURI();
} else {
$manage_uri = $this->getEditURI(null, 'nomanage/');
}
$view_uri = "/transactions/editengine/{$engine_key}/";
$actions[] = id(new PhabricatorActionView())
->setLabel(true)
->setName(pht('Configuration'));
$actions[] = id(new PhabricatorActionView())
->setName(pht('View Form Configurations'))
->setIcon('fa-list-ul')
->setHref($view_uri);
$actions[] = id(new PhabricatorActionView())
->setName(pht('Edit Form Configuration'))
->setIcon('fa-pencil')
->setHref($manage_uri)
->setDisabled(!$can_manage)
->setWorkflow(!$can_manage);
}
$actions[] = id(new PhabricatorActionView())
->setLabel(true)
->setName(pht('Documentation'));
$actions[] = id(new PhabricatorActionView())
->setName(pht('Using HTTP Parameters'))
->setIcon('fa-book')
->setHref($this->getEditURI($object, 'parameters/'));
$doc_href = PhabricatorEnv::getDoclink('User Guide: Customizing Forms');
$actions[] = id(new PhabricatorActionView())
->setName(pht('User Guide: Customizing Forms'))
->setIcon('fa-book')
->setHref($doc_href);
return $actions;
}
public function newNUXButton($text) {
$specs = $this->newCreateActionSpecifications(array());
$head = head($specs);
return id(new PHUIButtonView())
->setTag('a')
->setText($text)
->setHref($head['uri'])
->setDisabled($head['disabled'])
->setWorkflow($head['workflow'])
->setColor(PHUIButtonView::GREEN);
}
final public function addActionToCrumbs(
PHUICrumbsView $crumbs,
array $parameters = array()) {
$viewer = $this->getViewer();
$specs = $this->newCreateActionSpecifications($parameters);
$head = head($specs);
$menu_uri = $head['uri'];
$dropdown = null;
if (count($specs) > 1) {
$menu_icon = 'fa-caret-square-o-down';
$menu_name = $this->getObjectCreateShortText();
$workflow = false;
$disabled = false;
$dropdown = id(new PhabricatorActionListView())
->setUser($viewer);
foreach ($specs as $spec) {
$dropdown->addAction(
id(new PhabricatorActionView())
->setName($spec['name'])
->setIcon($spec['icon'])
->setHref($spec['uri'])
->setDisabled($head['disabled'])
->setWorkflow($head['workflow']));
}
} else {
$menu_icon = $head['icon'];
$menu_name = $head['name'];
$workflow = $head['workflow'];
$disabled = $head['disabled'];
}
$action = id(new PHUIListItemView())
->setName($menu_name)
->setHref($menu_uri)
->setIcon($menu_icon)
->setWorkflow($workflow)
->setDisabled($disabled);
if ($dropdown) {
$action->setDropdownMenu($dropdown);
}
$crumbs->addAction($action);
}
/**
* Build a raw description of available "Create New Object" UI options so
* other methods can build menus or buttons.
*/
public function newCreateActionSpecifications(array $parameters) {
$viewer = $this->getViewer();
$can_create = $this->hasCreateCapability();
if ($can_create) {
$configs = $this->loadUsableConfigurationsForCreate();
} else {
$configs = array();
}
$disabled = false;
$workflow = false;
$menu_icon = 'fa-plus-square';
$specs = array();
if (!$configs) {
if ($viewer->isLoggedIn()) {
$disabled = true;
} else {
// If the viewer isn't logged in, assume they'll get hit with a login
// dialog and are likely able to create objects after they log in.
$disabled = false;
}
$workflow = true;
if ($can_create) {
$create_uri = $this->getEditURI(null, 'nodefault/');
} else {
$create_uri = $this->getEditURI(null, 'nocreate/');
}
$specs[] = array(
'name' => $this->getObjectCreateShortText(),
'uri' => $create_uri,
'icon' => $menu_icon,
'disabled' => $disabled,
'workflow' => $workflow,
);
} else {
foreach ($configs as $config) {
$config_uri = $config->getCreateURI();
if ($parameters) {
$config_uri = (string)new PhutilURI($config_uri, $parameters);
}
$specs[] = array(
'name' => $config->getDisplayName(),
'uri' => $config_uri,
'icon' => 'fa-plus',
'disabled' => false,
'workflow' => false,
);
}
}
return $specs;
}
final public function buildEditEngineCommentView($object) {
$config = $this->loadDefaultEditConfiguration($object);
if (!$config) {
// TODO: This just nukes the entire comment form if you don't have access
// to any edit forms. We might want to tailor this UX a bit.
return id(new PhabricatorApplicationTransactionCommentView())
->setNoPermission(true);
}
$viewer = $this->getViewer();
$can_interact = PhabricatorPolicyFilter::canInteract($viewer, $object);
if (!$can_interact) {
$lock = PhabricatorEditEngineLock::newForObject($viewer, $object);
return id(new PhabricatorApplicationTransactionCommentView())
->setEditEngineLock($lock);
}
$object_phid = $object->getPHID();
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
if ($is_serious) {
$header_text = $this->getCommentViewSeriousHeaderText($object);
$button_text = $this->getCommentViewSeriousButtonText($object);
} else {
$header_text = $this->getCommentViewHeaderText($object);
$button_text = $this->getCommentViewButtonText($object);
}
$comment_uri = $this->getEditURI($object, 'comment/');
$requires_mfa = false;
if ($object instanceof PhabricatorEditEngineMFAInterface) {
$mfa_engine = PhabricatorEditEngineMFAEngine::newEngineForObject($object)
->setViewer($viewer);
$requires_mfa = $mfa_engine->shouldRequireMFA();
}
$view = id(new PhabricatorApplicationTransactionCommentView())
->setUser($viewer)
->setObjectPHID($object_phid)
->setHeaderText($header_text)
->setAction($comment_uri)
->setRequiresMFA($requires_mfa)
->setSubmitButtonName($button_text);
$draft = PhabricatorVersionedDraft::loadDraft(
$object_phid,
$viewer->getPHID());
if ($draft) {
$view->setVersionedDraft($draft);
}
$view->setCurrentVersion($this->loadDraftVersion($object));
$fields = $this->buildEditFields($object);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
$comment_actions = array();
foreach ($fields as $field) {
if (!$field->shouldGenerateTransactionsFromComment()) {
continue;
}
if (!$can_edit) {
if (!$field->getCanApplyWithoutEditCapability()) {
continue;
}
}
$comment_action = $field->getCommentAction();
if (!$comment_action) {
continue;
}
$key = $comment_action->getKey();
// TODO: Validate these better.
$comment_actions[$key] = $comment_action;
}
$comment_actions = msortv($comment_actions, 'getSortVector');
$view->setCommentActions($comment_actions);
$comment_groups = $this->newCommentActionGroups();
$view->setCommentActionGroups($comment_groups);
return $view;
}
protected function loadDraftVersion($object) {
$viewer = $this->getViewer();
if (!$viewer->isLoggedIn()) {
return null;
}
$template = $object->getApplicationTransactionTemplate();
$conn_r = $template->establishConnection('r');
// Find the most recent transaction the user has written. We'll use this
// as a version number to make sure that out-of-date drafts get discarded.
$result = queryfx_one(
$conn_r,
'SELECT id AS version FROM %T
WHERE objectPHID = %s AND authorPHID = %s
ORDER BY id DESC LIMIT 1',
$template->getTableName(),
$object->getPHID(),
$viewer->getPHID());
if ($result) {
return (int)$result['version'];
} else {
return null;
}
}
/* -( Responding to HTTP Parameter Requests )------------------------------ */
/**
* Respond to a request for documentation on HTTP parameters.
*
* @param object Editable object.
* @return AphrontResponse Response object.
* @task http
*/
private function buildParametersResponse($object) {
$controller = $this->getController();
$viewer = $this->getViewer();
$request = $controller->getRequest();
$fields = $this->buildEditFields($object);
$crumbs = $this->buildCrumbs($object);
$crumbs->addTextCrumb(pht('HTTP Parameters'));
$crumbs->setBorder(true);
$header_text = pht(
'HTTP Parameters: %s',
$this->getObjectCreateShortText());
$header = id(new PHUIHeaderView())
->setHeader($header_text);
$help_view = id(new PhabricatorApplicationEditHTTPParameterHelpView())
->setUser($viewer)
->setFields($fields);
$document = id(new PHUIDocumentView())
->setUser($viewer)
->setHeader($header)
->appendChild($help_view);
return $controller->newPage()
->setTitle(pht('HTTP Parameters'))
->setCrumbs($crumbs)
->appendChild($document);
}
private function buildError($object, $title, $body) {
$cancel_uri = $this->getObjectCreateCancelURI($object);
$dialog = $this->getController()
->newDialog()
->addCancelButton($cancel_uri);
if ($title !== null) {
$dialog->setTitle($title);
}
if ($body !== null) {
$dialog->appendParagraph($body);
}
return $dialog;
}
private function buildNoDefaultResponse($object) {
return $this->buildError(
$object,
pht('No Default Create Forms'),
pht(
'This application is not configured with any forms for creating '.
'objects that are visible to you and enabled.'));
}
private function buildNoCreateResponse($object) {
return $this->buildError(
$object,
pht('No Create Permission'),
pht('You do not have permission to create these objects.'));
}
private function buildNoManageResponse($object) {
return $this->buildError(
$object,
pht('No Manage Permission'),
pht(
'You do not have permission to configure forms for this '.
'application.'));
}
private function buildNoEditResponse($object) {
return $this->buildError(
$object,
pht('No Edit Forms'),
pht(
'You do not have access to any forms which are enabled and marked '.
'as edit forms.'));
}
private function buildNotEditFormRespose($object, $config) {
return $this->buildError(
$object,
pht('Not an Edit Form'),
pht(
'This form ("%s") is not marked as an edit form, so '.
'it can not be used to edit objects.',
$config->getName()));
}
private function buildDisabledFormResponse($object, $config) {
return $this->buildError(
$object,
pht('Form Disabled'),
pht(
'This form ("%s") has been disabled, so it can not be used.',
$config->getName()));
}
private function buildLockedObjectResponse($object) {
$dialog = $this->buildError($object, null, null);
$viewer = $this->getViewer();
$lock = PhabricatorEditEngineLock::newForObject($viewer, $object);
return $lock->willBlockUserInteractionWithDialog($dialog);
}
private function buildCommentResponse($object) {
$viewer = $this->getViewer();
if ($this->getIsCreate()) {
return new Aphront404Response();
}
$controller = $this->getController();
$request = $controller->getRequest();
// NOTE: We handle hisec inside the transaction editor with "Sign With MFA"
// comment actions.
if (!$request->isFormOrHisecPost()) {
return new Aphront400Response();
}
$can_interact = PhabricatorPolicyFilter::canInteract($viewer, $object);
if (!$can_interact) {
return $this->buildLockedObjectResponse($object);
}
$config = $this->loadDefaultEditConfiguration($object);
if (!$config) {
return new Aphront404Response();
}
$fields = $this->buildEditFields($object);
$is_preview = $request->isPreviewRequest();
$view_uri = $this->getEffectiveObjectViewURI($object);
$template = $object->getApplicationTransactionTemplate();
$comment_template = $template->getApplicationTransactionCommentObject();
$comment_text = $request->getStr('comment');
+ $comment_metadata = $request->getStr('comment_metadata');
+ if (strlen($comment_metadata)) {
+ $comment_metadata = phutil_json_decode($comment_metadata);
+ }
+
$actions = $request->getStr('editengine.actions');
if ($actions) {
$actions = phutil_json_decode($actions);
}
if ($is_preview) {
$version_key = PhabricatorVersionedDraft::KEY_VERSION;
$request_version = $request->getInt($version_key);
$current_version = $this->loadDraftVersion($object);
if ($request_version >= $current_version) {
$draft = PhabricatorVersionedDraft::loadOrCreateDraft(
$object->getPHID(),
$viewer->getPHID(),
$current_version);
- $is_empty = (!strlen($comment_text) && !$actions);
-
$draft
->setProperty('comment', $comment_text)
+ ->setProperty('metadata', $comment_metadata)
->setProperty('actions', $actions)
->save();
$draft_engine = $this->newDraftEngine($object);
if ($draft_engine) {
$draft_engine
->setVersionedDraft($draft)
->synchronize();
}
}
}
$xactions = array();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
if ($actions) {
$action_map = array();
foreach ($actions as $action) {
$type = idx($action, 'type');
if (!$type) {
continue;
}
if (empty($fields[$type])) {
continue;
}
$action_map[$type] = $action;
}
foreach ($action_map as $type => $action) {
$field = $fields[$type];
if (!$field->shouldGenerateTransactionsFromComment()) {
continue;
}
// If you don't have edit permission on the object, you're limited in
// which actions you can take via the comment form. Most actions
// need edit permission, but some actions (like "Accept Revision")
// can be applied by anyone with view permission.
if (!$can_edit) {
if (!$field->getCanApplyWithoutEditCapability()) {
// We know the user doesn't have the capability, so this will
// raise a policy exception.
PhabricatorPolicyFilter::requireCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
}
}
if (array_key_exists('initialValue', $action)) {
$field->setInitialValue($action['initialValue']);
}
$field->readValueFromComment(idx($action, 'value'));
$type_xactions = $field->generateTransactions(
clone $template,
array(
'value' => $field->getValueForTransaction(),
));
foreach ($type_xactions as $type_xaction) {
$xactions[] = $type_xaction;
}
}
}
$auto_xactions = $this->newAutomaticCommentTransactions($object);
foreach ($auto_xactions as $xaction) {
$xactions[] = $xaction;
}
if (strlen($comment_text) || !$xactions) {
$xactions[] = id(clone $template)
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
+ ->setMetadataValue('remarkup.control', $comment_metadata)
->attachComment(
id(clone $comment_template)
->setContent($comment_text));
}
$editor = $object->getApplicationTransactionEditor()
->setActor($viewer)
->setContinueOnNoEffect($request->isContinueRequest())
->setContinueOnMissingFields(true)
->setContentSourceFromRequest($request)
->setCancelURI($view_uri)
->setRaiseWarnings(!$request->getBool('editEngine.warnings'))
->setIsPreview($is_preview);
try {
$xactions = $editor->applyTransactions($object, $xactions);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
return id(new PhabricatorApplicationTransactionValidationResponse())
->setCancelURI($view_uri)
->setException($ex);
} catch (PhabricatorApplicationTransactionNoEffectException $ex) {
return id(new PhabricatorApplicationTransactionNoEffectResponse())
->setCancelURI($view_uri)
->setException($ex);
} catch (PhabricatorApplicationTransactionWarningException $ex) {
return id(new PhabricatorApplicationTransactionWarningResponse())
->setObject($object)
->setCancelURI($view_uri)
->setException($ex);
}
if (!$is_preview) {
PhabricatorVersionedDraft::purgeDrafts(
$object->getPHID(),
$viewer->getPHID());
$draft_engine = $this->newDraftEngine($object);
if ($draft_engine) {
$draft_engine
->setVersionedDraft(null)
->synchronize();
}
}
if ($request->isAjax() && $is_preview) {
$preview_content = $this->newCommentPreviewContent($object, $xactions);
$raw_view_data = $request->getStr('viewData');
try {
$view_data = phutil_json_decode($raw_view_data);
} catch (Exception $ex) {
$view_data = array();
}
return id(new PhabricatorApplicationTransactionResponse())
->setObject($object)
->setViewer($viewer)
->setTransactions($xactions)
->setIsPreview($is_preview)
->setViewData($view_data)
->setPreviewContent($preview_content);
} else {
return id(new AphrontRedirectResponse())
->setURI($view_uri);
}
}
protected function newDraftEngine($object) {
$viewer = $this->getViewer();
if ($object instanceof PhabricatorDraftInterface) {
$engine = $object->newDraftEngine();
} else {
$engine = new PhabricatorBuiltinDraftEngine();
}
return $engine
->setObject($object)
->setViewer($viewer);
}
/* -( Conduit )------------------------------------------------------------ */
/**
* Respond to a Conduit edit request.
*
* This method accepts a list of transactions to apply to an object, and
* either edits an existing object or creates a new one.
*
* @task conduit
*/
final public function buildConduitResponse(ConduitAPIRequest $request) {
$viewer = $this->getViewer();
$config = $this->loadDefaultConfiguration();
if (!$config) {
throw new Exception(
pht(
'Unable to load configuration for this EditEngine ("%s").',
get_class($this)));
}
$raw_xactions = $this->getRawConduitTransactions($request);
$identifier = $request->getValue('objectIdentifier');
if ($identifier) {
$this->setIsCreate(false);
// After T13186, each transaction can individually weaken or replace the
// capabilities required to apply it, so we no longer need CAN_EDIT to
// attempt to apply transactions to objects. In practice, almost all
// transactions require CAN_EDIT so we won't get very far if we don't
// have it.
$capabilities = array(
PhabricatorPolicyCapability::CAN_VIEW,
);
$object = $this->newObjectFromIdentifier(
$identifier,
$capabilities);
} else {
$this->requireCreateCapability();
$this->setIsCreate(true);
$object = $this->newEditableObjectFromConduit($raw_xactions);
}
$this->validateObject($object);
$fields = $this->buildEditFields($object);
$types = $this->getConduitEditTypesFromFields($fields);
$template = $object->getApplicationTransactionTemplate();
$xactions = $this->getConduitTransactions(
$request,
$raw_xactions,
$types,
$template);
$editor = $object->getApplicationTransactionEditor()
->setActor($viewer)
->setContentSource($request->newContentSource())
->setContinueOnNoEffect(true);
if (!$this->getIsCreate()) {
$editor->setContinueOnMissingFields(true);
}
$xactions = $editor->applyTransactions($object, $xactions);
$xactions_struct = array();
foreach ($xactions as $xaction) {
$xactions_struct[] = array(
'phid' => $xaction->getPHID(),
);
}
return array(
'object' => array(
'id' => (int)$object->getID(),
'phid' => $object->getPHID(),
),
'transactions' => $xactions_struct,
);
}
private function getRawConduitTransactions(ConduitAPIRequest $request) {
$transactions_key = 'transactions';
$xactions = $request->getValue($transactions_key);
if (!is_array($xactions)) {
throw new Exception(
pht(
'Parameter "%s" is not a list of transactions.',
$transactions_key));
}
foreach ($xactions as $key => $xaction) {
if (!is_array($xaction)) {
throw new Exception(
pht(
'Parameter "%s" must contain a list of transaction descriptions, '.
'but item with key "%s" is not a dictionary.',
$transactions_key,
$key));
}
if (!array_key_exists('type', $xaction)) {
throw new Exception(
pht(
'Parameter "%s" must contain a list of transaction descriptions, '.
'but item with key "%s" is missing a "type" field. Each '.
'transaction must have a type field.',
$transactions_key,
$key));
}
if (!array_key_exists('value', $xaction)) {
throw new Exception(
pht(
'Parameter "%s" must contain a list of transaction descriptions, '.
'but item with key "%s" is missing a "value" field. Each '.
'transaction must have a value field.',
$transactions_key,
$key));
}
}
return $xactions;
}
/**
* Generate transactions which can be applied from edit actions in a Conduit
* request.
*
* @param ConduitAPIRequest The request.
* @param list<wild> Raw conduit transactions.
* @param list<PhabricatorEditType> Supported edit types.
* @param PhabricatorApplicationTransaction Template transaction.
* @return list<PhabricatorApplicationTransaction> Generated transactions.
* @task conduit
*/
private function getConduitTransactions(
ConduitAPIRequest $request,
array $xactions,
array $types,
PhabricatorApplicationTransaction $template) {
$viewer = $request->getUser();
$results = array();
foreach ($xactions as $key => $xaction) {
$type = $xaction['type'];
if (empty($types[$type])) {
throw new Exception(
pht(
'Transaction with key "%s" has invalid type "%s". This type is '.
'not recognized. Valid types are: %s.',
$key,
$type,
implode(', ', array_keys($types))));
}
}
if ($this->getIsCreate()) {
$results[] = id(clone $template)
->setTransactionType(PhabricatorTransactions::TYPE_CREATE);
}
$is_strict = $request->getIsStrictlyTyped();
foreach ($xactions as $xaction) {
$type = $types[$xaction['type']];
// Let the parameter type interpret the value. This allows you to
// use usernames in list<user> fields, for example.
$parameter_type = $type->getConduitParameterType();
$parameter_type->setViewer($viewer);
try {
$value = $xaction['value'];
$value = $parameter_type->getValue($xaction, 'value', $is_strict);
$value = $type->getTransactionValueFromConduit($value);
$xaction['value'] = $value;
} catch (Exception $ex) {
throw new PhutilProxyException(
pht(
'Exception when processing transaction of type "%s": %s',
$xaction['type'],
$ex->getMessage()),
$ex);
}
$type_xactions = $type->generateTransactions(
clone $template,
$xaction);
foreach ($type_xactions as $type_xaction) {
$results[] = $type_xaction;
}
}
return $results;
}
/**
* @return map<string, PhabricatorEditType>
* @task conduit
*/
private function getConduitEditTypesFromFields(array $fields) {
$types = array();
foreach ($fields as $field) {
$field_types = $field->getConduitEditTypes();
if ($field_types === null) {
continue;
}
foreach ($field_types as $field_type) {
$types[$field_type->getEditType()] = $field_type;
}
}
return $types;
}
public function getConduitEditTypes() {
$config = $this->loadDefaultConfiguration();
if (!$config) {
return array();
}
$object = $this->newEditableObjectForDocumentation();
$fields = $this->buildEditFields($object);
return $this->getConduitEditTypesFromFields($fields);
}
final public static function getAllEditEngines() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getEngineKey')
->execute();
}
final public static function getByKey(PhabricatorUser $viewer, $key) {
return id(new PhabricatorEditEngineQuery())
->setViewer($viewer)
->withEngineKeys(array($key))
->executeOne();
}
public function getIcon() {
$application = $this->getApplication();
return $application->getIcon();
}
private function loadUsableConfigurationsForCreate() {
$viewer = $this->getViewer();
$configs = id(new PhabricatorEditEngineConfigurationQuery())
->setViewer($viewer)
->withEngineKeys(array($this->getEngineKey()))
->withIsDefault(true)
->withIsDisabled(false)
->execute();
$configs = msort($configs, 'getCreateSortKey');
// Attach this specific engine to configurations we load so they can access
// any runtime configuration. For example, this allows us to generate the
// correct "Create Form" buttons when editing forms, see T12301.
foreach ($configs as $config) {
$config->attachEngine($this);
}
return $configs;
}
protected function getValidationExceptionShortMessage(
PhabricatorApplicationTransactionValidationException $ex,
PhabricatorEditField $field) {
$xaction_type = $field->getTransactionType();
if ($xaction_type === null) {
return null;
}
return $ex->getShortMessage($xaction_type);
}
protected function getCreateNewObjectPolicy() {
return PhabricatorPolicies::POLICY_USER;
}
private function requireCreateCapability() {
PhabricatorPolicyFilter::requireCapability(
$this->getViewer(),
$this,
PhabricatorPolicyCapability::CAN_EDIT);
}
private function hasCreateCapability() {
return PhabricatorPolicyFilter::hasCapability(
$this->getViewer(),
$this,
PhabricatorPolicyCapability::CAN_EDIT);
}
public function isCommentAction() {
return ($this->getEditAction() == 'comment');
}
public function getEditAction() {
$controller = $this->getController();
$request = $controller->getRequest();
return $request->getURIData('editAction');
}
protected function newCommentActionGroups() {
return array();
}
protected function newAutomaticCommentTransactions($object) {
return array();
}
protected function newCommentPreviewContent($object, array $xactions) {
return null;
}
/* -( Form Pages )--------------------------------------------------------- */
public function getSelectedPage() {
return $this->page;
}
private function selectPage($object, $page_key) {
$pages = $this->getPages($object);
if (empty($pages[$page_key])) {
return null;
}
$this->page = $pages[$page_key];
return $this->page;
}
protected function newPages($object) {
return array();
}
protected function getPages($object) {
if ($this->pages === null) {
$pages = $this->newPages($object);
assert_instances_of($pages, 'PhabricatorEditPage');
$pages = mpull($pages, null, 'getKey');
$this->pages = $pages;
}
return $this->pages;
}
private function applyPageToFields($object, array $fields) {
$pages = $this->getPages($object);
if (!$pages) {
return $fields;
}
if (!$this->getSelectedPage()) {
return $fields;
}
$page_picks = array();
$default_key = head($pages)->getKey();
foreach ($pages as $page_key => $page) {
foreach ($page->getFieldKeys() as $field_key) {
$page_picks[$field_key] = $page_key;
}
if ($page->getIsDefault()) {
$default_key = $page_key;
}
}
$page_map = array_fill_keys(array_keys($pages), array());
foreach ($fields as $field_key => $field) {
if (isset($page_picks[$field_key])) {
$page_map[$page_picks[$field_key]][$field_key] = $field;
continue;
}
// TODO: Maybe let the field pick a page to associate itself with so
// extensions can force themselves onto a particular page?
$page_map[$default_key][$field_key] = $field;
}
$page = $this->getSelectedPage();
if (!$page) {
$page = head($pages);
}
$selected_key = $page->getKey();
return $page_map[$selected_key];
}
protected function willApplyTransactions($object, array $xactions) {
return $xactions;
}
protected function didApplyTransactions($object, array $xactions) {
return;
}
/* -( Bulk Edits )--------------------------------------------------------- */
final public function newBulkEditGroupMap() {
$groups = $this->newBulkEditGroups();
$map = array();
foreach ($groups as $group) {
$key = $group->getKey();
if (isset($map[$key])) {
throw new Exception(
pht(
'Two bulk edit groups have the same key ("%s"). Each bulk edit '.
'group must have a unique key.',
$key));
}
$map[$key] = $group;
}
if ($this->isEngineExtensible()) {
$extensions = PhabricatorEditEngineExtension::getAllEnabledExtensions();
} else {
$extensions = array();
}
foreach ($extensions as $extension) {
$extension_groups = $extension->newBulkEditGroups($this);
foreach ($extension_groups as $group) {
$key = $group->getKey();
if (isset($map[$key])) {
throw new Exception(
pht(
'Extension "%s" defines a bulk edit group with the same key '.
'("%s") as the main editor or another extension. Each bulk '.
'edit group must have a unique key.',
get_class($extension),
$key));
}
$map[$key] = $group;
}
}
return $map;
}
protected function newBulkEditGroups() {
return array(
id(new PhabricatorBulkEditGroup())
->setKey('default')
->setLabel(pht('Primary Fields')),
id(new PhabricatorBulkEditGroup())
->setKey('extension')
->setLabel(pht('Support Applications')),
);
}
final public function newBulkEditMap() {
$viewer = $this->getViewer();
$config = $this->loadDefaultConfiguration();
if (!$config) {
throw new Exception(
pht('No default edit engine configuration for bulk edit.'));
}
$object = $this->newEditableObject();
$fields = $this->buildEditFields($object);
$groups = $this->newBulkEditGroupMap();
$edit_types = $this->getBulkEditTypesFromFields($fields);
$map = array();
foreach ($edit_types as $key => $type) {
$bulk_type = $type->getBulkParameterType();
if ($bulk_type === null) {
continue;
}
$bulk_type->setViewer($viewer);
$bulk_label = $type->getBulkEditLabel();
if ($bulk_label === null) {
continue;
}
$group_key = $type->getBulkEditGroupKey();
if (!$group_key) {
$group_key = 'default';
}
if (!isset($groups[$group_key])) {
throw new Exception(
pht(
'Field "%s" has a bulk edit group key ("%s") with no '.
'corresponding bulk edit group.',
$key,
$group_key));
}
$map[] = array(
'label' => $bulk_label,
'xaction' => $key,
'group' => $group_key,
'control' => array(
'type' => $bulk_type->getPHUIXControlType(),
'spec' => (object)$bulk_type->getPHUIXControlSpecification(),
),
);
}
return $map;
}
final public function newRawBulkTransactions(array $xactions) {
$config = $this->loadDefaultConfiguration();
if (!$config) {
throw new Exception(
pht('No default edit engine configuration for bulk edit.'));
}
$object = $this->newEditableObject();
$fields = $this->buildEditFields($object);
$edit_types = $this->getBulkEditTypesFromFields($fields);
$template = $object->getApplicationTransactionTemplate();
$raw_xactions = array();
foreach ($xactions as $key => $xaction) {
PhutilTypeSpec::checkMap(
$xaction,
array(
'type' => 'string',
'value' => 'optional wild',
));
$type = $xaction['type'];
if (!isset($edit_types[$type])) {
throw new Exception(
pht(
'Unsupported bulk edit type "%s".',
$type));
}
$edit_type = $edit_types[$type];
// Replace the edit type with the underlying transaction type. Usually
// these are 1:1 and the transaction type just has more internal noise,
// but it's possible that this isn't the case.
$xaction['type'] = $edit_type->getTransactionType();
$value = $xaction['value'];
$value = $edit_type->getTransactionValueFromBulkEdit($value);
$xaction['value'] = $value;
$xaction_objects = $edit_type->generateTransactions(
clone $template,
$xaction);
foreach ($xaction_objects as $xaction_object) {
$raw_xaction = array(
'type' => $xaction_object->getTransactionType(),
'metadata' => $xaction_object->getMetadata(),
'new' => $xaction_object->getNewValue(),
);
if ($xaction_object->hasOldValue()) {
$raw_xaction['old'] = $xaction_object->getOldValue();
}
if ($xaction_object->hasComment()) {
$comment = $xaction_object->getComment();
$raw_xaction['comment'] = $comment->getContent();
}
$raw_xactions[] = $raw_xaction;
}
}
return $raw_xactions;
}
private function getBulkEditTypesFromFields(array $fields) {
$types = array();
foreach ($fields as $field) {
$field_types = $field->getBulkEditTypes();
if ($field_types === null) {
continue;
}
foreach ($field_types as $field_type) {
$types[$field_type->getEditType()] = $field_type;
}
}
return $types;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getPHID() {
return get_class($this);
}
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return PhabricatorPolicies::getMostOpenPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getCreateNewObjectPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
}
diff --git a/src/applications/transactions/editfield/PhabricatorRemarkupEditField.php b/src/applications/transactions/editfield/PhabricatorRemarkupEditField.php
index 039f0b368f..a299aa0b5c 100644
--- a/src/applications/transactions/editfield/PhabricatorRemarkupEditField.php
+++ b/src/applications/transactions/editfield/PhabricatorRemarkupEditField.php
@@ -1,18 +1,47 @@
<?php
final class PhabricatorRemarkupEditField
extends PhabricatorEditField {
protected function newControl() {
return new PhabricatorRemarkupControl();
}
+ protected function newHTTPParameterType() {
+ return new AphrontRemarkupHTTPParameterType();
+ }
+
protected function newConduitParameterType() {
return new ConduitStringParameterType();
}
protected function newBulkParameterType() {
return new BulkRemarkupParameterType();
}
+ public function getValueForTransaction() {
+ $value = $this->getValue();
+
+ if ($value instanceof RemarkupValue) {
+ $value = $value->getCorpus();
+ }
+
+ return $value;
+ }
+
+ public function getMetadata() {
+ $defaults = array();
+
+ $value = $this->getValue();
+ if ($value instanceof RemarkupValue) {
+ $defaults['remarkup.control'] = $value->getMetadata();
+ }
+
+ $metadata = parent::getMetadata();
+ $metadata = $metadata + $defaults;
+
+ return $metadata;
+ }
+
+
}
diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php
index b2405d90c4..dea9e9e4f7 100644
--- a/src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php
+++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php
@@ -1,289 +1,271 @@
<?php
final class PhabricatorApplicationTransactionCommentEditor
extends PhabricatorEditor {
private $contentSource;
private $actingAsPHID;
private $request;
private $cancelURI;
private $isNewComment;
public function setActingAsPHID($acting_as_phid) {
$this->actingAsPHID = $acting_as_phid;
return $this;
}
public function getActingAsPHID() {
if ($this->actingAsPHID) {
return $this->actingAsPHID;
}
return $this->getActor()->getPHID();
}
public function setContentSource(PhabricatorContentSource $content_source) {
$this->contentSource = $content_source;
return $this;
}
public function getContentSource() {
return $this->contentSource;
}
public function setRequest(AphrontRequest $request) {
$this->request = $request;
return $this;
}
public function getRequest() {
return $this->request;
}
public function setCancelURI($cancel_uri) {
$this->cancelURI = $cancel_uri;
return $this;
}
public function getCancelURI() {
return $this->cancelURI;
}
public function setIsNewComment($is_new) {
$this->isNewComment = $is_new;
return $this;
}
public function getIsNewComment() {
return $this->isNewComment;
}
/**
* Edit a transaction's comment. This method effects the required create,
* update or delete to set the transaction's comment to the provided comment.
*/
public function applyEdit(
PhabricatorApplicationTransaction $xaction,
PhabricatorApplicationTransactionComment $comment) {
$this->validateEdit($xaction, $comment);
$actor = $this->requireActor();
$this->applyMFAChecks($xaction, $comment);
$comment->setContentSource($this->getContentSource());
$comment->setAuthorPHID($this->getActingAsPHID());
// TODO: This needs to be more sophisticated once we have meta-policies.
$comment->setViewPolicy(PhabricatorPolicies::POLICY_PUBLIC);
$comment->setEditPolicy($this->getActingAsPHID());
- $file_phids = PhabricatorMarkupEngine::extractFilePHIDsFromEmbeddedFiles(
- $actor,
- array(
- $comment->getContent(),
- ));
-
$xaction->openTransaction();
$xaction->beginReadLocking();
if ($xaction->getID()) {
$xaction->reload();
}
$new_version = $xaction->getCommentVersion() + 1;
$comment->setCommentVersion($new_version);
$comment->setTransactionPHID($xaction->getPHID());
$comment->save();
$old_comment = $xaction->getComment();
$comment->attachOldComment($old_comment);
$xaction->setCommentVersion($new_version);
$xaction->setCommentPHID($comment->getPHID());
$xaction->setViewPolicy($comment->getViewPolicy());
$xaction->setEditPolicy($comment->getEditPolicy());
$xaction->save();
$xaction->attachComment($comment);
// For comment edits, we need to make sure there are no automagical
// transactions like adding mentions or projects.
if ($new_version > 1) {
$object = id(new PhabricatorObjectQuery())
->withPHIDs(array($xaction->getObjectPHID()))
->setViewer($this->getActor())
->executeOne();
if ($object &&
$object instanceof PhabricatorApplicationTransactionInterface) {
$editor = $object->getApplicationTransactionEditor();
$editor->setActor($this->getActor());
$support_xactions = $editor->getExpandedSupportTransactions(
$object,
$xaction);
if ($support_xactions) {
$editor
->setContentSource($this->getContentSource())
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($object, $support_xactions);
}
}
}
$xaction->endReadLocking();
$xaction->saveTransaction();
- // Add links to any files newly referenced by the edit.
- if ($file_phids) {
- $editor = new PhabricatorEdgeEditor();
- foreach ($file_phids as $file_phid) {
- $editor->addEdge(
- $xaction->getObjectPHID(),
- PhabricatorObjectHasFileEdgeType::EDGECONST ,
- $file_phid);
- }
- $editor->save();
- }
-
return $this;
}
/**
* Validate that the edit is permissible, and the actor has permission to
* perform it.
*/
private function validateEdit(
PhabricatorApplicationTransaction $xaction,
PhabricatorApplicationTransactionComment $comment) {
if (!$xaction->getPHID()) {
throw new Exception(
pht(
'Transaction must have a PHID before calling %s!',
'applyEdit()'));
}
$type_comment = PhabricatorTransactions::TYPE_COMMENT;
if ($xaction->getTransactionType() == $type_comment) {
if ($comment->getPHID()) {
throw new Exception(
pht('Transaction comment must not yet have a PHID!'));
}
}
if (!$this->getContentSource()) {
throw new PhutilInvalidStateException('applyEdit');
}
$actor = $this->requireActor();
PhabricatorPolicyFilter::requireCapability(
$actor,
$xaction,
PhabricatorPolicyCapability::CAN_VIEW);
if ($comment->getIsRemoved() && $actor->getIsAdmin()) {
// NOTE: Administrators can remove comments by any user, and don't need
// to pass the edit check.
} else {
PhabricatorPolicyFilter::requireCapability(
$actor,
$xaction,
PhabricatorPolicyCapability::CAN_EDIT);
PhabricatorPolicyFilter::requireCanInteract(
$actor,
$xaction->getObject());
}
}
private function applyMFAChecks(
PhabricatorApplicationTransaction $xaction,
PhabricatorApplicationTransactionComment $comment) {
$actor = $this->requireActor();
// We don't do any MFA checks here when you're creating a comment for the
// first time (the parent editor handles them for us), so we can just bail
// out if this is the creation flow.
if ($this->getIsNewComment()) {
return;
}
$request = $this->getRequest();
if (!$request) {
throw new PhutilInvalidStateException('setRequest');
}
$cancel_uri = $this->getCancelURI();
if (!strlen($cancel_uri)) {
throw new PhutilInvalidStateException('setCancelURI');
}
// If you're deleting a comment, we try to prompt you for MFA if you have
// it configured, but do not require that you have it configured. In most
// cases, this is administrators removing content.
// See PHI1173. If you're editing a comment you authored and the original
// comment was signed with MFA, you MUST have MFA on your account and you
// MUST sign the edit with MFA. Otherwise, we can end up with an MFA badge
// on different content than what was signed.
$want_mfa = false;
$need_mfa = false;
if ($comment->getIsRemoved()) {
// Try to prompt on removal.
$want_mfa = true;
}
if ($xaction->getIsMFATransaction()) {
if ($actor->getPHID() === $xaction->getAuthorPHID()) {
// Strictly require MFA if the original transaction was signed and
// you're the author.
$want_mfa = true;
$need_mfa = true;
}
}
if (!$want_mfa) {
return;
}
if ($need_mfa) {
$factors = id(new PhabricatorAuthFactorConfigQuery())
->setViewer($actor)
->withUserPHIDs(array($this->getActingAsPHID()))
->withFactorProviderStatuses(
array(
PhabricatorAuthFactorProviderStatus::STATUS_ACTIVE,
PhabricatorAuthFactorProviderStatus::STATUS_DEPRECATED,
))
->execute();
if (!$factors) {
$error = new PhabricatorApplicationTransactionValidationError(
$xaction->getTransactionType(),
pht('No MFA'),
pht(
'This comment was signed with MFA, so edits to it must also be '.
'signed with MFA. You do not have any MFA factors attached to '.
'your account, so you can not sign this edit. Add MFA to your '.
'account in Settings.'),
$xaction);
throw new PhabricatorApplicationTransactionValidationException(
array(
$error,
));
}
}
$workflow_key = sprintf(
'comment.edit(%s, %d)',
$xaction->getPHID(),
$xaction->getComment()->getID());
$hisec_token = id(new PhabricatorAuthSessionEngine())
->setWorkflowKey($workflow_key)
->requireHighSecurityToken($actor, $request, $cancel_uri);
}
}
diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
index 8d8dfeea5f..77b5fbfbb6 100644
--- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
+++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
@@ -1,5429 +1,5745 @@
<?php
/**
*
* Publishing and Managing State
* ======
*
* After applying changes, the Editor queues a worker to publish mail, feed,
* and notifications, and to perform other background work like updating search
* indexes. This allows it to do this work without impacting performance for
* users.
*
* When work is moved to the daemons, the Editor state is serialized by
* @{method:getWorkerState}, then reloaded in a daemon process by
* @{method:loadWorkerState}. **This is fragile.**
*
* State is not persisted into the daemons by default, because we can not send
* arbitrary objects into the queue. This means the default behavior of any
* state properties is to reset to their defaults without warning prior to
* publishing.
*
* The easiest way to avoid this is to keep Editors stateless: the overwhelming
* majority of Editors can be written statelessly. If you need to maintain
* state, you can either:
*
* - not require state to exist during publishing; or
* - pass state to the daemons by implementing @{method:getCustomWorkerState}
* and @{method:loadCustomWorkerState}.
*
* This architecture isn't ideal, and we may eventually split this class into
* "Editor" and "Publisher" parts to make it more robust. See T6367 for some
* discussion and context.
*
* @task mail Sending Mail
* @task feed Publishing Feed Stories
* @task search Search Index
* @task files Integration with Files
* @task workers Managing Workers
*/
abstract class PhabricatorApplicationTransactionEditor
extends PhabricatorEditor {
private $contentSource;
private $object;
private $xactions;
private $isNewObject;
private $mentionedPHIDs;
private $continueOnNoEffect;
private $continueOnMissingFields;
private $raiseWarnings;
private $parentMessageID;
private $heraldAdapter;
private $heraldTranscript;
private $subscribers;
private $unmentionablePHIDMap = array();
private $transactionGroupID;
private $applicationEmail;
private $isPreview;
private $isHeraldEditor;
private $isInverseEdgeEditor;
private $actingAsPHID;
private $heraldEmailPHIDs = array();
private $heraldForcedEmailPHIDs = array();
private $heraldHeader;
private $mailToPHIDs = array();
private $mailCCPHIDs = array();
private $feedNotifyPHIDs = array();
private $feedRelatedPHIDs = array();
private $feedShouldPublish = false;
private $mailShouldSend = false;
private $modularTypes;
private $silent;
private $mustEncrypt = array();
private $stampTemplates = array();
private $mailStamps = array();
private $oldTo = array();
private $oldCC = array();
private $mailRemovedPHIDs = array();
private $mailUnexpandablePHIDs = array();
private $mailMutedPHIDs = array();
private $webhookMap = array();
private $transactionQueue = array();
private $sendHistory = false;
private $shouldRequireMFA = false;
private $hasRequiredMFA = false;
private $request;
private $cancelURI;
private $extensions;
private $parentEditor;
private $subEditors = array();
private $publishableObject;
private $publishableTransactions;
const STORAGE_ENCODING_BINARY = 'binary';
/**
* Get the class name for the application this editor is a part of.
*
* Uninstalling the application will disable the editor.
*
* @return string Editor's application class name.
*/
abstract public function getEditorApplicationClass();
/**
* Get a description of the objects this editor edits, like "Differential
* Revisions".
*
* @return string Human readable description of edited objects.
*/
abstract public function getEditorObjectsDescription();
public function setActingAsPHID($acting_as_phid) {
$this->actingAsPHID = $acting_as_phid;
return $this;
}
public function getActingAsPHID() {
if ($this->actingAsPHID) {
return $this->actingAsPHID;
}
return $this->getActor()->getPHID();
}
/**
* When the editor tries to apply transactions that have no effect, should
* it raise an exception (default) or drop them and continue?
*
* Generally, you will set this flag for edits coming from "Edit" interfaces,
* and leave it cleared for edits coming from "Comment" interfaces, so the
* user will get a useful error if they try to submit a comment that does
* nothing (e.g., empty comment with a status change that has already been
* performed by another user).
*
* @param bool True to drop transactions without effect and continue.
* @return this
*/
public function setContinueOnNoEffect($continue) {
$this->continueOnNoEffect = $continue;
return $this;
}
public function getContinueOnNoEffect() {
return $this->continueOnNoEffect;
}
/**
* When the editor tries to apply transactions which don't populate all of
* an object's required fields, should it raise an exception (default) or
* drop them and continue?
*
* For example, if a user adds a new required custom field (like "Severity")
* to a task, all existing tasks won't have it populated. When users
* manually edit existing tasks, it's usually desirable to have them provide
* a severity. However, other operations (like batch editing just the
* owner of a task) will fail by default.
*
* By setting this flag for edit operations which apply to specific fields
* (like the priority, batch, and merge editors in Maniphest), these
* operations can continue to function even if an object is outdated.
*
* @param bool True to continue when transactions don't completely satisfy
* all required fields.
* @return this
*/
public function setContinueOnMissingFields($continue_on_missing_fields) {
$this->continueOnMissingFields = $continue_on_missing_fields;
return $this;
}
public function getContinueOnMissingFields() {
return $this->continueOnMissingFields;
}
/**
* Not strictly necessary, but reply handlers ideally set this value to
* make email threading work better.
*/
public function setParentMessageID($parent_message_id) {
$this->parentMessageID = $parent_message_id;
return $this;
}
public function getParentMessageID() {
return $this->parentMessageID;
}
public function getIsNewObject() {
return $this->isNewObject;
}
public function getMentionedPHIDs() {
return $this->mentionedPHIDs;
}
public function setIsPreview($is_preview) {
$this->isPreview = $is_preview;
return $this;
}
public function getIsPreview() {
return $this->isPreview;
}
public function setIsSilent($silent) {
$this->silent = $silent;
return $this;
}
public function getIsSilent() {
return $this->silent;
}
public function getMustEncrypt() {
return $this->mustEncrypt;
}
public function getHeraldRuleMonograms() {
// Convert the stored "<123>, <456>" string into a list: "H123", "H456".
- $list = $this->heraldHeader;
+ $list = phutil_string_cast($this->heraldHeader);
$list = preg_split('/[, ]+/', $list);
foreach ($list as $key => $item) {
$item = trim($item, '<>');
if (!is_numeric($item)) {
unset($list[$key]);
continue;
}
$list[$key] = 'H'.$item;
}
return $list;
}
public function setIsInverseEdgeEditor($is_inverse_edge_editor) {
$this->isInverseEdgeEditor = $is_inverse_edge_editor;
return $this;
}
public function getIsInverseEdgeEditor() {
return $this->isInverseEdgeEditor;
}
public function setIsHeraldEditor($is_herald_editor) {
$this->isHeraldEditor = $is_herald_editor;
return $this;
}
public function getIsHeraldEditor() {
return $this->isHeraldEditor;
}
public function addUnmentionablePHIDs(array $phids) {
foreach ($phids as $phid) {
$this->unmentionablePHIDMap[$phid] = true;
}
return $this;
}
private function getUnmentionablePHIDMap() {
return $this->unmentionablePHIDMap;
}
protected function shouldEnableMentions(
PhabricatorLiskDAO $object,
array $xactions) {
return true;
}
public function setApplicationEmail(
PhabricatorMetaMTAApplicationEmail $email) {
$this->applicationEmail = $email;
return $this;
}
public function getApplicationEmail() {
return $this->applicationEmail;
}
public function setRaiseWarnings($raise_warnings) {
$this->raiseWarnings = $raise_warnings;
return $this;
}
public function getRaiseWarnings() {
return $this->raiseWarnings;
}
public function setShouldRequireMFA($should_require_mfa) {
if ($this->hasRequiredMFA) {
throw new Exception(
pht(
'Call to setShouldRequireMFA() is too late: this Editor has already '.
'checked for MFA requirements.'));
}
$this->shouldRequireMFA = $should_require_mfa;
return $this;
}
public function getShouldRequireMFA() {
return $this->shouldRequireMFA;
}
public function getTransactionTypesForObject($object) {
$old = $this->object;
try {
$this->object = $object;
$result = $this->getTransactionTypes();
$this->object = $old;
} catch (Exception $ex) {
$this->object = $old;
throw $ex;
}
return $result;
}
public function getTransactionTypes() {
$types = array();
$types[] = PhabricatorTransactions::TYPE_CREATE;
$types[] = PhabricatorTransactions::TYPE_HISTORY;
+ $types[] = PhabricatorTransactions::TYPE_FILE;
+
if ($this->object instanceof PhabricatorEditEngineSubtypeInterface) {
$types[] = PhabricatorTransactions::TYPE_SUBTYPE;
}
if ($this->object instanceof PhabricatorSubscribableInterface) {
$types[] = PhabricatorTransactions::TYPE_SUBSCRIBERS;
}
if ($this->object instanceof PhabricatorCustomFieldInterface) {
$types[] = PhabricatorTransactions::TYPE_CUSTOMFIELD;
}
if ($this->object instanceof PhabricatorTokenReceiverInterface) {
$types[] = PhabricatorTransactions::TYPE_TOKEN;
}
if ($this->object instanceof PhabricatorProjectInterface ||
$this->object instanceof PhabricatorMentionableInterface) {
$types[] = PhabricatorTransactions::TYPE_EDGE;
}
if ($this->object instanceof PhabricatorSpacesInterface) {
$types[] = PhabricatorTransactions::TYPE_SPACE;
}
$types[] = PhabricatorTransactions::TYPE_MFA;
$template = $this->object->getApplicationTransactionTemplate();
if ($template instanceof PhabricatorModularTransaction) {
$xtypes = $template->newModularTransactionTypes();
foreach ($xtypes as $xtype) {
$types[] = $xtype->getTransactionTypeConstant();
}
}
if ($template) {
$comment = $template->getApplicationTransactionCommentObject();
if ($comment) {
$types[] = PhabricatorTransactions::TYPE_COMMENT;
}
}
return $types;
}
private function adjustTransactionValues(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
if ($xaction->shouldGenerateOldValue()) {
$old = $this->getTransactionOldValue($object, $xaction);
$xaction->setOldValue($old);
}
$new = $this->getTransactionNewValue($object, $xaction);
$xaction->setNewValue($new);
+
+ // Apply an optional transformation to convert "external" tranaction
+ // values (provided by APIs) into "internal" values.
+
+ $old = $xaction->getOldValue();
+ $new = $xaction->getNewValue();
+
+ $type = $xaction->getTransactionType();
+ $xtype = $this->getModularTransactionType($object, $type);
+ if ($xtype) {
+ $xtype = clone $xtype;
+ $xtype->setStorage($xaction);
+
+
+ // TODO: Provide a modular hook for modern transactions to do a
+ // transformation.
+ list($old, $new) = array($old, $new);
+
+ return;
+ } else {
+ switch ($type) {
+ case PhabricatorTransactions::TYPE_FILE:
+ list($old, $new) = $this->newFileTransactionInternalValues(
+ $object,
+ $xaction,
+ $old,
+ $new);
+ break;
+ }
+ }
+
+ $xaction->setOldValue($old);
+ $xaction->setNewValue($new);
+ }
+
+ private function newFileTransactionInternalValues(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction,
+ $old,
+ $new) {
+
+ $old_map = array();
+
+ if (!$this->getIsNewObject()) {
+ $phid = $object->getPHID();
+
+ $attachment_table = new PhabricatorFileAttachment();
+ $attachment_conn = $attachment_table->establishConnection('w');
+
+ $rows = queryfx_all(
+ $attachment_conn,
+ 'SELECT filePHID, attachmentMode FROM %R WHERE objectPHID = %s',
+ $attachment_table,
+ $phid);
+ $old_map = ipull($rows, 'attachmentMode', 'filePHID');
+ }
+
+ $mode_ref = PhabricatorFileAttachment::MODE_REFERENCE;
+ $mode_detach = PhabricatorFileAttachment::MODE_DETACH;
+
+ $new_map = $old_map;
+
+ foreach ($new as $file_phid => $attachment_mode) {
+ $is_ref = ($attachment_mode === $mode_ref);
+ $is_detach = ($attachment_mode === $mode_detach);
+
+ if ($is_detach) {
+ unset($new_map[$file_phid]);
+ continue;
+ }
+
+ $old_mode = idx($old_map, $file_phid);
+
+ // If we're adding a reference to a file but it is already attached,
+ // don't touch it.
+
+ if ($is_ref) {
+ if ($old_mode !== null) {
+ continue;
+ }
+ }
+
+ $new_map[$file_phid] = $attachment_mode;
+ }
+
+ foreach (array_keys($old_map + $new_map) as $key) {
+ if (isset($old_map[$key]) && isset($new_map[$key])) {
+ if ($old_map[$key] === $new_map[$key]) {
+ unset($old_map[$key]);
+ unset($new_map[$key]);
+ }
+ }
+ }
+
+ return array($old_map, $new_map);
}
private function getTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$type = $xaction->getTransactionType();
- $xtype = $this->getModularTransactionType($type);
+ $xtype = $this->getModularTransactionType($object, $type);
if ($xtype) {
$xtype = clone $xtype;
$xtype->setStorage($xaction);
return $xtype->generateOldValue($object);
}
switch ($type) {
case PhabricatorTransactions::TYPE_CREATE:
case PhabricatorTransactions::TYPE_HISTORY:
return null;
case PhabricatorTransactions::TYPE_SUBTYPE:
return $object->getEditEngineSubtype();
case PhabricatorTransactions::TYPE_MFA:
return null;
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
return array_values($this->subscribers);
case PhabricatorTransactions::TYPE_VIEW_POLICY:
if ($this->getIsNewObject()) {
return null;
}
return $object->getViewPolicy();
case PhabricatorTransactions::TYPE_EDIT_POLICY:
if ($this->getIsNewObject()) {
return null;
}
return $object->getEditPolicy();
case PhabricatorTransactions::TYPE_JOIN_POLICY:
if ($this->getIsNewObject()) {
return null;
}
return $object->getJoinPolicy();
case PhabricatorTransactions::TYPE_INTERACT_POLICY:
if ($this->getIsNewObject()) {
return null;
}
return $object->getInteractPolicy();
case PhabricatorTransactions::TYPE_SPACE:
if ($this->getIsNewObject()) {
return null;
}
$space_phid = $object->getSpacePHID();
if ($space_phid === null) {
$default_space = PhabricatorSpacesNamespaceQuery::getDefaultSpace();
if ($default_space) {
$space_phid = $default_space->getPHID();
}
}
return $space_phid;
case PhabricatorTransactions::TYPE_EDGE:
$edge_type = $xaction->getMetadataValue('edge:type');
if (!$edge_type) {
throw new Exception(
pht(
"Edge transaction has no '%s'!",
'edge:type'));
}
// See T13082. If this is an inverse edit, the parent editor has
// already populated the transaction values correctly.
if ($this->getIsInverseEdgeEditor()) {
return $xaction->getOldValue();
}
$old_edges = array();
if ($object->getPHID()) {
$edge_src = $object->getPHID();
$old_edges = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($edge_src))
->withEdgeTypes(array($edge_type))
->needEdgeData(true)
->execute();
$old_edges = $old_edges[$edge_src][$edge_type];
}
return $old_edges;
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
// NOTE: Custom fields have their old value pre-populated when they are
// built by PhabricatorCustomFieldList.
return $xaction->getOldValue();
case PhabricatorTransactions::TYPE_COMMENT:
return null;
+ case PhabricatorTransactions::TYPE_FILE:
+ return null;
default:
return $this->getCustomTransactionOldValue($object, $xaction);
}
}
private function getTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$type = $xaction->getTransactionType();
- $xtype = $this->getModularTransactionType($type);
+ $xtype = $this->getModularTransactionType($object, $type);
if ($xtype) {
$xtype = clone $xtype;
$xtype->setStorage($xaction);
return $xtype->generateNewValue($object, $xaction->getNewValue());
}
switch ($type) {
case PhabricatorTransactions::TYPE_CREATE:
return null;
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
return $this->getPHIDTransactionNewValue($xaction);
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
case PhabricatorTransactions::TYPE_INTERACT_POLICY:
case PhabricatorTransactions::TYPE_TOKEN:
case PhabricatorTransactions::TYPE_INLINESTATE:
case PhabricatorTransactions::TYPE_SUBTYPE:
case PhabricatorTransactions::TYPE_HISTORY:
+ case PhabricatorTransactions::TYPE_FILE:
return $xaction->getNewValue();
case PhabricatorTransactions::TYPE_MFA:
return true;
case PhabricatorTransactions::TYPE_SPACE:
$space_phid = $xaction->getNewValue();
if (!strlen($space_phid)) {
// If an install has no Spaces or the Spaces controls are not visible
// to the viewer, we might end up with the empty string here instead
// of a strict `null`, because some controller just used `getStr()`
// to read the space PHID from the request.
// Just make this work like callers might reasonably expect so we
// don't need to handle this specially in every EditController.
return $this->getActor()->getDefaultSpacePHID();
} else {
return $space_phid;
}
case PhabricatorTransactions::TYPE_EDGE:
// See T13082. If this is an inverse edit, the parent editor has
// already populated appropriate transaction values.
if ($this->getIsInverseEdgeEditor()) {
return $xaction->getNewValue();
}
$new_value = $this->getEdgeTransactionNewValue($xaction);
$edge_type = $xaction->getMetadataValue('edge:type');
$type_project = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
if ($edge_type == $type_project) {
$new_value = $this->applyProjectConflictRules($new_value);
}
return $new_value;
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getCustomFieldForTransaction($object, $xaction);
return $field->getNewValueFromApplicationTransactions($xaction);
case PhabricatorTransactions::TYPE_COMMENT:
return null;
default:
return $this->getCustomTransactionNewValue($object, $xaction);
}
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
throw new Exception(pht('Capability not supported!'));
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
throw new Exception(pht('Capability not supported!'));
}
protected function transactionHasEffect(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorTransactions::TYPE_CREATE:
case PhabricatorTransactions::TYPE_HISTORY:
return true;
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getCustomFieldForTransaction($object, $xaction);
return $field->getApplicationTransactionHasEffect($xaction);
case PhabricatorTransactions::TYPE_EDGE:
// A straight value comparison here doesn't always get the right
// result, because newly added edges aren't fully populated. Instead,
// compare the changes in a more granular way.
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$old_dst = array_keys($old);
$new_dst = array_keys($new);
// NOTE: For now, we don't consider edge reordering to be a change.
// We have very few order-dependent edges and effectively no order
// oriented UI. This might change in the future.
sort($old_dst);
sort($new_dst);
if ($old_dst !== $new_dst) {
// We've added or removed edges, so this transaction definitely
// has an effect.
return true;
}
// We haven't added or removed edges, but we might have changed
// edge data.
foreach ($old as $key => $old_value) {
$new_value = $new[$key];
if ($old_value['data'] !== $new_value['data']) {
return true;
}
}
return false;
}
$type = $xaction->getTransactionType();
- $xtype = $this->getModularTransactionType($type);
+ $xtype = $this->getModularTransactionType($object, $type);
if ($xtype) {
return $xtype->getTransactionHasEffect(
$object,
$xaction->getOldValue(),
$xaction->getNewValue());
}
if ($xaction->hasComment()) {
return true;
}
return ($xaction->getOldValue() !== $xaction->getNewValue());
}
protected function shouldApplyInitialEffects(
PhabricatorLiskDAO $object,
array $xactions) {
return false;
}
protected function applyInitialEffects(
PhabricatorLiskDAO $object,
array $xactions) {
throw new PhutilMethodNotImplementedException();
}
private function applyInternalEffects(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$type = $xaction->getTransactionType();
- $xtype = $this->getModularTransactionType($type);
+ $xtype = $this->getModularTransactionType($object, $type);
if ($xtype) {
$xtype = clone $xtype;
$xtype->setStorage($xaction);
return $xtype->applyInternalEffects($object, $xaction->getNewValue());
}
switch ($type) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getCustomFieldForTransaction($object, $xaction);
return $field->applyApplicationTransactionInternalEffects($xaction);
case PhabricatorTransactions::TYPE_CREATE:
case PhabricatorTransactions::TYPE_HISTORY:
case PhabricatorTransactions::TYPE_SUBTYPE:
case PhabricatorTransactions::TYPE_MFA:
case PhabricatorTransactions::TYPE_TOKEN:
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
case PhabricatorTransactions::TYPE_INTERACT_POLICY:
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
case PhabricatorTransactions::TYPE_INLINESTATE:
case PhabricatorTransactions::TYPE_EDGE:
case PhabricatorTransactions::TYPE_SPACE:
case PhabricatorTransactions::TYPE_COMMENT:
+ case PhabricatorTransactions::TYPE_FILE:
return $this->applyBuiltinInternalTransaction($object, $xaction);
}
return $this->applyCustomInternalTransaction($object, $xaction);
}
private function applyExternalEffects(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$type = $xaction->getTransactionType();
- $xtype = $this->getModularTransactionType($type);
+ $xtype = $this->getModularTransactionType($object, $type);
if ($xtype) {
$xtype = clone $xtype;
$xtype->setStorage($xaction);
return $xtype->applyExternalEffects($object, $xaction->getNewValue());
}
switch ($type) {
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
$subeditor = id(new PhabricatorSubscriptionsEditor())
->setObject($object)
->setActor($this->requireActor());
$old_map = array_fuse($xaction->getOldValue());
$new_map = array_fuse($xaction->getNewValue());
$subeditor->unsubscribe(
array_keys(
array_diff_key($old_map, $new_map)));
$subeditor->subscribeExplicit(
array_keys(
array_diff_key($new_map, $old_map)));
$subeditor->save();
// for the rest of these edits, subscribers should include those just
// added as well as those just removed.
$subscribers = array_unique(array_merge(
$this->subscribers,
$xaction->getOldValue(),
$xaction->getNewValue()));
$this->subscribers = $subscribers;
return $this->applyBuiltinExternalTransaction($object, $xaction);
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getCustomFieldForTransaction($object, $xaction);
return $field->applyApplicationTransactionExternalEffects($xaction);
case PhabricatorTransactions::TYPE_CREATE:
case PhabricatorTransactions::TYPE_HISTORY:
case PhabricatorTransactions::TYPE_SUBTYPE:
case PhabricatorTransactions::TYPE_MFA:
case PhabricatorTransactions::TYPE_EDGE:
case PhabricatorTransactions::TYPE_TOKEN:
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
case PhabricatorTransactions::TYPE_INTERACT_POLICY:
case PhabricatorTransactions::TYPE_INLINESTATE:
case PhabricatorTransactions::TYPE_SPACE:
case PhabricatorTransactions::TYPE_COMMENT:
+ case PhabricatorTransactions::TYPE_FILE:
return $this->applyBuiltinExternalTransaction($object, $xaction);
}
return $this->applyCustomExternalTransaction($object, $xaction);
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$type = $xaction->getTransactionType();
throw new Exception(
pht(
"Transaction type '%s' is missing an internal apply implementation!",
$type));
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$type = $xaction->getTransactionType();
throw new Exception(
pht(
"Transaction type '%s' is missing an external apply implementation!",
$type));
}
/**
* @{class:PhabricatorTransactions} provides many built-in transactions
* which should not require much - if any - code in specific applications.
*
* This method is a hook for the exceedingly-rare cases where you may need
* to do **additional** work for built-in transactions. Developers should
* extend this method, making sure to return the parent implementation
* regardless of handling any transactions.
*
* See also @{method:applyBuiltinExternalTransaction}.
*/
protected function applyBuiltinInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorTransactions::TYPE_VIEW_POLICY:
$object->setViewPolicy($xaction->getNewValue());
break;
case PhabricatorTransactions::TYPE_EDIT_POLICY:
$object->setEditPolicy($xaction->getNewValue());
break;
case PhabricatorTransactions::TYPE_JOIN_POLICY:
$object->setJoinPolicy($xaction->getNewValue());
break;
case PhabricatorTransactions::TYPE_INTERACT_POLICY:
$object->setInteractPolicy($xaction->getNewValue());
break;
case PhabricatorTransactions::TYPE_SPACE:
$object->setSpacePHID($xaction->getNewValue());
break;
case PhabricatorTransactions::TYPE_SUBTYPE:
$object->setEditEngineSubtype($xaction->getNewValue());
break;
}
}
/**
* See @{method::applyBuiltinInternalTransaction}.
*/
protected function applyBuiltinExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorTransactions::TYPE_EDGE:
if ($this->getIsInverseEdgeEditor()) {
// If we're writing an inverse edge transaction, don't actually
// do anything. The initiating editor on the other side of the
// transaction will take care of the edge writes.
break;
}
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$src = $object->getPHID();
$const = $xaction->getMetadataValue('edge:type');
foreach ($new as $dst_phid => $edge) {
$new[$dst_phid]['src'] = $src;
}
$editor = new PhabricatorEdgeEditor();
foreach ($old as $dst_phid => $edge) {
if (!empty($new[$dst_phid])) {
if ($old[$dst_phid]['data'] === $new[$dst_phid]['data']) {
continue;
}
}
$editor->removeEdge($src, $const, $dst_phid);
}
foreach ($new as $dst_phid => $edge) {
if (!empty($old[$dst_phid])) {
if ($old[$dst_phid]['data'] === $new[$dst_phid]['data']) {
continue;
}
}
$data = array(
'data' => $edge['data'],
);
$editor->addEdge($src, $const, $dst_phid, $data);
}
$editor->save();
$this->updateWorkboardColumns($object, $const, $old, $new);
break;
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_SPACE:
$this->scrambleFileSecrets($object);
break;
case PhabricatorTransactions::TYPE_HISTORY:
$this->sendHistory = true;
break;
+ case PhabricatorTransactions::TYPE_FILE:
+ $this->applyFileTransaction($object, $xaction);
+ break;
+ }
+ }
+
+ private function applyFileTransaction(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+
+ $old_map = $xaction->getOldValue();
+ $new_map = $xaction->getNewValue();
+
+ $add_phids = array();
+ $rem_phids = array();
+
+ foreach ($new_map as $phid => $mode) {
+ $add_phids[$phid] = $mode;
+ }
+
+ foreach ($old_map as $phid => $mode) {
+ if (!isset($new_map[$phid])) {
+ $rem_phids[] = $phid;
+ }
+ }
+
+ $now = PhabricatorTime::getNow();
+ $object_phid = $object->getPHID();
+ $attacher_phid = $this->getActingAsPHID();
+
+ $attachment_table = new PhabricatorFileAttachment();
+ $attachment_conn = $attachment_table->establishConnection('w');
+
+ $add_sql = array();
+ foreach ($add_phids as $add_phid => $add_mode) {
+ $add_sql[] = qsprintf(
+ $attachment_conn,
+ '(%s, %s, %s, %ns, %d, %d)',
+ $object_phid,
+ $add_phid,
+ $add_mode,
+ $attacher_phid,
+ $now,
+ $now);
+ }
+
+ $rem_sql = array();
+ foreach ($rem_phids as $rem_phid) {
+ $rem_sql[] = qsprintf(
+ $attachment_conn,
+ '%s',
+ $rem_phid);
+ }
+
+ foreach (PhabricatorLiskDAO::chunkSQL($add_sql) as $chunk) {
+ queryfx(
+ $attachment_conn,
+ 'INSERT INTO %R (objectPHID, filePHID, attachmentMode,
+ attacherPHID, dateCreated, dateModified)
+ VALUES %LQ
+ ON DUPLICATE KEY UPDATE
+ attachmentMode = VALUES(attachmentMode),
+ attacherPHID = VALUES(attacherPHID),
+ dateModified = VALUES(dateModified)',
+ $attachment_table,
+ $chunk);
+ }
+
+ foreach (PhabricatorLiskDAO::chunkSQL($rem_sql) as $chunk) {
+ queryfx(
+ $attachment_conn,
+ 'DELETE FROM %R WHERE objectPHID = %s AND filePHID in (%LQ)',
+ $attachment_table,
+ $object_phid,
+ $chunk);
}
}
/**
* Fill in a transaction's common values, like author and content source.
*/
protected function populateTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$actor = $this->getActor();
// TODO: This needs to be more sophisticated once we have meta-policies.
$xaction->setViewPolicy(PhabricatorPolicies::POLICY_PUBLIC);
if ($actor->isOmnipotent()) {
$xaction->setEditPolicy(PhabricatorPolicies::POLICY_NOONE);
} else {
$xaction->setEditPolicy($this->getActingAsPHID());
}
// If the transaction already has an explicit author PHID, allow it to
// stand. This is used by applications like Owners that hook into the
// post-apply change pipeline.
if (!$xaction->getAuthorPHID()) {
$xaction->setAuthorPHID($this->getActingAsPHID());
}
$xaction->setContentSource($this->getContentSource());
$xaction->attachViewer($actor);
$xaction->attachObject($object);
if ($object->getPHID()) {
$xaction->setObjectPHID($object->getPHID());
}
if ($this->getIsSilent()) {
$xaction->setIsSilentTransaction(true);
}
return $xaction;
}
protected function didApplyInternalEffects(
PhabricatorLiskDAO $object,
array $xactions) {
return $xactions;
}
protected function applyFinalEffects(
PhabricatorLiskDAO $object,
array $xactions) {
return $xactions;
}
final protected function didCommitTransactions(
PhabricatorLiskDAO $object,
array $xactions) {
foreach ($xactions as $xaction) {
$type = $xaction->getTransactionType();
// See T13082. When we're writing edges that imply corresponding inverse
// transactions, apply those inverse transactions now. We have to wait
// until the object we're editing (with this editor) has committed its
// transactions to do this. If we don't, the inverse editor may race,
// build a mail before we actually commit this object, and render "alice
// added an edge: Unknown Object".
if ($type === PhabricatorTransactions::TYPE_EDGE) {
// Don't do anything if we're already an inverse edge editor.
if ($this->getIsInverseEdgeEditor()) {
continue;
}
$edge_const = $xaction->getMetadataValue('edge:type');
$edge_type = PhabricatorEdgeType::getByConstant($edge_const);
if ($edge_type->shouldWriteInverseTransactions()) {
$this->applyInverseEdgeTransactions(
$object,
$xaction,
$edge_type->getInverseEdgeConstant());
}
continue;
}
- $xtype = $this->getModularTransactionType($type);
+ $xtype = $this->getModularTransactionType($object, $type);
if (!$xtype) {
continue;
}
$xtype = clone $xtype;
$xtype->setStorage($xaction);
$xtype->didCommitTransaction($object, $xaction->getNewValue());
}
}
public function setContentSource(PhabricatorContentSource $content_source) {
$this->contentSource = $content_source;
return $this;
}
public function setContentSourceFromRequest(AphrontRequest $request) {
$this->setRequest($request);
return $this->setContentSource(
PhabricatorContentSource::newFromRequest($request));
}
public function getContentSource() {
return $this->contentSource;
}
public function setRequest(AphrontRequest $request) {
$this->request = $request;
return $this;
}
public function getRequest() {
return $this->request;
}
public function setCancelURI($cancel_uri) {
$this->cancelURI = $cancel_uri;
return $this;
}
public function getCancelURI() {
return $this->cancelURI;
}
protected function getTransactionGroupID() {
if ($this->transactionGroupID === null) {
$this->transactionGroupID = Filesystem::readRandomCharacters(32);
}
return $this->transactionGroupID;
}
final public function applyTransactions(
PhabricatorLiskDAO $object,
array $xactions) {
$is_new = ($object->getID() === null);
$this->isNewObject = $is_new;
$is_preview = $this->getIsPreview();
$read_locking = false;
$transaction_open = false;
// If we're attempting to apply transactions, lock and reload the object
// before we go anywhere. If we don't do this at the very beginning, we
// may be looking at an older version of the object when we populate and
// filter the transactions. See PHI1165 for an example.
if (!$is_preview) {
if (!$is_new) {
$this->buildOldRecipientLists($object, $xactions);
$object->openTransaction();
$transaction_open = true;
$object->beginReadLocking();
$read_locking = true;
$object->reload();
}
}
try {
$this->object = $object;
$this->xactions = $xactions;
$this->validateEditParameters($object, $xactions);
$xactions = $this->newMFATransactions($object, $xactions);
$actor = $this->requireActor();
// NOTE: Some transaction expansion requires that the edited object be
// attached.
foreach ($xactions as $xaction) {
$xaction->attachObject($object);
$xaction->attachViewer($actor);
}
$xactions = $this->expandTransactions($object, $xactions);
$xactions = $this->expandSupportTransactions($object, $xactions);
$xactions = $this->combineTransactions($xactions);
foreach ($xactions as $xaction) {
$xaction = $this->populateTransaction($object, $xaction);
}
if (!$is_preview) {
$errors = array();
$type_map = mgroup($xactions, 'getTransactionType');
foreach ($this->getTransactionTypes() as $type) {
$type_xactions = idx($type_map, $type, array());
$errors[] = $this->validateTransaction(
$object,
$type,
$type_xactions);
}
$errors[] = $this->validateAllTransactions($object, $xactions);
$errors[] = $this->validateTransactionsWithExtensions(
$object,
$xactions);
$errors = array_mergev($errors);
$continue_on_missing = $this->getContinueOnMissingFields();
foreach ($errors as $key => $error) {
if ($continue_on_missing && $error->getIsMissingFieldError()) {
unset($errors[$key]);
}
}
if ($errors) {
throw new PhabricatorApplicationTransactionValidationException(
$errors);
}
if ($this->raiseWarnings) {
$warnings = array();
foreach ($xactions as $xaction) {
if ($this->hasWarnings($object, $xaction)) {
$warnings[] = $xaction;
}
}
if ($warnings) {
throw new PhabricatorApplicationTransactionWarningException(
$warnings);
}
}
}
foreach ($xactions as $xaction) {
$this->adjustTransactionValues($object, $xaction);
}
// Now that we've merged and combined transactions, check for required
// capabilities. Note that we're doing this before filtering
// transactions: if you try to apply an edit which you do not have
// permission to apply, we want to give you a permissions error even
// if the edit would have no effect.
$this->applyCapabilityChecks($object, $xactions);
$xactions = $this->filterTransactions($object, $xactions);
if (!$is_preview) {
$this->hasRequiredMFA = true;
if ($this->getShouldRequireMFA()) {
$this->requireMFA($object, $xactions);
}
if ($this->shouldApplyInitialEffects($object, $xactions)) {
if (!$transaction_open) {
$object->openTransaction();
$transaction_open = true;
}
}
}
if ($this->shouldApplyInitialEffects($object, $xactions)) {
$this->applyInitialEffects($object, $xactions);
}
// TODO: Once everything is on EditEngine, just use getIsNewObject() to
// figure this out instead.
$mark_as_create = false;
$create_type = PhabricatorTransactions::TYPE_CREATE;
foreach ($xactions as $xaction) {
if ($xaction->getTransactionType() == $create_type) {
$mark_as_create = true;
}
}
if ($mark_as_create) {
foreach ($xactions as $xaction) {
$xaction->setIsCreateTransaction(true);
}
}
$xactions = $this->sortTransactions($xactions);
- $file_phids = $this->extractFilePHIDs($object, $xactions);
if ($is_preview) {
$this->loadHandles($xactions);
return $xactions;
}
$comment_editor = id(new PhabricatorApplicationTransactionCommentEditor())
->setActor($actor)
->setActingAsPHID($this->getActingAsPHID())
->setContentSource($this->getContentSource())
->setIsNewComment(true);
if (!$transaction_open) {
$object->openTransaction();
$transaction_open = true;
}
// We can technically test any object for CAN_INTERACT, but we can
// run into some issues in doing so (for example, in project unit tests).
// For now, only test for CAN_INTERACT if the object is explicitly a
// lockable object.
$was_locked = false;
if ($object instanceof PhabricatorEditEngineLockableInterface) {
$was_locked = !PhabricatorPolicyFilter::canInteract($actor, $object);
}
foreach ($xactions as $xaction) {
$this->applyInternalEffects($object, $xaction);
}
$xactions = $this->didApplyInternalEffects($object, $xactions);
try {
$object->save();
} catch (AphrontDuplicateKeyQueryException $ex) {
// This callback has an opportunity to throw a better exception,
// so execution may end here.
$this->didCatchDuplicateKeyException($object, $xactions, $ex);
throw $ex;
}
$group_id = $this->getTransactionGroupID();
foreach ($xactions as $xaction) {
if ($was_locked) {
$is_override = $this->isLockOverrideTransaction($xaction);
if ($is_override) {
$xaction->setIsLockOverrideTransaction(true);
}
}
$xaction->setObjectPHID($object->getPHID());
$xaction->setTransactionGroupID($group_id);
if ($xaction->getComment()) {
$xaction->setPHID($xaction->generatePHID());
$comment_editor->applyEdit($xaction, $xaction->getComment());
} else {
// TODO: This is a transitional hack to let us migrate edge
// transactions to a more efficient storage format. For now, we're
// going to write a new slim format to the database but keep the old
// bulky format on the objects so we don't have to upgrade all the
// edit logic to the new format yet. See T13051.
$edge_type = PhabricatorTransactions::TYPE_EDGE;
if ($xaction->getTransactionType() == $edge_type) {
$bulky_old = $xaction->getOldValue();
$bulky_new = $xaction->getNewValue();
$record = PhabricatorEdgeChangeRecord::newFromTransaction($xaction);
$slim_old = $record->getModernOldEdgeTransactionData();
$slim_new = $record->getModernNewEdgeTransactionData();
$xaction->setOldValue($slim_old);
$xaction->setNewValue($slim_new);
$xaction->save();
$xaction->setOldValue($bulky_old);
$xaction->setNewValue($bulky_new);
} else {
$xaction->save();
}
}
}
- if ($file_phids) {
- $this->attachFiles($object, $file_phids);
- }
-
foreach ($xactions as $xaction) {
$this->applyExternalEffects($object, $xaction);
}
$xactions = $this->applyFinalEffects($object, $xactions);
if ($read_locking) {
$object->endReadLocking();
$read_locking = false;
}
if ($transaction_open) {
$object->saveTransaction();
$transaction_open = false;
}
$this->didCommitTransactions($object, $xactions);
} catch (Exception $ex) {
if ($read_locking) {
$object->endReadLocking();
$read_locking = false;
}
if ($transaction_open) {
$object->killTransaction();
$transaction_open = false;
}
throw $ex;
}
// If we need to perform cache engine updates, execute them now.
id(new PhabricatorCacheEngine())
->updateObject($object);
// Now that we've completely applied the core transaction set, try to apply
// Herald rules. Herald rules are allowed to either take direct actions on
// the database (like writing flags), or take indirect actions (like saving
// some targets for CC when we generate mail a little later), or return
// transactions which we'll apply normally using another Editor.
// First, check if *this* is a sub-editor which is itself applying Herald
// rules: if it is, stop working and return so we don't descend into
// madness.
// Otherwise, we're not a Herald editor, so process Herald rules (possibly
// using a Herald editor to apply resulting transactions) and then send out
// mail, notifications, and feed updates about everything.
if ($this->getIsHeraldEditor()) {
// We are the Herald editor, so stop work here and return the updated
// transactions.
return $xactions;
} else if ($this->getIsInverseEdgeEditor()) {
// Do not run Herald if we're just recording that this object was
// mentioned elsewhere. This tends to create Herald side effects which
// feel arbitrary, and can really slow down edits which mention a large
// number of other objects. See T13114.
} else if ($this->shouldApplyHeraldRules($object, $xactions)) {
// We are not the Herald editor, so try to apply Herald rules.
$herald_xactions = $this->applyHeraldRules($object, $xactions);
if ($herald_xactions) {
$xscript_id = $this->getHeraldTranscript()->getID();
foreach ($herald_xactions as $herald_xaction) {
// Don't set a transcript ID if this is a transaction from another
// application or source, like Owners.
if ($herald_xaction->getAuthorPHID()) {
continue;
}
$herald_xaction->setMetadataValue('herald:transcriptID', $xscript_id);
}
// NOTE: We're acting as the omnipotent user because rules deal with
// their own policy issues. We use a synthetic author PHID (the
// Herald application) as the author of record, so that transactions
// will render in a reasonable way ("Herald assigned this task ...").
$herald_actor = PhabricatorUser::getOmnipotentUser();
$herald_phid = id(new PhabricatorHeraldApplication())->getPHID();
// TODO: It would be nice to give transactions a more specific source
// which points at the rule which generated them. You can figure this
// out from transcripts, but it would be cleaner if you didn't have to.
$herald_source = PhabricatorContentSource::newForSource(
PhabricatorHeraldContentSource::SOURCECONST);
$herald_editor = $this->newEditorCopy()
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->setIsHeraldEditor(true)
->setActor($herald_actor)
->setActingAsPHID($herald_phid)
->setContentSource($herald_source);
$herald_xactions = $herald_editor->applyTransactions(
$object,
$herald_xactions);
// Merge the new transactions into the transaction list: we want to
// send email and publish feed stories about them, too.
$xactions = array_merge($xactions, $herald_xactions);
}
// If Herald did not generate transactions, we may still need to handle
// "Send an Email" rules.
$adapter = $this->getHeraldAdapter();
$this->heraldEmailPHIDs = $adapter->getEmailPHIDs();
$this->heraldForcedEmailPHIDs = $adapter->getForcedEmailPHIDs();
$this->webhookMap = $adapter->getWebhookMap();
}
$xactions = $this->didApplyTransactions($object, $xactions);
if ($object instanceof PhabricatorCustomFieldInterface) {
// Maybe this makes more sense to move into the search index itself? For
// now I'm putting it here since I think we might end up with things that
// need it to be up to date once the next page loads, but if we don't go
// there we could move it into search once search moves to the daemons.
// It now happens in the search indexer as well, but the search indexer is
// always daemonized, so the logic above still potentially holds. We could
// possibly get rid of this. The major motivation for putting it in the
// indexer was to enable reindexing to work.
$fields = PhabricatorCustomField::getObjectFields(
$object,
PhabricatorCustomField::ROLE_APPLICATIONSEARCH);
$fields->readFieldsFromStorage($object);
$fields->rebuildIndexes($object);
}
$herald_xscript = $this->getHeraldTranscript();
if ($herald_xscript) {
$herald_header = $herald_xscript->getXHeraldRulesHeader();
$herald_header = HeraldTranscript::saveXHeraldRulesHeader(
$object->getPHID(),
$herald_header);
} else {
$herald_header = HeraldTranscript::loadXHeraldRulesHeader(
$object->getPHID());
}
$this->heraldHeader = $herald_header;
// See PHI1134. If we're a subeditor, we don't publish information about
// the edit yet. Our parent editor still needs to finish applying
// transactions and execute Herald, which may change the information we
// publish.
// For example, Herald actions may change the parent object's title or
// visibility, or Herald may apply rules like "Must Encrypt" that affect
// email.
// Once the parent finishes work, it will queue its own publish step and
// then queue publish steps for its children.
$this->publishableObject = $object;
$this->publishableTransactions = $xactions;
if (!$this->parentEditor) {
$this->queuePublishing();
}
return $xactions;
}
private function queuePublishing() {
$object = $this->publishableObject;
$xactions = $this->publishableTransactions;
if (!$object) {
throw new Exception(
pht(
'Editor method "queuePublishing()" was called, but no publishable '.
'object is present. This Editor is not ready to publish.'));
}
// We're going to compute some of the data we'll use to publish these
// transactions here, before queueing a worker.
//
// Primarily, this is more correct: we want to publish the object as it
// exists right now. The worker may not execute for some time, and we want
// to use the current To/CC list, not respect any changes which may occur
// between now and when the worker executes.
//
// As a secondary benefit, this tends to reduce the amount of state that
// Editors need to pass into workers.
$object = $this->willPublish($object, $xactions);
if (!$this->getIsSilent()) {
if ($this->shouldSendMail($object, $xactions)) {
$this->mailShouldSend = true;
$this->mailToPHIDs = $this->getMailTo($object);
$this->mailCCPHIDs = $this->getMailCC($object);
$this->mailUnexpandablePHIDs = $this->newMailUnexpandablePHIDs($object);
// Add any recipients who were previously on the notification list
// but were removed by this change.
$this->applyOldRecipientLists();
if ($object instanceof PhabricatorSubscribableInterface) {
$this->mailMutedPHIDs = PhabricatorEdgeQuery::loadDestinationPHIDs(
$object->getPHID(),
PhabricatorMutedByEdgeType::EDGECONST);
} else {
$this->mailMutedPHIDs = array();
}
$mail_xactions = $this->getTransactionsForMail($object, $xactions);
$stamps = $this->newMailStamps($object, $xactions);
foreach ($stamps as $stamp) {
$this->mailStamps[] = $stamp->toDictionary();
}
}
if ($this->shouldPublishFeedStory($object, $xactions)) {
$this->feedShouldPublish = true;
$this->feedRelatedPHIDs = $this->getFeedRelatedPHIDs(
$object,
$xactions);
$this->feedNotifyPHIDs = $this->getFeedNotifyPHIDs(
$object,
$xactions);
}
}
PhabricatorWorker::scheduleTask(
'PhabricatorApplicationTransactionPublishWorker',
array(
'objectPHID' => $object->getPHID(),
'actorPHID' => $this->getActingAsPHID(),
'xactionPHIDs' => mpull($xactions, 'getPHID'),
'state' => $this->getWorkerState(),
),
array(
'objectPHID' => $object->getPHID(),
'priority' => PhabricatorWorker::PRIORITY_ALERTS,
));
foreach ($this->subEditors as $sub_editor) {
$sub_editor->queuePublishing();
}
$this->flushTransactionQueue($object);
}
protected function didCatchDuplicateKeyException(
PhabricatorLiskDAO $object,
array $xactions,
Exception $ex) {
return;
}
public function publishTransactions(
PhabricatorLiskDAO $object,
array $xactions) {
$this->object = $object;
$this->xactions = $xactions;
// Hook for edges or other properties that may need (re-)loading
$object = $this->willPublish($object, $xactions);
// The object might have changed, so reassign it.
$this->object = $object;
$messages = array();
if ($this->mailShouldSend) {
$messages = $this->buildMail($object, $xactions);
}
if ($this->supportsSearch()) {
PhabricatorSearchWorker::queueDocumentForIndexing(
$object->getPHID(),
array(
'transactionPHIDs' => mpull($xactions, 'getPHID'),
));
}
if ($this->feedShouldPublish) {
$mailed = array();
foreach ($messages as $mail) {
foreach ($mail->buildRecipientList() as $phid) {
$mailed[$phid] = $phid;
}
}
$this->publishFeedStory($object, $xactions, $mailed);
}
if ($this->sendHistory) {
$history_mail = $this->buildHistoryMail($object);
if ($history_mail) {
$messages[] = $history_mail;
}
}
foreach ($this->newAuxiliaryMail($object, $xactions) as $message) {
$messages[] = $message;
}
// NOTE: This actually sends the mail. We do this last to reduce the chance
// that we send some mail, hit an exception, then send the mail again when
// retrying.
foreach ($messages as $mail) {
$mail->save();
}
$this->queueWebhooks($object, $xactions);
return $xactions;
}
protected function didApplyTransactions($object, array $xactions) {
// Hook for subclasses.
return $xactions;
}
private function loadHandles(array $xactions) {
$phids = array();
foreach ($xactions as $key => $xaction) {
$phids[$key] = $xaction->getRequiredHandlePHIDs();
}
$handles = array();
$merged = array_mergev($phids);
if ($merged) {
$handles = id(new PhabricatorHandleQuery())
->setViewer($this->requireActor())
->withPHIDs($merged)
->execute();
}
foreach ($xactions as $key => $xaction) {
$xaction->setHandles(array_select_keys($handles, $phids[$key]));
}
}
private function loadSubscribers(PhabricatorLiskDAO $object) {
if ($object->getPHID() &&
($object instanceof PhabricatorSubscribableInterface)) {
$subs = PhabricatorSubscribersQuery::loadSubscribersForPHID(
$object->getPHID());
$this->subscribers = array_fuse($subs);
} else {
$this->subscribers = array();
}
}
private function validateEditParameters(
PhabricatorLiskDAO $object,
array $xactions) {
if (!$this->getContentSource()) {
throw new PhutilInvalidStateException('setContentSource');
}
// Do a bunch of sanity checks that the incoming transactions are fresh.
// They should be unsaved and have only "transactionType" and "newValue"
// set.
$types = array_fill_keys($this->getTransactionTypes(), true);
assert_instances_of($xactions, 'PhabricatorApplicationTransaction');
foreach ($xactions as $xaction) {
if ($xaction->getPHID() || $xaction->getID()) {
throw new PhabricatorApplicationTransactionStructureException(
$xaction,
pht('You can not apply transactions which already have IDs/PHIDs!'));
}
if ($xaction->getObjectPHID()) {
throw new PhabricatorApplicationTransactionStructureException(
$xaction,
pht(
'You can not apply transactions which already have %s!',
'objectPHIDs'));
}
if ($xaction->getCommentPHID()) {
throw new PhabricatorApplicationTransactionStructureException(
$xaction,
pht(
'You can not apply transactions which already have %s!',
'commentPHIDs'));
}
if ($xaction->getCommentVersion() !== 0) {
throw new PhabricatorApplicationTransactionStructureException(
$xaction,
pht(
'You can not apply transactions which already have '.
'commentVersions!'));
}
$expect_value = !$xaction->shouldGenerateOldValue();
$has_value = $xaction->hasOldValue();
// See T13082. In the narrow case of applying inverse edge edits, we
// expect the old value to be populated.
if ($this->getIsInverseEdgeEditor()) {
$expect_value = true;
}
if ($expect_value && !$has_value) {
throw new PhabricatorApplicationTransactionStructureException(
$xaction,
pht(
'This transaction is supposed to have an %s set, but it does not!',
'oldValue'));
}
if ($has_value && !$expect_value) {
throw new PhabricatorApplicationTransactionStructureException(
$xaction,
pht(
'This transaction should generate its %s automatically, '.
'but has already had one set!',
'oldValue'));
}
$type = $xaction->getTransactionType();
if (empty($types[$type])) {
throw new PhabricatorApplicationTransactionStructureException(
$xaction,
pht(
'Transaction has type "%s", but that transaction type is not '.
'supported by this editor (%s).',
$type,
get_class($this)));
}
}
}
private function applyCapabilityChecks(
PhabricatorLiskDAO $object,
array $xactions) {
assert_instances_of($xactions, 'PhabricatorApplicationTransaction');
$can_edit = PhabricatorPolicyCapability::CAN_EDIT;
if ($this->getIsNewObject()) {
// If we're creating a new object, we don't need any special capabilities
// on the object. The actor has already made it through creation checks,
// and objects which haven't been created yet often can not be
// meaningfully tested for capabilities anyway.
$required_capabilities = array();
} else {
if (!$xactions && !$this->xactions) {
// If we aren't doing anything, require CAN_EDIT to improve consistency.
$required_capabilities = array($can_edit);
} else {
$required_capabilities = array();
foreach ($xactions as $xaction) {
$type = $xaction->getTransactionType();
- $xtype = $this->getModularTransactionType($type);
+ $xtype = $this->getModularTransactionType($object, $type);
if (!$xtype) {
$capabilities = $this->getLegacyRequiredCapabilities($xaction);
} else {
$capabilities = $xtype->getRequiredCapabilities($object, $xaction);
}
// For convenience, we allow flexibility in the return types because
// it's very unusual that a transaction actually requires multiple
// capability checks.
if ($capabilities === null) {
$capabilities = array();
} else {
$capabilities = (array)$capabilities;
}
foreach ($capabilities as $capability) {
$required_capabilities[$capability] = $capability;
}
}
}
}
$required_capabilities = array_fuse($required_capabilities);
$actor = $this->getActor();
if ($required_capabilities) {
id(new PhabricatorPolicyFilter())
->setViewer($actor)
->requireCapabilities($required_capabilities)
->raisePolicyExceptions(true)
->apply(array($object));
}
}
private function getLegacyRequiredCapabilities(
PhabricatorApplicationTransaction $xaction) {
$type = $xaction->getTransactionType();
switch ($type) {
case PhabricatorTransactions::TYPE_COMMENT:
// TODO: Comments technically require CAN_INTERACT, but this is
// currently somewhat special and handled through EditEngine. For now,
// don't enforce it here.
return null;
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
// Anyone can subscribe to or unsubscribe from anything they can view,
// with no other permissions.
$old = array_fuse($xaction->getOldValue());
$new = array_fuse($xaction->getNewValue());
// To remove users other than yourself, you must be able to edit the
// object.
$rem = array_diff_key($old, $new);
foreach ($rem as $phid) {
if ($phid !== $this->getActingAsPHID()) {
return PhabricatorPolicyCapability::CAN_EDIT;
}
}
// To add users other than yourself, you must be able to interact.
// This allows "@mentioning" users to work as long as you can comment
// on objects.
// If you can edit, we return that policy instead so that you can
// override a soft lock and still make edits.
// TODO: This is a little bit hacky. We really want to be able to say
// "this requires either interact or edit", but there's currently no
// way to specify this kind of requirement.
$can_edit = PhabricatorPolicyFilter::hasCapability(
$this->getActor(),
$this->object,
PhabricatorPolicyCapability::CAN_EDIT);
$add = array_diff_key($new, $old);
foreach ($add as $phid) {
if ($phid !== $this->getActingAsPHID()) {
if ($can_edit) {
return PhabricatorPolicyCapability::CAN_EDIT;
} else {
return PhabricatorPolicyCapability::CAN_INTERACT;
}
}
}
return null;
case PhabricatorTransactions::TYPE_TOKEN:
// TODO: This technically requires CAN_INTERACT, like comments.
return null;
case PhabricatorTransactions::TYPE_HISTORY:
// This is a special magic transaction which sends you history via
// email and is only partially supported in the upstream. You don't
// need any capabilities to apply it.
return null;
case PhabricatorTransactions::TYPE_MFA:
// Signing a transaction group with MFA does not require permissions
// on its own.
return null;
+ case PhabricatorTransactions::TYPE_FILE:
+ return null;
case PhabricatorTransactions::TYPE_EDGE:
return $this->getLegacyRequiredEdgeCapabilities($xaction);
default:
// For other older (non-modular) transactions, always require exactly
// CAN_EDIT. Transactions which do not need CAN_EDIT or need additional
// capabilities must move to ModularTransactions.
return PhabricatorPolicyCapability::CAN_EDIT;
}
}
private function getLegacyRequiredEdgeCapabilities(
PhabricatorApplicationTransaction $xaction) {
// You don't need to have edit permission on an object to mention it or
// otherwise add a relationship pointing toward it.
if ($this->getIsInverseEdgeEditor()) {
return null;
}
$edge_type = $xaction->getMetadataValue('edge:type');
switch ($edge_type) {
case PhabricatorMutedByEdgeType::EDGECONST:
// At time of writing, you can only write this edge for yourself, so
// you don't need permissions. If you can eventually mute an object
// for other users, this would need to be revisited.
return null;
case PhabricatorProjectSilencedEdgeType::EDGECONST:
// At time of writing, you can only write this edge for yourself, so
// you don't need permissions. If you can eventually silence project
// for other users, this would need to be revisited.
return null;
case PhabricatorObjectMentionsObjectEdgeType::EDGECONST:
return null;
case PhabricatorProjectProjectHasMemberEdgeType::EDGECONST:
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$add = array_keys(array_diff_key($new, $old));
$rem = array_keys(array_diff_key($old, $new));
$actor_phid = $this->requireActor()->getPHID();
$is_join = (($add === array($actor_phid)) && !$rem);
$is_leave = (($rem === array($actor_phid)) && !$add);
if ($is_join) {
// You need CAN_JOIN to join a project.
return PhabricatorPolicyCapability::CAN_JOIN;
}
if ($is_leave) {
$object = $this->object;
// You usually don't need any capabilities to leave a project...
if ($object->getIsMembershipLocked()) {
// ...you must be able to edit to leave locked projects, though.
return PhabricatorPolicyCapability::CAN_EDIT;
} else {
return null;
}
}
// You need CAN_EDIT to change members other than yourself.
return PhabricatorPolicyCapability::CAN_EDIT;
case PhabricatorObjectHasWatcherEdgeType::EDGECONST:
// See PHI1024. Watching a project does not require CAN_EDIT.
return null;
default:
return PhabricatorPolicyCapability::CAN_EDIT;
}
}
private function buildSubscribeTransaction(
PhabricatorLiskDAO $object,
array $xactions,
array $changes) {
if (!($object instanceof PhabricatorSubscribableInterface)) {
return null;
}
if ($this->shouldEnableMentions($object, $xactions)) {
// Identify newly mentioned users. We ignore users who were previously
// mentioned so that we don't re-subscribe users after an edit of text
// which mentions them.
$old_texts = mpull($changes, 'getOldValue');
$new_texts = mpull($changes, 'getNewValue');
$old_phids = PhabricatorMarkupEngine::extractPHIDsFromMentions(
$this->getActor(),
$old_texts);
$new_phids = PhabricatorMarkupEngine::extractPHIDsFromMentions(
$this->getActor(),
$new_texts);
$phids = array_diff($new_phids, $old_phids);
} else {
$phids = array();
}
$this->mentionedPHIDs = $phids;
if ($object->getPHID()) {
// Don't try to subscribe already-subscribed mentions: we want to generate
// a dialog about an action having no effect if the user explicitly adds
// existing CCs, but not if they merely mention existing subscribers.
$phids = array_diff($phids, $this->subscribers);
}
if ($phids) {
$users = id(new PhabricatorPeopleQuery())
->setViewer($this->getActor())
->withPHIDs($phids)
->execute();
$users = mpull($users, null, 'getPHID');
foreach ($phids as $key => $phid) {
$user = idx($users, $phid);
// Don't subscribe invalid users.
if (!$user) {
unset($phids[$key]);
continue;
}
// Don't subscribe bots that get mentioned. If users truly intend
// to subscribe them, they can add them explicitly, but it's generally
// not useful to subscribe bots to objects.
if ($user->getIsSystemAgent()) {
unset($phids[$key]);
continue;
}
// Do not subscribe mentioned users who do not have permission to see
// the object.
if ($object instanceof PhabricatorPolicyInterface) {
$can_view = PhabricatorPolicyFilter::hasCapability(
$user,
$object,
PhabricatorPolicyCapability::CAN_VIEW);
if (!$can_view) {
unset($phids[$key]);
continue;
}
}
// Don't subscribe users who are already automatically subscribed.
if ($object->isAutomaticallySubscribed($phid)) {
unset($phids[$key]);
continue;
}
}
$phids = array_values($phids);
}
if (!$phids) {
return null;
}
$xaction = $object->getApplicationTransactionTemplate()
->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)
->setNewValue(array('+' => $phids));
return $xaction;
}
protected function mergeTransactions(
PhabricatorApplicationTransaction $u,
PhabricatorApplicationTransaction $v) {
+ $object = $this->object;
$type = $u->getTransactionType();
- $xtype = $this->getModularTransactionType($type);
+ $xtype = $this->getModularTransactionType($object, $type);
if ($xtype) {
- $object = $this->object;
return $xtype->mergeTransactions($object, $u, $v);
}
switch ($type) {
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
return $this->mergePHIDOrEdgeTransactions($u, $v);
case PhabricatorTransactions::TYPE_EDGE:
$u_type = $u->getMetadataValue('edge:type');
$v_type = $v->getMetadataValue('edge:type');
if ($u_type == $v_type) {
return $this->mergePHIDOrEdgeTransactions($u, $v);
}
return null;
}
// By default, do not merge the transactions.
return null;
}
/**
* Optionally expand transactions which imply other effects. For example,
* resigning from a revision in Differential implies removing yourself as
* a reviewer.
*/
protected function expandTransactions(
PhabricatorLiskDAO $object,
array $xactions) {
$results = array();
foreach ($xactions as $xaction) {
foreach ($this->expandTransaction($object, $xaction) as $expanded) {
$results[] = $expanded;
}
}
return $results;
}
protected function expandTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
return array($xaction);
}
public function getExpandedSupportTransactions(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$xactions = array($xaction);
$xactions = $this->expandSupportTransactions(
$object,
$xactions);
if (count($xactions) == 1) {
return array();
}
foreach ($xactions as $index => $cxaction) {
if ($cxaction === $xaction) {
unset($xactions[$index]);
break;
}
}
return $xactions;
}
private function expandSupportTransactions(
PhabricatorLiskDAO $object,
array $xactions) {
$this->loadSubscribers($object);
$xactions = $this->applyImplicitCC($object, $xactions);
$changes = $this->getRemarkupChanges($xactions);
$subscribe_xaction = $this->buildSubscribeTransaction(
$object,
$xactions,
$changes);
if ($subscribe_xaction) {
$xactions[] = $subscribe_xaction;
}
// TODO: For now, this is just a placeholder.
$engine = PhabricatorMarkupEngine::getEngine('extract');
$engine->setConfig('viewer', $this->requireActor());
$block_xactions = $this->expandRemarkupBlockTransactions(
$object,
$xactions,
$changes,
$engine);
foreach ($block_xactions as $xaction) {
$xactions[] = $xaction;
}
+ $file_xaction = $this->newFileTransaction(
+ $object,
+ $xactions,
+ $changes);
+ if ($file_xaction) {
+ $xactions[] = $file_xaction;
+ }
+
return $xactions;
}
+
+ private function newFileTransaction(
+ PhabricatorLiskDAO $object,
+ array $xactions,
+ array $remarkup_changes) {
+
+ assert_instances_of(
+ $remarkup_changes,
+ 'PhabricatorTransactionRemarkupChange');
+
+ $new_map = array();
+
+ $viewer = $this->getActor();
+
+ $old_blocks = mpull($remarkup_changes, 'getOldValue');
+ foreach ($old_blocks as $key => $old_block) {
+ $old_blocks[$key] = phutil_string_cast($old_block);
+ }
+
+ $new_blocks = mpull($remarkup_changes, 'getNewValue');
+ foreach ($new_blocks as $key => $new_block) {
+ $new_blocks[$key] = phutil_string_cast($new_block);
+ }
+
+ $old_refs = PhabricatorMarkupEngine::extractFilePHIDsFromEmbeddedFiles(
+ $viewer,
+ $old_blocks);
+ $old_refs = array_fuse($old_refs);
+
+ $new_refs = PhabricatorMarkupEngine::extractFilePHIDsFromEmbeddedFiles(
+ $viewer,
+ $new_blocks);
+ $new_refs = array_fuse($new_refs);
+
+ $add_refs = array_diff_key($new_refs, $old_refs);
+ foreach ($add_refs as $file_phid) {
+ $new_map[$file_phid] = PhabricatorFileAttachment::MODE_REFERENCE;
+ }
+
+ foreach ($remarkup_changes as $remarkup_change) {
+ $metadata = $remarkup_change->getMetadata();
+
+ $attached_phids = idx($metadata, 'attachedFilePHIDs', array());
+ foreach ($attached_phids as $file_phid) {
+
+ // If the blocks don't include a new embedded reference to this file,
+ // do not actually attach it. A common way for this to happen is for
+ // a user to upload a file, then change their mind and remove the
+ // reference. We do not want to attach the file if they decided against
+ // referencing it.
+
+ if (!isset($new_map[$file_phid])) {
+ continue;
+ }
+
+ $new_map[$file_phid] = PhabricatorFileAttachment::MODE_ATTACH;
+ }
+ }
+
+ $file_phids = $this->extractFilePHIDs($object, $xactions);
+ foreach ($file_phids as $file_phid) {
+ $new_map[$file_phid] = PhabricatorFileAttachment::MODE_ATTACH;
+ }
+
+ if (!$new_map) {
+ return null;
+ }
+
+ $xaction = $object->getApplicationTransactionTemplate()
+ ->setTransactionType(PhabricatorTransactions::TYPE_FILE)
+ ->setMetadataValue('attach.implicit', true)
+ ->setNewValue($new_map);
+
+ return $xaction;
+ }
+
+
private function getRemarkupChanges(array $xactions) {
$changes = array();
foreach ($xactions as $key => $xaction) {
foreach ($this->getRemarkupChangesFromTransaction($xaction) as $change) {
$changes[] = $change;
}
}
return $changes;
}
private function getRemarkupChangesFromTransaction(
PhabricatorApplicationTransaction $transaction) {
return $transaction->getRemarkupChanges();
}
private function expandRemarkupBlockTransactions(
PhabricatorLiskDAO $object,
array $xactions,
array $changes,
PhutilMarkupEngine $engine) {
$block_xactions = $this->expandCustomRemarkupBlockTransactions(
$object,
$xactions,
$changes,
$engine);
$mentioned_phids = array();
if ($this->shouldEnableMentions($object, $xactions)) {
foreach ($changes as $change) {
// Here, we don't care about processing only new mentions after an edit
// because there is no way for an object to ever "unmention" itself on
// another object, so we can ignore the old value.
$engine->markupText($change->getNewValue());
$mentioned_phids += $engine->getTextMetadata(
PhabricatorObjectRemarkupRule::KEY_MENTIONED_OBJECTS,
array());
}
}
if (!$mentioned_phids) {
return $block_xactions;
}
$mentioned_objects = id(new PhabricatorObjectQuery())
->setViewer($this->getActor())
->withPHIDs($mentioned_phids)
->execute();
$unmentionable_map = $this->getUnmentionablePHIDMap();
$mentionable_phids = array();
if ($this->shouldEnableMentions($object, $xactions)) {
foreach ($mentioned_objects as $mentioned_object) {
if ($mentioned_object instanceof PhabricatorMentionableInterface) {
$mentioned_phid = $mentioned_object->getPHID();
if (isset($unmentionable_map[$mentioned_phid])) {
continue;
}
// don't let objects mention themselves
if ($object->getPHID() && $mentioned_phid == $object->getPHID()) {
continue;
}
$mentionable_phids[$mentioned_phid] = $mentioned_phid;
}
}
}
if ($mentionable_phids) {
$edge_type = PhabricatorObjectMentionsObjectEdgeType::EDGECONST;
$block_xactions[] = newv(get_class(head($xactions)), array())
->setIgnoreOnNoEffect(true)
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $edge_type)
->setNewValue(array('+' => $mentionable_phids));
}
return $block_xactions;
}
protected function expandCustomRemarkupBlockTransactions(
PhabricatorLiskDAO $object,
array $xactions,
array $changes,
PhutilMarkupEngine $engine) {
return array();
}
/**
* Attempt to combine similar transactions into a smaller number of total
* transactions. For example, two transactions which edit the title of an
* object can be merged into a single edit.
*/
private function combineTransactions(array $xactions) {
$stray_comments = array();
$result = array();
$types = array();
foreach ($xactions as $key => $xaction) {
$type = $xaction->getTransactionType();
if (isset($types[$type])) {
foreach ($types[$type] as $other_key) {
$other_xaction = $result[$other_key];
// Don't merge transactions with different authors. For example,
// don't merge Herald transactions and owners transactions.
if ($other_xaction->getAuthorPHID() != $xaction->getAuthorPHID()) {
continue;
}
$merged = $this->mergeTransactions($result[$other_key], $xaction);
if ($merged) {
$result[$other_key] = $merged;
if ($xaction->getComment() &&
($xaction->getComment() !== $merged->getComment())) {
$stray_comments[] = $xaction->getComment();
}
if ($result[$other_key]->getComment() &&
($result[$other_key]->getComment() !== $merged->getComment())) {
$stray_comments[] = $result[$other_key]->getComment();
}
// Move on to the next transaction.
continue 2;
}
}
}
$result[$key] = $xaction;
$types[$type][] = $key;
}
// If we merged any comments away, restore them.
foreach ($stray_comments as $comment) {
$xaction = newv(get_class(head($result)), array());
$xaction->setTransactionType(PhabricatorTransactions::TYPE_COMMENT);
$xaction->setComment($comment);
$result[] = $xaction;
}
return array_values($result);
}
public function mergePHIDOrEdgeTransactions(
PhabricatorApplicationTransaction $u,
PhabricatorApplicationTransaction $v) {
$result = $u->getNewValue();
foreach ($v->getNewValue() as $key => $value) {
if ($u->getTransactionType() == PhabricatorTransactions::TYPE_EDGE) {
if (empty($result[$key])) {
$result[$key] = $value;
} else {
// We're merging two lists of edge adds, sets, or removes. Merge
// them by merging individual PHIDs within them.
$merged = $result[$key];
foreach ($value as $dst => $v_spec) {
if (empty($merged[$dst])) {
$merged[$dst] = $v_spec;
} else {
// Two transactions are trying to perform the same operation on
// the same edge. Normalize the edge data and then merge it. This
// allows transactions to specify how data merges execute in a
// precise way.
$u_spec = $merged[$dst];
if (!is_array($u_spec)) {
$u_spec = array('dst' => $u_spec);
}
if (!is_array($v_spec)) {
$v_spec = array('dst' => $v_spec);
}
$ux_data = idx($u_spec, 'data', array());
$vx_data = idx($v_spec, 'data', array());
$merged_data = $this->mergeEdgeData(
$u->getMetadataValue('edge:type'),
$ux_data,
$vx_data);
$u_spec['data'] = $merged_data;
$merged[$dst] = $u_spec;
}
}
$result[$key] = $merged;
}
} else {
$result[$key] = array_merge($value, idx($result, $key, array()));
}
}
$u->setNewValue($result);
// When combining an "ignore" transaction with a normal transaction, make
// sure we don't propagate the "ignore" flag.
if (!$v->getIgnoreOnNoEffect()) {
$u->setIgnoreOnNoEffect(false);
}
return $u;
}
protected function mergeEdgeData($type, array $u, array $v) {
return $v + $u;
}
protected function getPHIDTransactionNewValue(
PhabricatorApplicationTransaction $xaction,
$old = null) {
if ($old !== null) {
$old = array_fuse($old);
} else {
$old = array_fuse($xaction->getOldValue());
}
return $this->getPHIDList($old, $xaction->getNewValue());
}
public function getPHIDList(array $old, array $new) {
$new_add = idx($new, '+', array());
unset($new['+']);
$new_rem = idx($new, '-', array());
unset($new['-']);
$new_set = idx($new, '=', null);
if ($new_set !== null) {
$new_set = array_fuse($new_set);
}
unset($new['=']);
if ($new) {
throw new Exception(
pht(
"Invalid '%s' value for PHID transaction. Value should contain only ".
"keys '%s' (add PHIDs), '%s' (remove PHIDs) and '%s' (set PHIDS).",
'new',
'+',
'-',
'='));
}
$result = array();
foreach ($old as $phid) {
if ($new_set !== null && empty($new_set[$phid])) {
continue;
}
$result[$phid] = $phid;
}
if ($new_set !== null) {
foreach ($new_set as $phid) {
$result[$phid] = $phid;
}
}
foreach ($new_add as $phid) {
$result[$phid] = $phid;
}
foreach ($new_rem as $phid) {
unset($result[$phid]);
}
return array_values($result);
}
protected function getEdgeTransactionNewValue(
PhabricatorApplicationTransaction $xaction) {
$new = $xaction->getNewValue();
$new_add = idx($new, '+', array());
unset($new['+']);
$new_rem = idx($new, '-', array());
unset($new['-']);
$new_set = idx($new, '=', null);
unset($new['=']);
if ($new) {
throw new Exception(
pht(
"Invalid '%s' value for Edge transaction. Value should contain only ".
"keys '%s' (add edges), '%s' (remove edges) and '%s' (set edges).",
'new',
'+',
'-',
'='));
}
$old = $xaction->getOldValue();
$lists = array($new_set, $new_add, $new_rem);
foreach ($lists as $list) {
$this->checkEdgeList($list, $xaction->getMetadataValue('edge:type'));
}
$result = array();
foreach ($old as $dst_phid => $edge) {
if ($new_set !== null && empty($new_set[$dst_phid])) {
continue;
}
$result[$dst_phid] = $this->normalizeEdgeTransactionValue(
$xaction,
$edge,
$dst_phid);
}
if ($new_set !== null) {
foreach ($new_set as $dst_phid => $edge) {
$result[$dst_phid] = $this->normalizeEdgeTransactionValue(
$xaction,
$edge,
$dst_phid);
}
}
foreach ($new_add as $dst_phid => $edge) {
$result[$dst_phid] = $this->normalizeEdgeTransactionValue(
$xaction,
$edge,
$dst_phid);
}
foreach ($new_rem as $dst_phid => $edge) {
unset($result[$dst_phid]);
}
return $result;
}
private function checkEdgeList($list, $edge_type) {
if (!$list) {
return;
}
foreach ($list as $key => $item) {
if (phid_get_type($key) === PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) {
throw new Exception(
pht(
'Edge transactions must have destination PHIDs as in edge '.
'lists (found key "%s" on transaction of type "%s").',
$key,
$edge_type));
}
if (!is_array($item) && $item !== $key) {
throw new Exception(
pht(
'Edge transactions must have PHIDs or edge specs as values '.
'(found value "%s" on transaction of type "%s").',
$item,
$edge_type));
}
}
}
private function normalizeEdgeTransactionValue(
PhabricatorApplicationTransaction $xaction,
$edge,
$dst_phid) {
if (!is_array($edge)) {
if ($edge != $dst_phid) {
throw new Exception(
pht(
'Transaction edge data must either be the edge PHID or an edge '.
'specification dictionary.'));
}
$edge = array();
} else {
foreach ($edge as $key => $value) {
switch ($key) {
case 'src':
case 'dst':
case 'type':
case 'data':
case 'dateCreated':
case 'dateModified':
case 'seq':
case 'dataID':
break;
default:
throw new Exception(
pht(
'Transaction edge specification contains unexpected key "%s".',
$key));
}
}
}
$edge['dst'] = $dst_phid;
$edge_type = $xaction->getMetadataValue('edge:type');
if (empty($edge['type'])) {
$edge['type'] = $edge_type;
} else {
if ($edge['type'] != $edge_type) {
$this_type = $edge['type'];
throw new Exception(
pht(
"Edge transaction includes edge of type '%s', but ".
"transaction is of type '%s'. Each edge transaction ".
"must alter edges of only one type.",
$this_type,
$edge_type));
}
}
if (!isset($edge['data'])) {
$edge['data'] = array();
}
return $edge;
}
protected function sortTransactions(array $xactions) {
$head = array();
$tail = array();
// Move bare comments to the end, so the actions precede them.
foreach ($xactions as $xaction) {
$type = $xaction->getTransactionType();
if ($type == PhabricatorTransactions::TYPE_COMMENT) {
$tail[] = $xaction;
} else {
$head[] = $xaction;
}
}
return array_values(array_merge($head, $tail));
}
protected function filterTransactions(
PhabricatorLiskDAO $object,
array $xactions) {
$type_comment = PhabricatorTransactions::TYPE_COMMENT;
$type_mfa = PhabricatorTransactions::TYPE_MFA;
$no_effect = array();
$has_comment = false;
$any_effect = false;
$meta_xactions = array();
foreach ($xactions as $key => $xaction) {
if ($xaction->getTransactionType() === $type_mfa) {
$meta_xactions[$key] = $xaction;
continue;
}
if ($this->transactionHasEffect($object, $xaction)) {
if ($xaction->getTransactionType() != $type_comment) {
$any_effect = true;
}
} else if ($xaction->getIgnoreOnNoEffect()) {
unset($xactions[$key]);
} else {
$no_effect[$key] = $xaction;
}
if ($xaction->hasComment()) {
$has_comment = true;
}
}
// If every transaction is a meta-transaction applying to the transaction
// group, these transactions are junk.
if (count($meta_xactions) == count($xactions)) {
$no_effect = $xactions;
$any_effect = false;
}
if (!$no_effect) {
return $xactions;
}
// If none of the transactions have an effect, the meta-transactions also
// have no effect. Add them to the "no effect" list so we get a full set
// of errors for everything.
if (!$any_effect && !$has_comment) {
$no_effect += $meta_xactions;
}
if (!$this->getContinueOnNoEffect() && !$this->getIsPreview()) {
throw new PhabricatorApplicationTransactionNoEffectException(
$no_effect,
$any_effect,
$has_comment);
}
if (!$any_effect && !$has_comment) {
// If we only have empty comment transactions, just drop them all.
return array();
}
foreach ($no_effect as $key => $xaction) {
if ($xaction->hasComment()) {
$xaction->setTransactionType($type_comment);
$xaction->setOldValue(null);
$xaction->setNewValue(null);
} else {
unset($xactions[$key]);
}
}
return $xactions;
}
/**
* Hook for validating transactions. This callback will be invoked for each
* available transaction type, even if an edit does not apply any transactions
* of that type. This allows you to raise exceptions when required fields are
* missing, by detecting that the object has no field value and there is no
* transaction which sets one.
*
* @param PhabricatorLiskDAO Object being edited.
* @param string Transaction type to validate.
* @param list<PhabricatorApplicationTransaction> Transactions of given type,
* which may be empty if the edit does not apply any transactions of the
* given type.
* @return list<PhabricatorApplicationTransactionValidationError> List of
* validation errors.
*/
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = array();
- $xtype = $this->getModularTransactionType($type);
+ $xtype = $this->getModularTransactionType($object, $type);
if ($xtype) {
$errors[] = $xtype->validateTransactions($object, $xactions);
}
switch ($type) {
case PhabricatorTransactions::TYPE_VIEW_POLICY:
$errors[] = $this->validatePolicyTransaction(
$object,
$xactions,
$type,
PhabricatorPolicyCapability::CAN_VIEW);
break;
case PhabricatorTransactions::TYPE_EDIT_POLICY:
$errors[] = $this->validatePolicyTransaction(
$object,
$xactions,
$type,
PhabricatorPolicyCapability::CAN_EDIT);
break;
case PhabricatorTransactions::TYPE_SPACE:
$errors[] = $this->validateSpaceTransactions(
$object,
$xactions,
$type);
break;
case PhabricatorTransactions::TYPE_SUBTYPE:
$errors[] = $this->validateSubtypeTransactions(
$object,
$xactions,
$type);
break;
case PhabricatorTransactions::TYPE_MFA:
$errors[] = $this->validateMFATransactions(
$object,
$xactions,
$type);
break;
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$groups = array();
foreach ($xactions as $xaction) {
$groups[$xaction->getMetadataValue('customfield:key')][] = $xaction;
}
$field_list = PhabricatorCustomField::getObjectFields(
$object,
PhabricatorCustomField::ROLE_EDIT);
$field_list->setViewer($this->getActor());
$role_xactions = PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS;
foreach ($field_list->getFields() as $field) {
if (!$field->shouldEnableForRole($role_xactions)) {
continue;
}
$errors[] = $field->validateApplicationTransactions(
$this,
$type,
idx($groups, $field->getFieldKey(), array()));
}
break;
+ case PhabricatorTransactions::TYPE_FILE:
+ $errors[] = $this->validateFileTransactions(
+ $object,
+ $xactions,
+ $type);
+ break;
}
return array_mergev($errors);
}
+ private function validateFileTransactions(
+ PhabricatorLiskDAO $object,
+ array $xactions,
+ $transaction_type) {
+
+ $errors = array();
+
+ $mode_map = PhabricatorFileAttachment::getModeList();
+ $mode_map = array_fuse($mode_map);
+
+ $file_phids = array();
+ foreach ($xactions as $xaction) {
+ $new = $xaction->getNewValue();
+
+ if (!is_array($new)) {
+ $errors[] = new PhabricatorApplicationTransactionValidationError(
+ $transaction_type,
+ pht('Invalid'),
+ pht(
+ 'File attachment transaction must have a map of files to '.
+ 'attachment modes, found "%s".',
+ phutil_describe_type($new)),
+ $xaction);
+ continue;
+ }
+
+ foreach ($new as $file_phid => $attachment_mode) {
+ $file_phids[$file_phid] = $file_phid;
+
+ if (is_string($attachment_mode) && isset($mode_map[$attachment_mode])) {
+ continue;
+ }
+
+ if (!is_string($attachment_mode)) {
+ $errors[] = new PhabricatorApplicationTransactionValidationError(
+ $transaction_type,
+ pht('Invalid'),
+ pht(
+ 'File attachment mode (for file "%s") is invalid. Expected '.
+ 'a string, found "%s".',
+ $file_phid,
+ phutil_describe_type($attachment_mode)),
+ $xaction);
+ } else {
+ $errors[] = new PhabricatorApplicationTransactionValidationError(
+ $transaction_type,
+ pht('Invalid'),
+ pht(
+ 'File attachment mode "%s" (for file "%s") is invalid. Valid '.
+ 'modes are: %s.',
+ $attachment_mode,
+ $file_phid,
+ pht_list($mode_map)),
+ $xaction);
+ }
+ }
+ }
+
+ if ($file_phids) {
+ $file_map = id(new PhabricatorFileQuery())
+ ->setViewer($this->getActor())
+ ->withPHIDs($file_phids)
+ ->execute();
+ $file_map = mpull($file_map, null, 'getPHID');
+ } else {
+ $file_map = array();
+ }
+
+ foreach ($xactions as $xaction) {
+ $new = $xaction->getNewValue();
+
+ if (!is_array($new)) {
+ continue;
+ }
+
+ foreach ($new as $file_phid => $attachment_mode) {
+ if (isset($file_map[$file_phid])) {
+ continue;
+ }
+
+ $errors[] = new PhabricatorApplicationTransactionValidationError(
+ $transaction_type,
+ pht('Invalid'),
+ pht(
+ 'File "%s" is invalid: it could not be loaded, or you do not '.
+ 'have permission to view it. You must be able to see a file to '.
+ 'attach it to an object.',
+ $file_phid),
+ $xaction);
+ }
+ }
+
+ return $errors;
+ }
+
+
public function validatePolicyTransaction(
PhabricatorLiskDAO $object,
array $xactions,
$transaction_type,
$capability) {
$actor = $this->requireActor();
$errors = array();
// Note $this->xactions is necessary; $xactions is $this->xactions of
// $transaction_type
$policy_object = $this->adjustObjectForPolicyChecks(
$object,
$this->xactions);
// Make sure the user isn't editing away their ability to $capability this
// object.
foreach ($xactions as $xaction) {
try {
PhabricatorPolicyFilter::requireCapabilityWithForcedPolicy(
$actor,
$policy_object,
$capability,
$xaction->getNewValue());
} catch (PhabricatorPolicyException $ex) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$transaction_type,
pht('Invalid'),
pht(
'You can not select this %s policy, because you would no longer '.
'be able to %s the object.',
$capability,
$capability),
$xaction);
}
}
if ($this->getIsNewObject()) {
if (!$xactions) {
$has_capability = PhabricatorPolicyFilter::hasCapability(
$actor,
$policy_object,
$capability);
if (!$has_capability) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$transaction_type,
pht('Invalid'),
pht(
'The selected %s policy excludes you. Choose a %s policy '.
'which allows you to %s the object.',
$capability,
$capability,
$capability));
}
}
}
return $errors;
}
private function validateSpaceTransactions(
PhabricatorLiskDAO $object,
array $xactions,
$transaction_type) {
$errors = array();
$actor = $this->getActor();
$has_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($actor);
$actor_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($actor);
$active_spaces = PhabricatorSpacesNamespaceQuery::getViewerActiveSpaces(
$actor);
foreach ($xactions as $xaction) {
$space_phid = $xaction->getNewValue();
if ($space_phid === null) {
if (!$has_spaces) {
// The install doesn't have any spaces, so this is fine.
continue;
}
// The install has some spaces, so every object needs to be put
// in a valid space.
$errors[] = new PhabricatorApplicationTransactionValidationError(
$transaction_type,
pht('Invalid'),
pht('You must choose a space for this object.'),
$xaction);
continue;
}
// If the PHID isn't `null`, it needs to be a valid space that the
// viewer can see.
if (empty($actor_spaces[$space_phid])) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$transaction_type,
pht('Invalid'),
pht(
'You can not shift this object in the selected space, because '.
'the space does not exist or you do not have access to it.'),
$xaction);
} else if (empty($active_spaces[$space_phid])) {
// It's OK to edit objects in an archived space, so just move on if
// we aren't adjusting the value.
$old_space_phid = $this->getTransactionOldValue($object, $xaction);
if ($space_phid == $old_space_phid) {
continue;
}
$errors[] = new PhabricatorApplicationTransactionValidationError(
$transaction_type,
pht('Archived'),
pht(
'You can not shift this object into the selected space, because '.
'the space is archived. Objects can not be created inside (or '.
'moved into) archived spaces.'),
$xaction);
}
}
return $errors;
}
private function validateSubtypeTransactions(
PhabricatorLiskDAO $object,
array $xactions,
$transaction_type) {
$errors = array();
$map = $object->newEditEngineSubtypeMap();
$old = $object->getEditEngineSubtype();
foreach ($xactions as $xaction) {
$new = $xaction->getNewValue();
if ($old == $new) {
continue;
}
if (!$map->isValidSubtype($new)) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$transaction_type,
pht('Invalid'),
pht(
'The subtype "%s" is not a valid subtype.',
$new),
$xaction);
continue;
}
}
return $errors;
}
private function validateMFATransactions(
PhabricatorLiskDAO $object,
array $xactions,
$transaction_type) {
$errors = array();
$factors = id(new PhabricatorAuthFactorConfigQuery())
->setViewer($this->getActor())
->withUserPHIDs(array($this->getActingAsPHID()))
->withFactorProviderStatuses(
array(
PhabricatorAuthFactorProviderStatus::STATUS_ACTIVE,
PhabricatorAuthFactorProviderStatus::STATUS_DEPRECATED,
))
->execute();
foreach ($xactions as $xaction) {
if (!$factors) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$transaction_type,
pht('No MFA'),
pht(
'You do not have any MFA factors attached to your account, so '.
'you can not sign this transaction group with MFA. Add MFA to '.
'your account in Settings.'),
$xaction);
}
}
if ($xactions) {
$this->setShouldRequireMFA(true);
}
return $errors;
}
protected function adjustObjectForPolicyChecks(
PhabricatorLiskDAO $object,
array $xactions) {
$copy = clone $object;
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
$clone_xaction = clone $xaction;
$clone_xaction->setOldValue(array_values($this->subscribers));
$clone_xaction->setNewValue(
$this->getPHIDTransactionNewValue(
$clone_xaction));
PhabricatorPolicyRule::passTransactionHintToRule(
$copy,
new PhabricatorSubscriptionsSubscribersPolicyRule(),
array_fuse($clone_xaction->getNewValue()));
break;
case PhabricatorTransactions::TYPE_SPACE:
$space_phid = $this->getTransactionNewValue($object, $xaction);
$copy->setSpacePHID($space_phid);
break;
}
}
return $copy;
}
protected function validateAllTransactions(
PhabricatorLiskDAO $object,
array $xactions) {
return array();
}
/**
* Check for a missing text field.
*
* A text field is missing if the object has no value and there are no
* transactions which set a value, or if the transactions remove the value.
* This method is intended to make implementing @{method:validateTransaction}
* more convenient:
*
* $missing = $this->validateIsEmptyTextField(
* $object->getName(),
* $xactions);
*
* This will return `true` if the net effect of the object and transactions
* is an empty field.
*
* @param wild Current field value.
* @param list<PhabricatorApplicationTransaction> Transactions editing the
* field.
* @return bool True if the field will be an empty text field after edits.
*/
protected function validateIsEmptyTextField($field_value, array $xactions) {
- if (strlen($field_value) && empty($xactions)) {
+ if (($field_value !== null && strlen($field_value)) && empty($xactions)) {
return false;
}
if ($xactions && strlen(last($xactions)->getNewValue())) {
return false;
}
return true;
}
/* -( Implicit CCs )------------------------------------------------------- */
/**
* When a user interacts with an object, we might want to add them to CC.
*/
final public function applyImplicitCC(
PhabricatorLiskDAO $object,
array $xactions) {
if (!($object instanceof PhabricatorSubscribableInterface)) {
// If the object isn't subscribable, we can't CC them.
return $xactions;
}
$actor_phid = $this->getActingAsPHID();
$type_user = PhabricatorPeopleUserPHIDType::TYPECONST;
if (phid_get_type($actor_phid) != $type_user) {
// Transactions by application actors like Herald, Harbormaster and
// Diffusion should not CC the applications.
return $xactions;
}
if ($object->isAutomaticallySubscribed($actor_phid)) {
// If they're auto-subscribed, don't CC them.
return $xactions;
}
$should_cc = false;
foreach ($xactions as $xaction) {
if ($this->shouldImplyCC($object, $xaction)) {
$should_cc = true;
break;
}
}
if (!$should_cc) {
// Only some types of actions imply a CC (like adding a comment).
return $xactions;
}
if ($object->getPHID()) {
if (isset($this->subscribers[$actor_phid])) {
// If the user is already subscribed, don't implicitly CC them.
return $xactions;
}
$unsub = PhabricatorEdgeQuery::loadDestinationPHIDs(
$object->getPHID(),
PhabricatorObjectHasUnsubscriberEdgeType::EDGECONST);
$unsub = array_fuse($unsub);
if (isset($unsub[$actor_phid])) {
// If the user has previously unsubscribed from this object explicitly,
// don't implicitly CC them.
return $xactions;
}
}
$actor = $this->getActor();
$user = id(new PhabricatorPeopleQuery())
->setViewer($actor)
->withPHIDs(array($actor_phid))
->executeOne();
if (!$user) {
return $xactions;
}
// When a bot acts (usually via the API), don't automatically subscribe
// them as a side effect. They can always subscribe explicitly if they
// want, and bot subscriptions normally just clutter things up since bots
// usually do not read email.
if ($user->getIsSystemAgent()) {
return $xactions;
}
$xaction = newv(get_class(head($xactions)), array());
$xaction->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS);
$xaction->setNewValue(array('+' => array($actor_phid)));
array_unshift($xactions, $xaction);
return $xactions;
}
protected function shouldImplyCC(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
return $xaction->isCommentTransaction();
}
/* -( Sending Mail )------------------------------------------------------- */
/**
* @task mail
*/
protected function shouldSendMail(
PhabricatorLiskDAO $object,
array $xactions) {
return false;
}
/**
* @task mail
*/
private function buildMail(
PhabricatorLiskDAO $object,
array $xactions) {
$email_to = $this->mailToPHIDs;
$email_cc = $this->mailCCPHIDs;
$email_cc = array_merge($email_cc, $this->heraldEmailPHIDs);
$unexpandable = $this->mailUnexpandablePHIDs;
if (!is_array($unexpandable)) {
$unexpandable = array();
}
$messages = $this->buildMailWithRecipients(
$object,
$xactions,
$email_to,
$email_cc,
$unexpandable);
$this->runHeraldMailRules($messages);
return $messages;
}
private function buildMailWithRecipients(
PhabricatorLiskDAO $object,
array $xactions,
array $email_to,
array $email_cc,
array $unexpandable) {
$targets = $this->buildReplyHandler($object)
->setUnexpandablePHIDs($unexpandable)
->getMailTargets($email_to, $email_cc);
// Set this explicitly before we start swapping out the effective actor.
$this->setActingAsPHID($this->getActingAsPHID());
$xaction_phids = mpull($xactions, 'getPHID');
$messages = array();
foreach ($targets as $target) {
$original_actor = $this->getActor();
$viewer = $target->getViewer();
$this->setActor($viewer);
$locale = PhabricatorEnv::beginScopedLocale($viewer->getTranslation());
$caught = null;
$mail = null;
try {
// Reload the transactions for the current viewer.
if ($xaction_phids) {
$query = PhabricatorApplicationTransactionQuery::newQueryForObject(
$object);
$mail_xactions = $query
->setViewer($viewer)
->withObjectPHIDs(array($object->getPHID()))
->withPHIDs($xaction_phids)
->execute();
// Sort the mail transactions in the input order.
$mail_xactions = mpull($mail_xactions, null, 'getPHID');
$mail_xactions = array_select_keys($mail_xactions, $xaction_phids);
$mail_xactions = array_values($mail_xactions);
} else {
$mail_xactions = array();
}
// Reload handles for the current viewer. This covers older code which
// emits a list of handle PHIDs upfront.
$this->loadHandles($mail_xactions);
$mail = $this->buildMailForTarget($object, $mail_xactions, $target);
if ($mail) {
if ($this->mustEncrypt) {
$mail
->setMustEncrypt(true)
->setMustEncryptReasons($this->mustEncrypt);
}
}
} catch (Exception $ex) {
$caught = $ex;
}
$this->setActor($original_actor);
unset($locale);
if ($caught) {
throw $ex;
}
if ($mail) {
$messages[] = $mail;
}
}
return $messages;
}
protected function getTransactionsForMail(
PhabricatorLiskDAO $object,
array $xactions) {
return $xactions;
}
private function buildMailForTarget(
PhabricatorLiskDAO $object,
array $xactions,
PhabricatorMailTarget $target) {
// Check if any of the transactions are visible for this viewer. If we
// don't have any visible transactions, don't send the mail.
$any_visible = false;
foreach ($xactions as $xaction) {
if (!$xaction->shouldHideForMail($xactions)) {
$any_visible = true;
break;
}
}
if (!$any_visible) {
return null;
}
$mail_xactions = $this->getTransactionsForMail($object, $xactions);
$mail = $this->buildMailTemplate($object);
$body = $this->buildMailBody($object, $mail_xactions);
$mail_tags = $this->getMailTags($object, $mail_xactions);
$action = $this->getMailAction($object, $mail_xactions);
$stamps = $this->generateMailStamps($object, $this->mailStamps);
if (PhabricatorEnv::getEnvConfig('metamta.email-preferences')) {
$this->addEmailPreferenceSectionToMailBody(
$body,
$object,
$mail_xactions);
}
$muted_phids = $this->mailMutedPHIDs;
if (!is_array($muted_phids)) {
$muted_phids = array();
}
$mail
->setSensitiveContent(false)
->setFrom($this->getActingAsPHID())
->setSubjectPrefix($this->getMailSubjectPrefix())
->setVarySubjectPrefix('['.$action.']')
->setThreadID($this->getMailThreadID($object), $this->getIsNewObject())
->setRelatedPHID($object->getPHID())
->setExcludeMailRecipientPHIDs($this->getExcludeMailRecipientPHIDs())
->setMutedPHIDs($muted_phids)
->setForceHeraldMailRecipientPHIDs($this->heraldForcedEmailPHIDs)
->setMailTags($mail_tags)
->setIsBulk(true)
->setBody($body->render())
->setHTMLBody($body->renderHTML());
foreach ($body->getAttachments() as $attachment) {
$mail->addAttachment($attachment);
}
if ($this->heraldHeader) {
$mail->addHeader('X-Herald-Rules', $this->heraldHeader);
}
if ($object instanceof PhabricatorProjectInterface) {
$this->addMailProjectMetadata($object, $mail);
}
if ($this->getParentMessageID()) {
$mail->setParentMessageID($this->getParentMessageID());
}
// If we have stamps, attach the raw dictionary version (not the actual
// objects) to the mail so that debugging tools can see what we used to
// render the final list.
if ($this->mailStamps) {
$mail->setMailStampMetadata($this->mailStamps);
}
// If we have rendered stamps, attach them to the mail.
if ($stamps) {
$mail->setMailStamps($stamps);
}
return $target->willSendMail($mail);
}
private function addMailProjectMetadata(
PhabricatorLiskDAO $object,
PhabricatorMetaMTAMail $template) {
$project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$object->getPHID(),
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
if (!$project_phids) {
return;
}
// TODO: This viewer isn't quite right. It would be slightly better to use
// the mail recipient, but that's not very easy given the way rendering
// works today.
$handles = id(new PhabricatorHandleQuery())
->setViewer($this->requireActor())
->withPHIDs($project_phids)
->execute();
$project_tags = array();
foreach ($handles as $handle) {
if (!$handle->isComplete()) {
continue;
}
$project_tags[] = '<'.$handle->getObjectName().'>';
}
if (!$project_tags) {
return;
}
$project_tags = implode(', ', $project_tags);
$template->addHeader('X-Phabricator-Projects', $project_tags);
}
protected function getMailThreadID(PhabricatorLiskDAO $object) {
return $object->getPHID();
}
/**
* @task mail
*/
protected function getStrongestAction(
PhabricatorLiskDAO $object,
array $xactions) {
return head(msortv($xactions, 'newActionStrengthSortVector'));
}
/**
* @task mail
*/
protected function buildReplyHandler(PhabricatorLiskDAO $object) {
throw new Exception(pht('Capability not supported.'));
}
/**
* @task mail
*/
protected function getMailSubjectPrefix() {
throw new Exception(pht('Capability not supported.'));
}
/**
* @task mail
*/
protected function getMailTags(
PhabricatorLiskDAO $object,
array $xactions) {
$tags = array();
foreach ($xactions as $xaction) {
$tags[] = $xaction->getMailTags();
}
return array_mergev($tags);
}
/**
* @task mail
*/
public function getMailTagsMap() {
// TODO: We should move shared mail tags, like "comment", here.
return array();
}
/**
* @task mail
*/
protected function getMailAction(
PhabricatorLiskDAO $object,
array $xactions) {
return $this->getStrongestAction($object, $xactions)->getActionName();
}
/**
* @task mail
*/
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
throw new Exception(pht('Capability not supported.'));
}
/**
* @task mail
*/
protected function getMailTo(PhabricatorLiskDAO $object) {
throw new Exception(pht('Capability not supported.'));
}
protected function newMailUnexpandablePHIDs(PhabricatorLiskDAO $object) {
return array();
}
/**
* @task mail
*/
protected function getMailCC(PhabricatorLiskDAO $object) {
$phids = array();
$has_support = false;
if ($object instanceof PhabricatorSubscribableInterface) {
$phid = $object->getPHID();
$phids[] = PhabricatorSubscribersQuery::loadSubscribersForPHID($phid);
$has_support = true;
}
if ($object instanceof PhabricatorProjectInterface) {
$project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$object->getPHID(),
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
if ($project_phids) {
$projects = id(new PhabricatorProjectQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs($project_phids)
->needWatchers(true)
->execute();
$watcher_phids = array();
foreach ($projects as $project) {
foreach ($project->getAllAncestorWatcherPHIDs() as $phid) {
$watcher_phids[$phid] = $phid;
}
}
if ($watcher_phids) {
// We need to do a visibility check for all the watchers, as
// watching a project is not a guarantee that you can see objects
// associated with it.
$users = id(new PhabricatorPeopleQuery())
->setViewer($this->requireActor())
->withPHIDs($watcher_phids)
->execute();
$watchers = array();
foreach ($users as $user) {
$can_see = PhabricatorPolicyFilter::hasCapability(
$user,
$object,
PhabricatorPolicyCapability::CAN_VIEW);
if ($can_see) {
$watchers[] = $user->getPHID();
}
}
$phids[] = $watchers;
}
}
$has_support = true;
}
if (!$has_support) {
throw new Exception(
pht('The object being edited does not implement any standard '.
'interfaces (like PhabricatorSubscribableInterface) which allow '.
'CCs to be generated automatically. Override the "getMailCC()" '.
'method and generate CCs explicitly.'));
}
return array_mergev($phids);
}
/**
* @task mail
*/
protected function buildMailBody(
PhabricatorLiskDAO $object,
array $xactions) {
$body = id(new PhabricatorMetaMTAMailBody())
->setViewer($this->requireActor())
->setContextObject($object);
$button_label = $this->getObjectLinkButtonLabelForMail($object);
$button_uri = $this->getObjectLinkButtonURIForMail($object);
$this->addHeadersAndCommentsToMailBody(
$body,
$xactions,
$button_label,
$button_uri);
$this->addCustomFieldsToMailBody($body, $object, $xactions);
return $body;
}
protected function getObjectLinkButtonLabelForMail(
PhabricatorLiskDAO $object) {
return null;
}
protected function getObjectLinkButtonURIForMail(
PhabricatorLiskDAO $object) {
// Most objects define a "getURI()" method which does what we want, but
// this isn't formally part of an interface at time of writing. Try to
// call the method, expecting an exception if it does not exist.
try {
$uri = $object->getURI();
return PhabricatorEnv::getProductionURI($uri);
} catch (Exception $ex) {
return null;
}
}
/**
* @task mail
*/
protected function addEmailPreferenceSectionToMailBody(
PhabricatorMetaMTAMailBody $body,
PhabricatorLiskDAO $object,
array $xactions) {
$href = PhabricatorEnv::getProductionURI(
'/settings/panel/emailpreferences/');
$body->addLinkSection(pht('EMAIL PREFERENCES'), $href);
}
/**
* @task mail
*/
protected function addHeadersAndCommentsToMailBody(
PhabricatorMetaMTAMailBody $body,
array $xactions,
$object_label = null,
$object_uri = null) {
// First, remove transactions which shouldn't be rendered in mail.
foreach ($xactions as $key => $xaction) {
if ($xaction->shouldHideForMail($xactions)) {
unset($xactions[$key]);
}
}
$headers = array();
$headers_html = array();
$comments = array();
$details = array();
$seen_comment = false;
foreach ($xactions as $xaction) {
// Most mail has zero or one comments. In these cases, we render the
// "alice added a comment." transaction in the header, like a normal
// transaction.
// Some mail, like Differential undraft mail or "!history" mail, may
// have two or more comments. In these cases, we'll put the first
// "alice added a comment." transaction in the header normally, but
// move the other transactions down so they provide context above the
// actual comment.
$comment = $this->getBodyForTextMail($xaction);
if ($comment !== null) {
$is_comment = true;
$comments[] = array(
'xaction' => $xaction,
'comment' => $comment,
'initial' => !$seen_comment,
);
} else {
$is_comment = false;
}
if (!$is_comment || !$seen_comment) {
$header = $this->getTitleForTextMail($xaction);
if ($header !== null) {
$headers[] = $header;
}
$header_html = $this->getTitleForHTMLMail($xaction);
if ($header_html !== null) {
$headers_html[] = $header_html;
}
}
if ($xaction->hasChangeDetailsForMail()) {
$details[] = $xaction;
}
if ($is_comment) {
$seen_comment = true;
}
}
$headers_text = implode("\n", $headers);
$body->addRawPlaintextSection($headers_text);
$headers_html = phutil_implode_html(phutil_tag('br'), $headers_html);
$header_button = null;
if ($object_label !== null && $object_uri !== null) {
$button_style = array(
'text-decoration: none;',
'padding: 4px 8px;',
'margin: 0 8px 8px;',
'float: right;',
'color: #464C5C;',
'font-weight: bold;',
'border-radius: 3px;',
'background-color: #F7F7F9;',
'background-image: linear-gradient(to bottom,#fff,#f1f0f1);',
'display: inline-block;',
'border: 1px solid rgba(71,87,120,.2);',
);
$header_button = phutil_tag(
'a',
array(
'style' => implode(' ', $button_style),
'href' => $object_uri,
),
$object_label);
}
$xactions_style = array();
$header_action = phutil_tag(
'td',
array(),
$header_button);
$header_action = phutil_tag(
'td',
array(
'style' => implode(' ', $xactions_style),
),
array(
$headers_html,
// Add an extra newline to prevent the "View Object" button from
// running into the transaction text in Mail.app text snippet
// previews.
"\n",
));
$headers_html = phutil_tag(
'table',
array(),
phutil_tag('tr', array(), array($header_action, $header_button)));
$body->addRawHTMLSection($headers_html);
foreach ($comments as $spec) {
$xaction = $spec['xaction'];
$comment = $spec['comment'];
$is_initial = $spec['initial'];
// If this is not the first comment in the mail, add the header showing
// who wrote the comment immediately above the comment.
if (!$is_initial) {
$header = $this->getTitleForTextMail($xaction);
if ($header !== null) {
$body->addRawPlaintextSection($header);
}
$header_html = $this->getTitleForHTMLMail($xaction);
if ($header_html !== null) {
$body->addRawHTMLSection($header_html);
}
}
$body->addRemarkupSection(null, $comment);
}
foreach ($details as $xaction) {
$details = $xaction->renderChangeDetailsForMail($body->getViewer());
if ($details !== null) {
$label = $this->getMailDiffSectionHeader($xaction);
$body->addHTMLSection($label, $details);
}
}
}
private function getMailDiffSectionHeader($xaction) {
$type = $xaction->getTransactionType();
+ $object = $this->object;
- $xtype = $this->getModularTransactionType($type);
+ $xtype = $this->getModularTransactionType($object, $type);
if ($xtype) {
return $xtype->getMailDiffSectionHeader();
}
return pht('EDIT DETAILS');
}
/**
* @task mail
*/
protected function addCustomFieldsToMailBody(
PhabricatorMetaMTAMailBody $body,
PhabricatorLiskDAO $object,
array $xactions) {
if ($object instanceof PhabricatorCustomFieldInterface) {
$field_list = PhabricatorCustomField::getObjectFields(
$object,
PhabricatorCustomField::ROLE_TRANSACTIONMAIL);
$field_list->setViewer($this->getActor());
$field_list->readFieldsFromStorage($object);
foreach ($field_list->getFields() as $field) {
$field->updateTransactionMailBody(
$body,
$this,
$xactions);
}
}
}
/**
* @task mail
*/
private function runHeraldMailRules(array $messages) {
foreach ($messages as $message) {
$engine = new HeraldEngine();
$adapter = id(new PhabricatorMailOutboundMailHeraldAdapter())
->setObject($message);
$rules = $engine->loadRulesForAdapter($adapter);
$effects = $engine->applyRules($rules, $adapter);
$engine->applyEffects($effects, $adapter, $rules);
}
}
/* -( Publishing Feed Stories )-------------------------------------------- */
/**
* @task feed
*/
protected function shouldPublishFeedStory(
PhabricatorLiskDAO $object,
array $xactions) {
return false;
}
/**
* @task feed
*/
protected function getFeedStoryType() {
return 'PhabricatorApplicationTransactionFeedStory';
}
/**
* @task feed
*/
protected function getFeedRelatedPHIDs(
PhabricatorLiskDAO $object,
array $xactions) {
$phids = array(
$object->getPHID(),
$this->getActingAsPHID(),
);
if ($object instanceof PhabricatorProjectInterface) {
$project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$object->getPHID(),
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
foreach ($project_phids as $project_phid) {
$phids[] = $project_phid;
}
}
return $phids;
}
/**
* @task feed
*/
protected function getFeedNotifyPHIDs(
PhabricatorLiskDAO $object,
array $xactions) {
// If some transactions are forcing notification delivery, add the forced
// recipients to the notify list.
$force_list = array();
foreach ($xactions as $xaction) {
$force_phids = $xaction->getForceNotifyPHIDs();
if (!$force_phids) {
continue;
}
foreach ($force_phids as $force_phid) {
$force_list[] = $force_phid;
}
}
$to_list = $this->getMailTo($object);
$cc_list = $this->getMailCC($object);
$full_list = array_merge($force_list, $to_list, $cc_list);
$full_list = array_fuse($full_list);
return array_keys($full_list);
}
/**
* @task feed
*/
protected function getFeedStoryData(
PhabricatorLiskDAO $object,
array $xactions) {
$xactions = msortv($xactions, 'newActionStrengthSortVector');
return array(
'objectPHID' => $object->getPHID(),
'transactionPHIDs' => mpull($xactions, 'getPHID'),
);
}
/**
* @task feed
*/
protected function publishFeedStory(
PhabricatorLiskDAO $object,
array $xactions,
array $mailed_phids) {
// Remove transactions which don't publish feed stories or notifications.
// These never show up anywhere, so we don't need to do anything with them.
foreach ($xactions as $key => $xaction) {
if (!$xaction->shouldHideForFeed()) {
continue;
}
if (!$xaction->shouldHideForNotifications()) {
continue;
}
unset($xactions[$key]);
}
if (!$xactions) {
return;
}
$related_phids = $this->feedRelatedPHIDs;
$subscribed_phids = $this->feedNotifyPHIDs;
// Remove muted users from the subscription list so they don't get
// notifications, either.
$muted_phids = $this->mailMutedPHIDs;
if (!is_array($muted_phids)) {
$muted_phids = array();
}
$subscribed_phids = array_fuse($subscribed_phids);
foreach ($muted_phids as $muted_phid) {
unset($subscribed_phids[$muted_phid]);
}
$subscribed_phids = array_values($subscribed_phids);
$story_type = $this->getFeedStoryType();
$story_data = $this->getFeedStoryData($object, $xactions);
$unexpandable_phids = $this->mailUnexpandablePHIDs;
if (!is_array($unexpandable_phids)) {
$unexpandable_phids = array();
}
id(new PhabricatorFeedStoryPublisher())
->setStoryType($story_type)
->setStoryData($story_data)
->setStoryTime(time())
->setStoryAuthorPHID($this->getActingAsPHID())
->setRelatedPHIDs($related_phids)
->setPrimaryObjectPHID($object->getPHID())
->setSubscribedPHIDs($subscribed_phids)
->setUnexpandablePHIDs($unexpandable_phids)
->setMailRecipientPHIDs($mailed_phids)
->setMailTags($this->getMailTags($object, $xactions))
->publish();
}
/* -( Search Index )------------------------------------------------------- */
/**
* @task search
*/
protected function supportsSearch() {
return false;
}
/* -( Herald Integration )-------------------------------------------------- */
protected function shouldApplyHeraldRules(
PhabricatorLiskDAO $object,
array $xactions) {
return false;
}
protected function buildHeraldAdapter(
PhabricatorLiskDAO $object,
array $xactions) {
throw new Exception(pht('No herald adapter specified.'));
}
private function setHeraldAdapter(HeraldAdapter $adapter) {
$this->heraldAdapter = $adapter;
return $this;
}
protected function getHeraldAdapter() {
return $this->heraldAdapter;
}
private function setHeraldTranscript(HeraldTranscript $transcript) {
$this->heraldTranscript = $transcript;
return $this;
}
protected function getHeraldTranscript() {
return $this->heraldTranscript;
}
private function applyHeraldRules(
PhabricatorLiskDAO $object,
array $xactions) {
$adapter = $this->buildHeraldAdapter($object, $xactions)
->setContentSource($this->getContentSource())
->setIsNewObject($this->getIsNewObject())
->setActingAsPHID($this->getActingAsPHID())
->setAppliedTransactions($xactions);
if ($this->getApplicationEmail()) {
$adapter->setApplicationEmail($this->getApplicationEmail());
}
// If this editor is operating in silent mode, tell Herald that we aren't
// going to send any mail. This allows it to skip "the first time this
// rule matches, send me an email" rules which would otherwise match even
// though we aren't going to send any mail.
if ($this->getIsSilent()) {
$adapter->setForbiddenAction(
HeraldMailableState::STATECONST,
HeraldCoreStateReasons::REASON_SILENT);
}
$xscript = HeraldEngine::loadAndApplyRules($adapter);
$this->setHeraldAdapter($adapter);
$this->setHeraldTranscript($xscript);
if ($adapter instanceof HarbormasterBuildableAdapterInterface) {
$buildable_phid = $adapter->getHarbormasterBuildablePHID();
HarbormasterBuildable::applyBuildPlans(
$buildable_phid,
$adapter->getHarbormasterContainerPHID(),
$adapter->getQueuedHarbormasterBuildRequests());
// Whether we queued any builds or not, any automatic buildable for this
// object is now done preparing builds and can transition into a
// completed status.
$buildables = id(new HarbormasterBuildableQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withManualBuildables(false)
->withBuildablePHIDs(array($buildable_phid))
->execute();
foreach ($buildables as $buildable) {
// If this buildable has already moved beyond preparation, we don't
// need to nudge it again.
if (!$buildable->isPreparing()) {
continue;
}
$buildable->sendMessage(
$this->getActor(),
HarbormasterMessageType::BUILDABLE_BUILD,
true);
}
}
$this->mustEncrypt = $adapter->getMustEncryptReasons();
// See PHI1134. Propagate "Must Encrypt" state to sub-editors.
foreach ($this->subEditors as $sub_editor) {
$sub_editor->mustEncrypt = $this->mustEncrypt;
}
$apply_xactions = $this->didApplyHeraldRules($object, $adapter, $xscript);
assert_instances_of($apply_xactions, 'PhabricatorApplicationTransaction');
$queue_xactions = $adapter->getQueuedTransactions();
return array_merge(
array_values($apply_xactions),
array_values($queue_xactions));
}
protected function didApplyHeraldRules(
PhabricatorLiskDAO $object,
HeraldAdapter $adapter,
HeraldTranscript $transcript) {
return array();
}
/* -( Custom Fields )------------------------------------------------------ */
/**
* @task customfield
*/
private function getCustomFieldForTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$field_key = $xaction->getMetadataValue('customfield:key');
if (!$field_key) {
throw new Exception(
pht(
"Custom field transaction has no '%s'!",
'customfield:key'));
}
$field = PhabricatorCustomField::getObjectField(
$object,
PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS,
$field_key);
if (!$field) {
throw new Exception(
pht(
"Custom field transaction has invalid '%s'; field '%s' ".
"is disabled or does not exist.",
'customfield:key',
$field_key));
}
if (!$field->shouldAppearInApplicationTransactions()) {
throw new Exception(
pht(
"Custom field transaction '%s' does not implement ".
"integration for %s.",
$field_key,
'ApplicationTransactions'));
}
$field->setViewer($this->getActor());
return $field;
}
/* -( Files )-------------------------------------------------------------- */
/**
* Extract the PHIDs of any files which these transactions attach.
*
* @task files
*/
private function extractFilePHIDs(
PhabricatorLiskDAO $object,
array $xactions) {
- $changes = $this->getRemarkupChanges($xactions);
- $blocks = mpull($changes, 'getNewValue');
-
$phids = array();
- if ($blocks) {
- $phids[] = PhabricatorMarkupEngine::extractFilePHIDsFromEmbeddedFiles(
- $this->getActor(),
- $blocks);
- }
foreach ($xactions as $xaction) {
$type = $xaction->getTransactionType();
- $xtype = $this->getModularTransactionType($type);
+ $xtype = $this->getModularTransactionType($object, $type);
if ($xtype) {
$phids[] = $xtype->extractFilePHIDs($object, $xaction->getNewValue());
} else {
$phids[] = $this->extractFilePHIDsFromCustomTransaction(
$object,
$xaction);
}
}
$phids = array_unique(array_filter(array_mergev($phids)));
- if (!$phids) {
- return array();
- }
-
- // Only let a user attach files they can actually see, since this would
- // otherwise let you access any file by attaching it to an object you have
- // view permission on.
- $files = id(new PhabricatorFileQuery())
- ->setViewer($this->getActor())
- ->withPHIDs($phids)
- ->execute();
-
- return mpull($files, 'getPHID');
+ return $phids;
}
/**
* @task files
*/
protected function extractFilePHIDsFromCustomTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
return array();
}
- /**
- * @task files
- */
- private function attachFiles(
- PhabricatorLiskDAO $object,
- array $file_phids) {
-
- if (!$file_phids) {
- return;
- }
-
- $editor = new PhabricatorEdgeEditor();
-
- $src = $object->getPHID();
- $type = PhabricatorObjectHasFileEdgeType::EDGECONST;
- foreach ($file_phids as $dst) {
- $editor->addEdge($src, $type, $dst);
- }
-
- $editor->save();
- }
-
private function applyInverseEdgeTransactions(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction,
$inverse_type) {
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$add = array_keys(array_diff_key($new, $old));
$rem = array_keys(array_diff_key($old, $new));
$add = array_fuse($add);
$rem = array_fuse($rem);
$all = $add + $rem;
$nodes = id(new PhabricatorObjectQuery())
->setViewer($this->requireActor())
->withPHIDs($all)
->execute();
$object_phid = $object->getPHID();
foreach ($nodes as $node) {
if (!($node instanceof PhabricatorApplicationTransactionInterface)) {
continue;
}
if ($node instanceof PhabricatorUser) {
// TODO: At least for now, don't record inverse edge transactions
// for users (for example, "alincoln joined project X"): Feed fills
// this role instead.
continue;
}
$node_phid = $node->getPHID();
$editor = $node->getApplicationTransactionEditor();
$template = $node->getApplicationTransactionTemplate();
// See T13082. We have to build these transactions with synthetic values
// because we've already applied the actual edit to the edge database
// table. If we try to apply this transaction naturally, it will no-op
// itself because it doesn't have any effect.
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($node_phid))
->withEdgeTypes(array($inverse_type));
$edge_query->execute();
$edge_phids = $edge_query->getDestinationPHIDs();
$edge_phids = array_fuse($edge_phids);
$new_phids = $edge_phids;
$old_phids = $edge_phids;
if (isset($add[$node_phid])) {
unset($old_phids[$object_phid]);
} else {
$old_phids[$object_phid] = $object_phid;
}
$template
->setTransactionType($xaction->getTransactionType())
->setMetadataValue('edge:type', $inverse_type)
->setOldValue($old_phids)
->setNewValue($new_phids);
$editor = $this->newSubEditor($editor)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->setIsInverseEdgeEditor(true);
$editor->applyTransactions($node, array($template));
}
}
/* -( Workers )------------------------------------------------------------ */
/**
* Load any object state which is required to publish transactions.
*
* This hook is invoked in the main process before we compute data related
* to publishing transactions (like email "To" and "CC" lists), and again in
* the worker before publishing occurs.
*
* @return object Publishable object.
* @task workers
*/
protected function willPublish(PhabricatorLiskDAO $object, array $xactions) {
return $object;
}
/**
* Convert the editor state to a serializable dictionary which can be passed
* to a worker.
*
* This data will be loaded with @{method:loadWorkerState} in the worker.
*
* @return dict<string, wild> Serializable editor state.
* @task workers
*/
private function getWorkerState() {
$state = array();
foreach ($this->getAutomaticStateProperties() as $property) {
$state[$property] = $this->$property;
}
$custom_state = $this->getCustomWorkerState();
$custom_encoding = $this->getCustomWorkerStateEncoding();
$state += array(
'excludeMailRecipientPHIDs' => $this->getExcludeMailRecipientPHIDs(),
'custom' => $this->encodeStateForStorage($custom_state, $custom_encoding),
'custom.encoding' => $custom_encoding,
);
return $state;
}
/**
* Hook; return custom properties which need to be passed to workers.
*
* @return dict<string, wild> Custom properties.
* @task workers
*/
protected function getCustomWorkerState() {
return array();
}
/**
* Hook; return storage encoding for custom properties which need to be
* passed to workers.
*
* This primarily allows binary data to be passed to workers and survive
* JSON encoding.
*
* @return dict<string, string> Property encodings.
* @task workers
*/
protected function getCustomWorkerStateEncoding() {
return array();
}
/**
* Load editor state using a dictionary emitted by @{method:getWorkerState}.
*
* This method is used to load state when running worker operations.
*
* @param dict<string, wild> Editor state, from @{method:getWorkerState}.
* @return this
* @task workers
*/
final public function loadWorkerState(array $state) {
foreach ($this->getAutomaticStateProperties() as $property) {
$this->$property = idx($state, $property);
}
$exclude = idx($state, 'excludeMailRecipientPHIDs', array());
$this->setExcludeMailRecipientPHIDs($exclude);
$custom_state = idx($state, 'custom', array());
$custom_encodings = idx($state, 'custom.encoding', array());
$custom = $this->decodeStateFromStorage($custom_state, $custom_encodings);
$this->loadCustomWorkerState($custom);
return $this;
}
/**
* Hook; set custom properties on the editor from data emitted by
* @{method:getCustomWorkerState}.
*
* @param dict<string, wild> Custom state,
* from @{method:getCustomWorkerState}.
* @return this
* @task workers
*/
protected function loadCustomWorkerState(array $state) {
return $this;
}
/**
* Get a list of object properties which should be automatically sent to
* workers in the state data.
*
* These properties will be automatically stored and loaded by the editor in
* the worker.
*
* @return list<string> List of properties.
* @task workers
*/
private function getAutomaticStateProperties() {
return array(
'parentMessageID',
'isNewObject',
'heraldEmailPHIDs',
'heraldForcedEmailPHIDs',
'heraldHeader',
'mailToPHIDs',
'mailCCPHIDs',
'feedNotifyPHIDs',
'feedRelatedPHIDs',
'feedShouldPublish',
'mailShouldSend',
'mustEncrypt',
'mailStamps',
'mailUnexpandablePHIDs',
'mailMutedPHIDs',
'webhookMap',
'silent',
'sendHistory',
);
}
/**
* Apply encodings prior to storage.
*
* See @{method:getCustomWorkerStateEncoding}.
*
* @param map<string, wild> Map of values to encode.
* @param map<string, string> Map of encodings to apply.
* @return map<string, wild> Map of encoded values.
* @task workers
*/
private function encodeStateForStorage(
array $state,
array $encodings) {
foreach ($state as $key => $value) {
$encoding = idx($encodings, $key);
switch ($encoding) {
case self::STORAGE_ENCODING_BINARY:
// The mechanics of this encoding (serialize + base64) are a little
// awkward, but it allows us encode arrays and still be JSON-safe
// with binary data.
$value = @serialize($value);
if ($value === false) {
throw new Exception(
pht(
'Failed to serialize() value for key "%s".',
$key));
}
$value = base64_encode($value);
if ($value === false) {
throw new Exception(
pht(
'Failed to base64 encode value for key "%s".',
$key));
}
break;
}
$state[$key] = $value;
}
return $state;
}
/**
* Undo storage encoding applied when storing state.
*
* See @{method:getCustomWorkerStateEncoding}.
*
* @param map<string, wild> Map of encoded values.
* @param map<string, string> Map of encodings.
* @return map<string, wild> Map of decoded values.
* @task workers
*/
private function decodeStateFromStorage(
array $state,
array $encodings) {
foreach ($state as $key => $value) {
$encoding = idx($encodings, $key);
switch ($encoding) {
case self::STORAGE_ENCODING_BINARY:
$value = base64_decode($value);
if ($value === false) {
throw new Exception(
pht(
'Failed to base64_decode() value for key "%s".',
$key));
}
$value = unserialize($value);
break;
}
$state[$key] = $value;
}
return $state;
}
/**
* Remove conflicts from a list of projects.
*
* Objects aren't allowed to be tagged with multiple milestones in the same
* group, nor projects such that one tag is the ancestor of any other tag.
* If the list of PHIDs include mutually exclusive projects, remove the
* conflicting projects.
*
* @param list<phid> List of project PHIDs.
* @return list<phid> List with conflicts removed.
*/
private function applyProjectConflictRules(array $phids) {
if (!$phids) {
return array();
}
// Overall, the last project in the list wins in cases of conflict (so when
// you add something, the thing you just added sticks and removes older
// values).
// Beyond that, there are two basic cases:
// Milestones: An object can't be in "A > Sprint 3" and "A > Sprint 4".
// If multiple projects are milestones of the same parent, we only keep the
// last one.
// Ancestor: You can't be in "A" and "A > B". If "A > B" comes later
// in the list, we remove "A" and keep "A > B". If "A" comes later, we
// remove "A > B" and keep "A".
// Note that it's OK to be in "A > B" and "A > C". There's only a conflict
// if one project is an ancestor of another. It's OK to have something
// tagged with multiple projects which share a common ancestor, so long as
// they are not mutual ancestors.
$viewer = PhabricatorUser::getOmnipotentUser();
$projects = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withPHIDs(array_keys($phids))
->execute();
$projects = mpull($projects, null, 'getPHID');
// We're going to build a map from each project with milestones to the last
// milestone in the list. This last milestone is the milestone we'll keep.
$milestone_map = array();
// We're going to build a set of the projects which have no descendants
// later in the list. This allows us to apply both ancestor rules.
$ancestor_map = array();
foreach ($phids as $phid => $ignored) {
$project = idx($projects, $phid);
if (!$project) {
continue;
}
// This is the last milestone we've seen, so set it as the selection for
// the project's parent. This might be setting a new value or overwriting
// an earlier value.
if ($project->isMilestone()) {
$parent_phid = $project->getParentProjectPHID();
$milestone_map[$parent_phid] = $phid;
}
// Since this is the last item in the list we've examined so far, add it
// to the set of projects with no later descendants.
$ancestor_map[$phid] = $phid;
// Remove any ancestors from the set, since this is a later descendant.
foreach ($project->getAncestorProjects() as $ancestor) {
$ancestor_phid = $ancestor->getPHID();
unset($ancestor_map[$ancestor_phid]);
}
}
// Now that we've built the maps, we can throw away all the projects which
// have conflicts.
foreach ($phids as $phid => $ignored) {
$project = idx($projects, $phid);
if (!$project) {
// If a PHID is invalid, we just leave it as-is. We could clean it up,
// but leaving it untouched is less likely to cause collateral damage.
continue;
}
// If this was a milestone, check if it was the last milestone from its
// group in the list. If not, remove it from the list.
if ($project->isMilestone()) {
$parent_phid = $project->getParentProjectPHID();
if ($milestone_map[$parent_phid] !== $phid) {
unset($phids[$phid]);
continue;
}
}
// If a later project in the list is a subproject of this one, it will
// have removed ancestors from the map. If this project does not point
// at itself in the ancestor map, it should be discarded in favor of a
// subproject that comes later.
if (idx($ancestor_map, $phid) !== $phid) {
unset($phids[$phid]);
continue;
}
// If a later project in the list is an ancestor of this one, it will
// have added itself to the map. If any ancestor of this project points
// at itself in the map, this project should be discarded in favor of
// that later ancestor.
foreach ($project->getAncestorProjects() as $ancestor) {
$ancestor_phid = $ancestor->getPHID();
if (isset($ancestor_map[$ancestor_phid])) {
unset($phids[$phid]);
continue 2;
}
}
}
return $phids;
}
/**
* When the view policy for an object is changed, scramble the secret keys
* for attached files to invalidate existing URIs.
*/
private function scrambleFileSecrets($object) {
// If this is a newly created object, we don't need to scramble anything
// since it couldn't have been previously published.
if ($this->getIsNewObject()) {
return;
}
// If the object is a file itself, scramble it.
if ($object instanceof PhabricatorFile) {
if ($this->shouldScramblePolicy($object->getViewPolicy())) {
$object->scrambleSecret();
$object->save();
}
}
- $phid = $object->getPHID();
-
- $attached_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
- $phid,
- PhabricatorObjectHasFileEdgeType::EDGECONST);
- if (!$attached_phids) {
- return;
- }
-
$omnipotent_viewer = PhabricatorUser::getOmnipotentUser();
$files = id(new PhabricatorFileQuery())
->setViewer($omnipotent_viewer)
- ->withPHIDs($attached_phids)
+ ->withAttachedObjectPHIDs(array($object->getPHID()))
->execute();
foreach ($files as $file) {
$view_policy = $file->getViewPolicy();
if ($this->shouldScramblePolicy($view_policy)) {
$file->scrambleSecret();
$file->save();
}
}
}
/**
* Check if a policy is strong enough to justify scrambling. Objects which
* are set to very open policies don't need to scramble their files, and
* files with very open policies don't need to be scrambled when associated
* objects change.
*/
private function shouldScramblePolicy($policy) {
switch ($policy) {
case PhabricatorPolicies::POLICY_PUBLIC:
case PhabricatorPolicies::POLICY_USER:
return false;
}
return true;
}
private function updateWorkboardColumns($object, $const, $old, $new) {
// If an object is removed from a project, remove it from any proxy
// columns for that project. This allows a task which is moved up from a
// milestone to the parent to move back into the "Backlog" column on the
// parent workboard.
if ($const != PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) {
return;
}
// TODO: This should likely be some future WorkboardInterface.
$appears_on_workboards = ($object instanceof ManiphestTask);
if (!$appears_on_workboards) {
return;
}
$removed_phids = array_keys(array_diff_key($old, $new));
if (!$removed_phids) {
return;
}
// Find any proxy columns for the removed projects.
$proxy_columns = id(new PhabricatorProjectColumnQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withProxyPHIDs($removed_phids)
->execute();
if (!$proxy_columns) {
return array();
}
$proxy_phids = mpull($proxy_columns, 'getPHID');
$position_table = new PhabricatorProjectColumnPosition();
$conn_w = $position_table->establishConnection('w');
queryfx(
$conn_w,
'DELETE FROM %T WHERE objectPHID = %s AND columnPHID IN (%Ls)',
$position_table->getTableName(),
$object->getPHID(),
$proxy_phids);
}
- private function getModularTransactionTypes() {
+ private function getModularTransactionTypes(
+ PhabricatorLiskDAO $object) {
+
if ($this->modularTypes === null) {
- $template = $this->object->getApplicationTransactionTemplate();
+ $template = $object->getApplicationTransactionTemplate();
if ($template instanceof PhabricatorModularTransaction) {
$xtypes = $template->newModularTransactionTypes();
foreach ($xtypes as $key => $xtype) {
$xtype = clone $xtype;
$xtype->setEditor($this);
$xtypes[$key] = $xtype;
}
} else {
$xtypes = array();
}
$this->modularTypes = $xtypes;
}
return $this->modularTypes;
}
- private function getModularTransactionType($type) {
- $types = $this->getModularTransactionTypes();
+ private function getModularTransactionType($object, $type) {
+ $types = $this->getModularTransactionTypes($object);
return idx($types, $type);
}
public function getCreateObjectTitle($author, $object) {
return pht('%s created this object.', $author);
}
public function getCreateObjectTitleForFeed($author, $object) {
return pht('%s created an object: %s.', $author, $object);
}
/* -( Queue )-------------------------------------------------------------- */
protected function queueTransaction(
PhabricatorApplicationTransaction $xaction) {
$this->transactionQueue[] = $xaction;
return $this;
}
private function flushTransactionQueue($object) {
if (!$this->transactionQueue) {
return;
}
$xactions = $this->transactionQueue;
$this->transactionQueue = array();
$editor = $this->newEditorCopy();
return $editor->applyTransactions($object, $xactions);
}
final protected function newSubEditor(
PhabricatorApplicationTransactionEditor $template = null) {
$editor = $this->newEditorCopy($template);
$editor->parentEditor = $this;
$this->subEditors[] = $editor;
return $editor;
}
private function newEditorCopy(
PhabricatorApplicationTransactionEditor $template = null) {
if ($template === null) {
$template = newv(get_class($this), array());
}
$editor = id(clone $template)
->setActor($this->getActor())
->setContentSource($this->getContentSource())
->setContinueOnNoEffect($this->getContinueOnNoEffect())
->setContinueOnMissingFields($this->getContinueOnMissingFields())
->setParentMessageID($this->getParentMessageID())
->setIsSilent($this->getIsSilent());
if ($this->actingAsPHID !== null) {
$editor->setActingAsPHID($this->actingAsPHID);
}
$editor->mustEncrypt = $this->mustEncrypt;
$editor->transactionGroupID = $this->getTransactionGroupID();
return $editor;
}
/* -( Stamps )------------------------------------------------------------- */
public function newMailStampTemplates($object) {
$actor = $this->getActor();
$templates = array();
$extensions = $this->newMailExtensions($object);
foreach ($extensions as $extension) {
$stamps = $extension->newMailStampTemplates($object);
foreach ($stamps as $stamp) {
$key = $stamp->getKey();
if (isset($templates[$key])) {
throw new Exception(
pht(
'Mail extension ("%s") defines a stamp template with the '.
'same key ("%s") as another template. Each stamp template '.
'must have a unique key.',
get_class($extension),
$key));
}
$stamp->setViewer($actor);
$templates[$key] = $stamp;
}
}
return $templates;
}
final public function getMailStamp($key) {
if (!isset($this->stampTemplates)) {
throw new PhutilInvalidStateException('newMailStampTemplates');
}
if (!isset($this->stampTemplates[$key])) {
throw new Exception(
pht(
'Editor ("%s") has no mail stamp template with provided key ("%s").',
get_class($this),
$key));
}
return $this->stampTemplates[$key];
}
private function newMailStamps($object, array $xactions) {
$actor = $this->getActor();
$this->stampTemplates = $this->newMailStampTemplates($object);
$extensions = $this->newMailExtensions($object);
$stamps = array();
foreach ($extensions as $extension) {
$extension->newMailStamps($object, $xactions);
}
return $this->stampTemplates;
}
private function newMailExtensions($object) {
$actor = $this->getActor();
$all_extensions = PhabricatorMailEngineExtension::getAllExtensions();
$extensions = array();
foreach ($all_extensions as $key => $template) {
$extension = id(clone $template)
->setViewer($actor)
->setEditor($this);
if ($extension->supportsObject($object)) {
$extensions[$key] = $extension;
}
}
return $extensions;
}
protected function newAuxiliaryMail($object, array $xactions) {
return array();
}
private function generateMailStamps($object, $data) {
if (!$data || !is_array($data)) {
return null;
}
$templates = $this->newMailStampTemplates($object);
foreach ($data as $spec) {
if (!is_array($spec)) {
continue;
}
$key = idx($spec, 'key');
if (!isset($templates[$key])) {
continue;
}
$type = idx($spec, 'type');
if ($templates[$key]->getStampType() !== $type) {
continue;
}
$value = idx($spec, 'value');
$templates[$key]->setValueFromDictionary($value);
}
$results = array();
foreach ($templates as $template) {
$value = $template->getValueForRendering();
$rendered = $template->renderStamps($value);
if ($rendered === null) {
continue;
}
$rendered = (array)$rendered;
foreach ($rendered as $stamp) {
$results[] = $stamp;
}
}
natcasesort($results);
return $results;
}
public function getRemovedRecipientPHIDs() {
return $this->mailRemovedPHIDs;
}
private function buildOldRecipientLists($object, $xactions) {
// See T4776. Before we start making any changes, build a list of the old
// recipients. If a change removes a user from the recipient list for an
// object we still want to notify the user about that change. This allows
// them to respond if they didn't want to be removed.
if (!$this->shouldSendMail($object, $xactions)) {
return;
}
$this->oldTo = $this->getMailTo($object);
$this->oldCC = $this->getMailCC($object);
return $this;
}
private function applyOldRecipientLists() {
$actor_phid = $this->getActingAsPHID();
// If you took yourself off the recipient list (for example, by
// unsubscribing or resigning) assume that you know what you did and
// don't need to be notified.
// If you just moved from "To" to "Cc" (or vice versa), you're still a
// recipient so we don't need to add you back in.
$map = array_fuse($this->mailToPHIDs) + array_fuse($this->mailCCPHIDs);
foreach ($this->oldTo as $phid) {
if ($phid === $actor_phid) {
continue;
}
if (isset($map[$phid])) {
continue;
}
$this->mailToPHIDs[] = $phid;
$this->mailRemovedPHIDs[] = $phid;
}
foreach ($this->oldCC as $phid) {
if ($phid === $actor_phid) {
continue;
}
if (isset($map[$phid])) {
continue;
}
$this->mailCCPHIDs[] = $phid;
$this->mailRemovedPHIDs[] = $phid;
}
return $this;
}
private function queueWebhooks($object, array $xactions) {
$hook_viewer = PhabricatorUser::getOmnipotentUser();
$webhook_map = $this->webhookMap;
if (!is_array($webhook_map)) {
$webhook_map = array();
}
// Add any "Firehose" hooks to the list of hooks we're going to call.
$firehose_hooks = id(new HeraldWebhookQuery())
->setViewer($hook_viewer)
->withStatuses(
array(
HeraldWebhook::HOOKSTATUS_FIREHOSE,
))
->execute();
foreach ($firehose_hooks as $firehose_hook) {
// This is "the hook itself is the reason this hook is being called",
// since we're including it because it's configured as a firehose
// hook.
$hook_phid = $firehose_hook->getPHID();
$webhook_map[$hook_phid][] = $hook_phid;
}
if (!$webhook_map) {
return;
}
// NOTE: We're going to queue calls to disabled webhooks, they'll just
// immediately fail in the worker queue. This makes the behavior more
// visible.
$call_hooks = id(new HeraldWebhookQuery())
->setViewer($hook_viewer)
->withPHIDs(array_keys($webhook_map))
->execute();
foreach ($call_hooks as $call_hook) {
$trigger_phids = idx($webhook_map, $call_hook->getPHID());
$request = HeraldWebhookRequest::initializeNewWebhookRequest($call_hook)
->setObjectPHID($object->getPHID())
->setTransactionPHIDs(mpull($xactions, 'getPHID'))
->setTriggerPHIDs($trigger_phids)
->setRetryMode(HeraldWebhookRequest::RETRY_FOREVER)
->setIsSilentAction((bool)$this->getIsSilent())
->setIsSecureAction((bool)$this->getMustEncrypt())
->save();
$request->queueCall();
}
}
private function hasWarnings($object, $xaction) {
// TODO: For the moment, this is a very un-modular hack to support
// a small number of warnings related to draft revisions. See PHI433.
if (!($object instanceof DifferentialRevision)) {
return false;
}
$type = $xaction->getTransactionType();
// TODO: This doesn't warn for inlines in Audit, even though they have
// the same overall workflow.
if ($type === DifferentialTransaction::TYPE_INLINE) {
return (bool)$xaction->getComment()->getAttribute('editing', false);
}
if (!$object->isDraft()) {
return false;
}
if ($type != PhabricatorTransactions::TYPE_SUBSCRIBERS) {
return false;
}
// We're only going to raise a warning if the transaction adds subscribers
// other than the acting user. (This implementation is clumsy because the
// code runs before a lot of normalization occurs.)
$old = $this->getTransactionOldValue($object, $xaction);
$new = $this->getPHIDTransactionNewValue($xaction, $old);
$old = array_fuse($old);
$new = array_fuse($new);
$add = array_diff_key($new, $old);
unset($add[$this->getActingAsPHID()]);
if (!$add) {
return false;
}
return true;
}
private function buildHistoryMail(PhabricatorLiskDAO $object) {
$viewer = $this->requireActor();
$recipient_phid = $this->getActingAsPHID();
// Load every transaction so we can build a mail message with a complete
// history for the object.
$query = PhabricatorApplicationTransactionQuery::newQueryForObject($object);
$xactions = $query
->setViewer($viewer)
->withObjectPHIDs(array($object->getPHID()))
->execute();
$xactions = array_reverse($xactions);
$mail_messages = $this->buildMailWithRecipients(
$object,
$xactions,
array($recipient_phid),
array(),
array());
$mail = head($mail_messages);
// Since the user explicitly requested "!history", force delivery of this
// message regardless of their other mail settings.
$mail->setForceDelivery(true);
return $mail;
}
public function newAutomaticInlineTransactions(
PhabricatorLiskDAO $object,
$transaction_type,
PhabricatorCursorPagedPolicyAwareQuery $query_template) {
$actor = $this->getActor();
$inlines = id(clone $query_template)
->setViewer($actor)
->withObjectPHIDs(array($object->getPHID()))
->withPublishableComments(true)
->needAppliedDrafts(true)
->needReplyToComments(true)
->execute();
$inlines = msort($inlines, 'getID');
$xactions = array();
foreach ($inlines as $key => $inline) {
$xactions[] = $object->getApplicationTransactionTemplate()
->setTransactionType($transaction_type)
->attachComment($inline);
}
$state_xaction = $this->newInlineStateTransaction(
$object,
$query_template);
if ($state_xaction) {
$xactions[] = $state_xaction;
}
return $xactions;
}
protected function newInlineStateTransaction(
PhabricatorLiskDAO $object,
PhabricatorCursorPagedPolicyAwareQuery $query_template) {
$actor_phid = $this->getActingAsPHID();
$author_phid = $object->getAuthorPHID();
$actor_is_author = ($actor_phid == $author_phid);
$state_map = PhabricatorTransactions::getInlineStateMap();
$inline_query = id(clone $query_template)
->setViewer($this->getActor())
->withObjectPHIDs(array($object->getPHID()))
->withFixedStates(array_keys($state_map))
->withPublishableComments(true);
if ($actor_is_author) {
$inline_query->withPublishedComments(true);
}
$inlines = $inline_query->execute();
if (!$inlines) {
return null;
}
$old_value = mpull($inlines, 'getFixedState', 'getPHID');
$new_value = array();
foreach ($old_value as $key => $state) {
$new_value[$key] = $state_map[$state];
}
// See PHI995. Copy some information about the inlines into the transaction
// so we can tailor rendering behavior. In particular, we don't want to
// render transactions about users marking their own inlines as "Done".
$inline_details = array();
foreach ($inlines as $inline) {
$inline_details[$inline->getPHID()] = array(
'authorPHID' => $inline->getAuthorPHID(),
);
}
return $object->getApplicationTransactionTemplate()
->setTransactionType(PhabricatorTransactions::TYPE_INLINESTATE)
->setIgnoreOnNoEffect(true)
->setMetadataValue('inline.details', $inline_details)
->setOldValue($old_value)
->setNewValue($new_value);
}
private function requireMFA(PhabricatorLiskDAO $object, array $xactions) {
$actor = $this->getActor();
// Let omnipotent editors skip MFA. This is mostly aimed at scripts.
if ($actor->isOmnipotent()) {
return;
}
$editor_class = get_class($this);
$object_phid = $object->getPHID();
if ($object_phid) {
$workflow_key = sprintf(
'editor(%s).phid(%s)',
$editor_class,
$object_phid);
} else {
$workflow_key = sprintf(
'editor(%s).new()',
$editor_class);
}
$request = $this->getRequest();
if ($request === null) {
$source_type = $this->getContentSource()->getSourceTypeConstant();
$conduit_type = PhabricatorConduitContentSource::SOURCECONST;
$is_conduit = ($source_type === $conduit_type);
if ($is_conduit) {
throw new Exception(
pht(
'This transaction group requires MFA to apply, but you can not '.
'provide an MFA response via Conduit. Edit this object via the '.
'web UI.'));
} else {
throw new Exception(
pht(
'This transaction group requires MFA to apply, but the Editor was '.
'not configured with a Request. This workflow can not perform an '.
'MFA check.'));
}
}
$cancel_uri = $this->getCancelURI();
if ($cancel_uri === null) {
throw new Exception(
pht(
'This transaction group requires MFA to apply, but the Editor was '.
'not configured with a Cancel URI. This workflow can not perform '.
'an MFA check.'));
}
$token = id(new PhabricatorAuthSessionEngine())
->setWorkflowKey($workflow_key)
->requireHighSecurityToken($actor, $request, $cancel_uri);
if (!$token->getIsUnchallengedToken()) {
foreach ($xactions as $xaction) {
$xaction->setIsMFATransaction(true);
}
}
}
private function newMFATransactions(
PhabricatorLiskDAO $object,
array $xactions) {
$has_engine = ($object instanceof PhabricatorEditEngineMFAInterface);
if ($has_engine) {
$engine = PhabricatorEditEngineMFAEngine::newEngineForObject($object)
->setViewer($this->getActor());
$require_mfa = $engine->shouldRequireMFA();
$try_mfa = $engine->shouldTryMFA();
} else {
$require_mfa = false;
$try_mfa = false;
}
// If the user is mentioning an MFA object on another object or creating
// a relationship like "parent" or "child" to this object, we always
// allow the edit to move forward without requiring MFA.
if ($this->getIsInverseEdgeEditor()) {
return $xactions;
}
if (!$require_mfa) {
// If the object hasn't already opted into MFA, see if any of the
// transactions want it.
if (!$try_mfa) {
foreach ($xactions as $xaction) {
$type = $xaction->getTransactionType();
- $xtype = $this->getModularTransactionType($type);
+ $xtype = $this->getModularTransactionType($object, $type);
if ($xtype) {
$xtype = clone $xtype;
$xtype->setStorage($xaction);
if ($xtype->shouldTryMFA($object, $xaction)) {
$try_mfa = true;
break;
}
}
}
}
if ($try_mfa) {
$this->setShouldRequireMFA(true);
}
return $xactions;
}
$type_mfa = PhabricatorTransactions::TYPE_MFA;
$has_mfa = false;
foreach ($xactions as $xaction) {
if ($xaction->getTransactionType() === $type_mfa) {
$has_mfa = true;
break;
}
}
if ($has_mfa) {
return $xactions;
}
$template = $object->getApplicationTransactionTemplate();
$mfa_xaction = id(clone $template)
->setTransactionType($type_mfa)
->setNewValue(true);
array_unshift($xactions, $mfa_xaction);
return $xactions;
}
private function getTitleForTextMail(
PhabricatorApplicationTransaction $xaction) {
$type = $xaction->getTransactionType();
+ $object = $this->object;
- $xtype = $this->getModularTransactionType($type);
+ $xtype = $this->getModularTransactionType($object, $type);
if ($xtype) {
$xtype = clone $xtype;
$xtype->setStorage($xaction);
$comment = $xtype->getTitleForTextMail();
if ($comment !== false) {
return $comment;
}
}
return $xaction->getTitleForTextMail();
}
private function getTitleForHTMLMail(
PhabricatorApplicationTransaction $xaction) {
$type = $xaction->getTransactionType();
+ $object = $this->object;
- $xtype = $this->getModularTransactionType($type);
+ $xtype = $this->getModularTransactionType($object, $type);
if ($xtype) {
$xtype = clone $xtype;
$xtype->setStorage($xaction);
$comment = $xtype->getTitleForHTMLMail();
if ($comment !== false) {
return $comment;
}
}
return $xaction->getTitleForHTMLMail();
}
private function getBodyForTextMail(
PhabricatorApplicationTransaction $xaction) {
$type = $xaction->getTransactionType();
+ $object = $this->object;
- $xtype = $this->getModularTransactionType($type);
+ $xtype = $this->getModularTransactionType($object, $type);
if ($xtype) {
$xtype = clone $xtype;
$xtype->setStorage($xaction);
$comment = $xtype->getBodyForTextMail();
if ($comment !== false) {
return $comment;
}
}
return $xaction->getBodyForMail();
}
private function isLockOverrideTransaction(
PhabricatorApplicationTransaction $xaction) {
// See PHI1209. When an object is locked, certain types of transactions
// can still be applied without requiring a policy check, like subscribing
// or unsubscribing. We don't want these transactions to show the "Lock
// Override" icon in the transaction timeline.
// We could test if a transaction did no direct policy checks, but it may
// have done additional policy checks during validation, so this is not a
// reliable test (and could cause false negatives, where edits which did
// override a lock are not marked properly).
// For now, do this in a narrow way and just check against a hard-coded
// list of non-override transaction situations. Some day, this should
// likely be modularized.
// Inverse edge edits don't interact with locks.
if ($this->getIsInverseEdgeEditor()) {
return false;
}
// For now, all edits other than subscribes always override locks.
$type = $xaction->getTransactionType();
if ($type !== PhabricatorTransactions::TYPE_SUBSCRIBERS) {
return true;
}
// Subscribes override locks if they affect any users other than the
// acting user.
$acting_phid = $this->getActingAsPHID();
$old = array_fuse($xaction->getOldValue());
$new = array_fuse($xaction->getNewValue());
$add = array_diff_key($new, $old);
$rem = array_diff_key($old, $new);
$all = $add + $rem;
foreach ($all as $phid) {
if ($phid !== $acting_phid) {
return true;
}
}
return false;
}
/* -( Extensions )--------------------------------------------------------- */
private function validateTransactionsWithExtensions(
PhabricatorLiskDAO $object,
array $xactions) {
$errors = array();
$extensions = $this->getEditorExtensions();
foreach ($extensions as $extension) {
$extension_errors = $extension
->setObject($object)
->validateTransactions($object, $xactions);
assert_instances_of(
$extension_errors,
'PhabricatorApplicationTransactionValidationError');
$errors[] = $extension_errors;
}
return array_mergev($errors);
}
private function getEditorExtensions() {
if ($this->extensions === null) {
$this->extensions = $this->newEditorExtensions();
}
return $this->extensions;
}
private function newEditorExtensions() {
$extensions = PhabricatorEditorExtension::getAllExtensions();
$actor = $this->getActor();
$object = $this->object;
foreach ($extensions as $key => $extension) {
$extension = id(clone $extension)
->setViewer($actor)
->setEditor($this)
->setObject($object);
if (!$extension->supportsObject($this, $object)) {
unset($extensions[$key]);
continue;
}
$extensions[$key] = $extension;
}
return $extensions;
}
}
diff --git a/src/applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php b/src/applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php
index 4f6f45bea7..eb4d4d8174 100644
--- a/src/applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php
+++ b/src/applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php
@@ -1,123 +1,119 @@
<?php
abstract class PhabricatorApplicationTransactionCommentQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $authorPHIDs;
private $phids;
private $transactionPHIDs;
private $isDeleted;
private $hasTransaction;
abstract protected function newApplicationTransactionCommentTemplate();
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withTransactionPHIDs(array $transaction_phids) {
$this->transactionPHIDs = $transaction_phids;
return $this;
}
public function withAuthorPHIDs(array $phids) {
$this->authorPHIDs = $phids;
return $this;
}
public function withIsDeleted($deleted) {
$this->isDeleted = $deleted;
return $this;
}
public function withHasTransaction($has_transaction) {
$this->hasTransaction = $has_transaction;
return $this;
}
public function newResultObject() {
return $this->newApplicationTransactionCommentTemplate();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
$alias = $this->getPrimaryTableAlias();
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'%T.id IN (%Ld)',
$alias,
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'%T.phid IN (%Ls)',
$alias,
$this->phids);
}
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'%T.authorPHID IN (%Ls)',
$alias,
$this->authorPHIDs);
}
if ($this->transactionPHIDs !== null) {
$where[] = qsprintf(
$conn,
'%T.transactionPHID IN (%Ls)',
$alias,
$this->transactionPHIDs);
}
if ($this->isDeleted !== null) {
$where[] = qsprintf(
$conn,
'%T.isDeleted = %d',
$alias,
(int)$this->isDeleted);
}
if ($this->hasTransaction !== null) {
if ($this->hasTransaction) {
$where[] = qsprintf(
$conn,
'%T.transactionPHID IS NOT NULL',
$alias);
} else {
$where[] = qsprintf(
$conn,
'%T.transactionPHID IS NULL',
$alias);
}
}
return $where;
}
protected function getPrimaryTableAlias() {
return 'xcomment';
}
public function getQueryApplicationClass() {
// TODO: Figure out the app via the template?
return null;
}
}
diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
index 8abb5178d5..51ed2adba8 100644
--- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
+++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
@@ -1,1828 +1,2031 @@
<?php
abstract class PhabricatorApplicationTransaction
extends PhabricatorLiskDAO
implements
PhabricatorPolicyInterface,
PhabricatorDestructibleInterface {
const TARGET_TEXT = 'text';
const TARGET_HTML = 'html';
protected $phid;
protected $objectPHID;
protected $authorPHID;
protected $viewPolicy;
protected $editPolicy;
protected $commentPHID;
protected $commentVersion = 0;
protected $transactionType;
protected $oldValue;
protected $newValue;
protected $metadata = array();
protected $contentSource;
private $comment;
private $commentNotLoaded;
private $handles;
private $renderingTarget = self::TARGET_HTML;
private $transactionGroup = array();
private $viewer = self::ATTACHABLE;
private $object = self::ATTACHABLE;
private $oldValueHasBeenSet = false;
private $ignoreOnNoEffect;
/**
* Flag this transaction as a pure side-effect which should be ignored when
* applying transactions if it has no effect, even if transaction application
* would normally fail. This both provides users with better error messages
* and allows transactions to perform optional side effects.
*/
public function setIgnoreOnNoEffect($ignore) {
$this->ignoreOnNoEffect = $ignore;
return $this;
}
public function getIgnoreOnNoEffect() {
return $this->ignoreOnNoEffect;
}
public function shouldGenerateOldValue() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_TOKEN:
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
case PhabricatorTransactions::TYPE_INLINESTATE:
return false;
}
return true;
}
abstract public function getApplicationTransactionType();
private function getApplicationObjectTypeName() {
$types = PhabricatorPHIDType::getAllTypes();
$type = idx($types, $this->getApplicationTransactionType());
if ($type) {
return $type->getTypeName();
}
return pht('Object');
}
public function getApplicationTransactionCommentObject() {
return null;
}
public function getMetadataValue($key, $default = null) {
return idx($this->metadata, $key, $default);
}
public function setMetadataValue($key, $value) {
$this->metadata[$key] = $value;
return $this;
}
public function generatePHID() {
$type = PhabricatorApplicationTransactionTransactionPHIDType::TYPECONST;
$subtype = $this->getApplicationTransactionType();
return PhabricatorPHID::generateNewPHID($type, $subtype);
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'oldValue' => self::SERIALIZATION_JSON,
'newValue' => self::SERIALIZATION_JSON,
'metadata' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'commentPHID' => 'phid?',
'commentVersion' => 'uint32',
'contentSource' => 'text',
'transactionType' => 'text32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_object' => array(
'columns' => array('objectPHID'),
),
),
) + parent::getConfiguration();
}
public function setContentSource(PhabricatorContentSource $content_source) {
$this->contentSource = $content_source->serialize();
return $this;
}
public function getContentSource() {
return PhabricatorContentSource::newFromSerialized($this->contentSource);
}
public function hasComment() {
$comment = $this->getComment();
if (!$comment) {
return false;
}
if ($comment->isEmptyComment()) {
return false;
}
return true;
}
public function getComment() {
if ($this->commentNotLoaded) {
throw new Exception(pht('Comment for this transaction was not loaded.'));
}
return $this->comment;
}
public function setIsCreateTransaction($create) {
return $this->setMetadataValue('core.create', $create);
}
public function getIsCreateTransaction() {
return (bool)$this->getMetadataValue('core.create', false);
}
public function setIsDefaultTransaction($default) {
return $this->setMetadataValue('core.default', $default);
}
public function getIsDefaultTransaction() {
return (bool)$this->getMetadataValue('core.default', false);
}
public function setIsSilentTransaction($silent) {
return $this->setMetadataValue('core.silent', $silent);
}
public function getIsSilentTransaction() {
return (bool)$this->getMetadataValue('core.silent', false);
}
public function setIsMFATransaction($mfa) {
return $this->setMetadataValue('core.mfa', $mfa);
}
public function getIsMFATransaction() {
return (bool)$this->getMetadataValue('core.mfa', false);
}
public function setIsLockOverrideTransaction($override) {
return $this->setMetadataValue('core.lock-override', $override);
}
public function getIsLockOverrideTransaction() {
return (bool)$this->getMetadataValue('core.lock-override', false);
}
public function setTransactionGroupID($group_id) {
return $this->setMetadataValue('core.groupID', $group_id);
}
public function getTransactionGroupID() {
return $this->getMetadataValue('core.groupID', null);
}
public function attachComment(
PhabricatorApplicationTransactionComment $comment) {
$this->comment = $comment;
$this->commentNotLoaded = false;
return $this;
}
public function setCommentNotLoaded($not_loaded) {
$this->commentNotLoaded = $not_loaded;
return $this;
}
public function attachObject($object) {
$this->object = $object;
return $this;
}
public function getObject() {
return $this->assertAttached($this->object);
}
public function getRemarkupChanges() {
$changes = $this->newRemarkupChanges();
assert_instances_of($changes, 'PhabricatorTransactionRemarkupChange');
// Convert older-style remarkup blocks into newer-style remarkup changes.
// This builds changes that do not have the correct "old value", so rules
// that operate differently against edits (like @user mentions) won't work
// properly.
foreach ($this->getRemarkupBlocks() as $block) {
$changes[] = $this->newRemarkupChange()
->setOldValue(null)
->setNewValue($block);
}
$comment = $this->getComment();
if ($comment) {
if ($comment->hasOldComment()) {
$old_value = $comment->getOldComment()->getContent();
} else {
$old_value = null;
}
$new_value = $comment->getContent();
$changes[] = $this->newRemarkupChange()
->setOldValue($old_value)
->setNewValue($new_value);
}
+ $metadata = $this->getMetadataValue('remarkup.control', array());
+ foreach ($changes as $change) {
+ if (!$change->getMetadata()) {
+ $change->setMetadata($metadata);
+ }
+ }
+
return $changes;
}
protected function newRemarkupChanges() {
return array();
}
protected function newRemarkupChange() {
return id(new PhabricatorTransactionRemarkupChange())
->setTransaction($this);
}
/**
* @deprecated
*/
public function getRemarkupBlocks() {
$blocks = array();
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
$custom_blocks = $field->getApplicationTransactionRemarkupBlocks(
$this);
foreach ($custom_blocks as $custom_block) {
$blocks[] = $custom_block;
}
}
break;
}
return $blocks;
}
public function setOldValue($value) {
$this->oldValueHasBeenSet = true;
$this->writeField('oldValue', $value);
return $this;
}
public function hasOldValue() {
return $this->oldValueHasBeenSet;
}
public function newChronologicalSortVector() {
return id(new PhutilSortVector())
->addInt((int)$this->getDateCreated())
->addInt((int)$this->getID());
}
/* -( Rendering )---------------------------------------------------------- */
public function setRenderingTarget($rendering_target) {
$this->renderingTarget = $rendering_target;
return $this;
}
public function getRenderingTarget() {
return $this->renderingTarget;
}
public function attachViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->assertAttached($this->viewer);
}
public function getRequiredHandlePHIDs() {
$phids = array();
$old = $this->getOldValue();
$new = $this->getNewValue();
$phids[] = array($this->getAuthorPHID());
$phids[] = array($this->getObjectPHID());
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
$phids[] = $field->getApplicationTransactionRequiredHandlePHIDs(
$this);
}
break;
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
$phids[] = $old;
$phids[] = $new;
break;
+ case PhabricatorTransactions::TYPE_FILE:
+ $phids[] = array_keys($old + $new);
+ break;
case PhabricatorTransactions::TYPE_EDGE:
$record = PhabricatorEdgeChangeRecord::newFromTransaction($this);
$phids[] = $record->getChangedPHIDs();
break;
case PhabricatorTransactions::TYPE_COLUMNS:
foreach ($new as $move) {
$phids[] = array(
$move['columnPHID'],
$move['boardPHID'],
);
$phids[] = $move['fromColumnPHIDs'];
}
break;
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
case PhabricatorTransactions::TYPE_INTERACT_POLICY:
if (!PhabricatorPolicyQuery::isSpecialPolicy($old)) {
$phids[] = array($old);
}
if (!PhabricatorPolicyQuery::isSpecialPolicy($new)) {
$phids[] = array($new);
}
break;
case PhabricatorTransactions::TYPE_SPACE:
if ($old) {
$phids[] = array($old);
}
if ($new) {
$phids[] = array($new);
}
break;
case PhabricatorTransactions::TYPE_TOKEN:
break;
}
if ($this->getComment()) {
$phids[] = array($this->getComment()->getAuthorPHID());
}
return array_mergev($phids);
}
public function setHandles(array $handles) {
$this->handles = $handles;
return $this;
}
public function getHandle($phid) {
if (empty($this->handles[$phid])) {
throw new Exception(
pht(
'Transaction ("%s", of type "%s") requires a handle ("%s") that it '.
'did not load.',
$this->getPHID(),
$this->getTransactionType(),
$phid));
}
return $this->handles[$phid];
}
public function getHandleIfExists($phid) {
return idx($this->handles, $phid);
}
public function getHandles() {
if ($this->handles === null) {
throw new Exception(
pht('Transaction requires handles and it did not load them.'));
}
return $this->handles;
}
public function renderHandleLink($phid) {
if ($this->renderingTarget == self::TARGET_HTML) {
return $this->getHandle($phid)->renderHovercardLink();
} else {
return $this->getHandle($phid)->getLinkName();
}
}
public function renderHandleList(array $phids) {
$links = array();
foreach ($phids as $phid) {
$links[] = $this->renderHandleLink($phid);
}
if ($this->renderingTarget == self::TARGET_HTML) {
return phutil_implode_html(', ', $links);
} else {
return implode(', ', $links);
}
}
private function renderSubscriberList(array $phids, $change_type) {
if ($this->getRenderingTarget() == self::TARGET_TEXT) {
return $this->renderHandleList($phids);
} else {
$handles = array_select_keys($this->getHandles(), $phids);
return id(new SubscriptionListStringBuilder())
->setHandles($handles)
->setObjectPHID($this->getPHID())
->buildTransactionString($change_type);
}
}
protected function renderPolicyName($phid, $state = 'old') {
$policy = PhabricatorPolicy::newFromPolicyAndHandle(
$phid,
$this->getHandleIfExists($phid));
$ref = $policy->newRef($this->getViewer());
if ($this->renderingTarget == self::TARGET_HTML) {
$output = $ref->newTransactionLink($state, $this);
} else {
$output = $ref->getPolicyDisplayName();
}
return $output;
}
public function getIcon() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$comment = $this->getComment();
if ($comment && $comment->getIsRemoved()) {
return 'fa-trash';
}
return 'fa-comment';
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
$old = $this->getOldValue();
$new = $this->getNewValue();
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
if ($add && $rem) {
return 'fa-user';
} else if ($add) {
return 'fa-user-plus';
} else if ($rem) {
return 'fa-user-times';
} else {
return 'fa-user';
}
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
case PhabricatorTransactions::TYPE_INTERACT_POLICY:
return 'fa-lock';
case PhabricatorTransactions::TYPE_EDGE:
switch ($this->getMetadataValue('edge:type')) {
case DiffusionCommitRevertedByCommitEdgeType::EDGECONST:
return 'fa-undo';
case DiffusionCommitRevertsCommitEdgeType::EDGECONST:
return 'fa-ambulance';
}
return 'fa-link';
case PhabricatorTransactions::TYPE_TOKEN:
return 'fa-trophy';
case PhabricatorTransactions::TYPE_SPACE:
return 'fa-th-large';
case PhabricatorTransactions::TYPE_COLUMNS:
return 'fa-columns';
case PhabricatorTransactions::TYPE_MFA:
return 'fa-vcard';
}
return 'fa-pencil';
}
public function getToken() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_TOKEN:
$old = $this->getOldValue();
$new = $this->getNewValue();
if ($new) {
$icon = substr($new, 10);
} else {
$icon = substr($old, 10);
}
return array($icon, !$this->getNewValue());
}
return array(null, null);
}
public function getColor() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT;
$comment = $this->getComment();
if ($comment && $comment->getIsRemoved()) {
return 'black';
}
break;
case PhabricatorTransactions::TYPE_EDGE:
switch ($this->getMetadataValue('edge:type')) {
case DiffusionCommitRevertedByCommitEdgeType::EDGECONST:
return 'pink';
case DiffusionCommitRevertsCommitEdgeType::EDGECONST:
return 'sky';
}
break;
case PhabricatorTransactions::TYPE_MFA;
return 'pink';
}
return null;
}
protected function getTransactionCustomField() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$key = $this->getMetadataValue('customfield:key');
if (!$key) {
return null;
}
$object = $this->getObject();
if (!($object instanceof PhabricatorCustomFieldInterface)) {
return null;
}
$field = PhabricatorCustomField::getObjectField(
$object,
PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS,
$key);
if (!$field) {
return null;
}
$field->setViewer($this->getViewer());
return $field;
}
return null;
}
public function shouldHide() {
// Never hide comments.
if ($this->hasComment()) {
return false;
}
$xaction_type = $this->getTransactionType();
// Always hide requests for object history.
if ($xaction_type === PhabricatorTransactions::TYPE_HISTORY) {
return true;
}
+ // Always hide file attach/detach transactions.
+ if ($xaction_type === PhabricatorTransactions::TYPE_FILE) {
+ if ($this->getMetadataValue('attach.implicit')) {
+ return true;
+ }
+ }
+
// Hide creation transactions if the old value is empty. These are
// transactions like "alice set the task title to: ...", which are
// essentially never interesting.
if ($this->getIsCreateTransaction()) {
switch ($xaction_type) {
case PhabricatorTransactions::TYPE_CREATE:
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
case PhabricatorTransactions::TYPE_INTERACT_POLICY:
case PhabricatorTransactions::TYPE_SPACE:
break;
case PhabricatorTransactions::TYPE_SUBTYPE:
return true;
default:
$old = $this->getOldValue();
if (is_array($old) && !$old) {
return true;
}
if (!is_array($old)) {
- if (!strlen($old)) {
+ if ($old === '' || $old === null) {
return true;
}
// The integer 0 is also uninteresting by default; this is often
// an "off" flag for something like "All Day Event".
if ($old === 0) {
return true;
}
}
break;
}
}
// Hide creation transactions setting values to defaults, even if
// the old value is not empty. For example, tasks may have a global
// default view policy of "All Users", but a particular form sets the
// policy to "Administrators". The transaction corresponding to this
// change is not interesting, since it is the default behavior of the
// form.
if ($this->getIsCreateTransaction()) {
if ($this->getIsDefaultTransaction()) {
return true;
}
}
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
case PhabricatorTransactions::TYPE_INTERACT_POLICY:
case PhabricatorTransactions::TYPE_SPACE:
if ($this->getIsCreateTransaction()) {
break;
}
// TODO: Remove this eventually, this is handling old changes during
// object creation prior to the introduction of "create" and "default"
// transaction display flags.
// NOTE: We can also hit this case with Space transactions that later
// update a default space (`null`) to an explicit space, so handling
// the Space case may require some finesse.
if ($this->getOldValue() === null) {
return true;
} else {
return false;
}
break;
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->shouldHideInApplicationTransactions($this);
}
break;
case PhabricatorTransactions::TYPE_COLUMNS:
return !$this->getInterestingMoves($this->getNewValue());
case PhabricatorTransactions::TYPE_EDGE:
$edge_type = $this->getMetadataValue('edge:type');
switch ($edge_type) {
case PhabricatorObjectMentionsObjectEdgeType::EDGECONST:
case ManiphestTaskHasDuplicateTaskEdgeType::EDGECONST:
case ManiphestTaskIsDuplicateOfTaskEdgeType::EDGECONST:
case PhabricatorMutedEdgeType::EDGECONST:
case PhabricatorMutedByEdgeType::EDGECONST:
return true;
case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST:
$record = PhabricatorEdgeChangeRecord::newFromTransaction($this);
$add = $record->getAddedPHIDs();
$add_value = reset($add);
$add_handle = $this->getHandle($add_value);
if ($add_handle->getPolicyFiltered()) {
return true;
}
return false;
break;
default:
break;
}
break;
case PhabricatorTransactions::TYPE_INLINESTATE:
list($done, $undone) = $this->getInterestingInlineStateChangeCounts();
if (!$done && !$undone) {
return true;
}
break;
}
return false;
}
public function shouldHideForMail(array $xactions) {
if ($this->isSelfSubscription()) {
return true;
}
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_TOKEN:
return true;
- case PhabricatorTransactions::TYPE_EDGE:
+ case PhabricatorTransactions::TYPE_EDGE:
$edge_type = $this->getMetadataValue('edge:type');
switch ($edge_type) {
case PhabricatorObjectMentionsObjectEdgeType::EDGECONST:
case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST:
case DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST:
case DifferentialRevisionDependedOnByRevisionEdgeType::EDGECONST:
case ManiphestTaskHasCommitEdgeType::EDGECONST:
case DiffusionCommitHasTaskEdgeType::EDGECONST:
case DiffusionCommitHasRevisionEdgeType::EDGECONST:
case DifferentialRevisionHasCommitEdgeType::EDGECONST:
return true;
case PhabricatorProjectObjectHasProjectEdgeType::EDGECONST:
// When an object is first created, we hide any corresponding
// project transactions in the web UI because you can just look at
// the UI element elsewhere on screen to see which projects it
// is tagged with. However, in mail there's no other way to get
// this information, and it has some amount of value to users, so
// we keep the transaction. See T10493.
return false;
default:
break;
}
break;
}
if ($this->isInlineCommentTransaction()) {
$inlines = array();
// If there's a normal comment, we don't need to publish the inline
// transaction, since the normal comment covers things.
foreach ($xactions as $xaction) {
if ($xaction->isInlineCommentTransaction()) {
$inlines[] = $xaction;
continue;
}
// We found a normal comment, so hide this inline transaction.
if ($xaction->hasComment()) {
return true;
}
}
// If there are several inline comments, only publish the first one.
if ($this !== head($inlines)) {
return true;
}
}
return $this->shouldHide();
}
public function shouldHideForFeed() {
if ($this->isSelfSubscription()) {
return true;
}
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_TOKEN:
case PhabricatorTransactions::TYPE_MFA:
return true;
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
// See T8952. When an application (usually Herald) modifies
// subscribers, this tends to be very uninteresting.
if ($this->isApplicationAuthor()) {
return true;
}
break;
case PhabricatorTransactions::TYPE_EDGE:
$edge_type = $this->getMetadataValue('edge:type');
switch ($edge_type) {
case PhabricatorObjectMentionsObjectEdgeType::EDGECONST:
case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST:
case DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST:
case DifferentialRevisionDependedOnByRevisionEdgeType::EDGECONST:
case ManiphestTaskHasCommitEdgeType::EDGECONST:
case DiffusionCommitHasTaskEdgeType::EDGECONST:
case DiffusionCommitHasRevisionEdgeType::EDGECONST:
case DifferentialRevisionHasCommitEdgeType::EDGECONST:
return true;
default:
break;
}
break;
case PhabricatorTransactions::TYPE_INLINESTATE:
return true;
}
return $this->shouldHide();
}
public function shouldHideForNotifications() {
return $this->shouldHideForFeed();
}
private function getTitleForMailWithRenderingTarget($new_target) {
$old_target = $this->getRenderingTarget();
try {
$this->setRenderingTarget($new_target);
$result = $this->getTitleForMail();
} catch (Exception $ex) {
$this->setRenderingTarget($old_target);
throw $ex;
}
$this->setRenderingTarget($old_target);
return $result;
}
public function getTitleForMail() {
return $this->getTitle();
}
public function getTitleForTextMail() {
return $this->getTitleForMailWithRenderingTarget(self::TARGET_TEXT);
}
public function getTitleForHTMLMail() {
// TODO: For now, rendering this with TARGET_HTML generates links with
// bad targets ("/x/y/" instead of "https://dev.example.com/x/y/"). Throw
// a rug over the issue for the moment. See T12921.
$title = $this->getTitleForMailWithRenderingTarget(self::TARGET_TEXT);
if ($title === null) {
return null;
}
if ($this->hasChangeDetails()) {
$details_uri = $this->getChangeDetailsURI();
$details_uri = PhabricatorEnv::getProductionURI($details_uri);
$show_details = phutil_tag(
'a',
array(
'href' => $details_uri,
),
pht('(Show Details)'));
$title = array($title, ' ', $show_details);
}
return $title;
}
public function getChangeDetailsURI() {
return '/transactions/detail/'.$this->getPHID().'/';
}
public function getBodyForMail() {
if ($this->isInlineCommentTransaction()) {
// We don't return inline comment content as mail body content, because
// applications need to contextualize it (by adding line numbers, for
// example) in order for it to make sense.
return null;
}
$comment = $this->getComment();
if ($comment && strlen($comment->getContent())) {
return $comment->getContent();
}
return null;
}
public function getNoEffectDescription() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
return pht('You can not post an empty comment.');
case PhabricatorTransactions::TYPE_VIEW_POLICY:
return pht(
'This %s already has that view policy.',
$this->getApplicationObjectTypeName());
case PhabricatorTransactions::TYPE_EDIT_POLICY:
return pht(
'This %s already has that edit policy.',
$this->getApplicationObjectTypeName());
case PhabricatorTransactions::TYPE_JOIN_POLICY:
return pht(
'This %s already has that join policy.',
$this->getApplicationObjectTypeName());
case PhabricatorTransactions::TYPE_INTERACT_POLICY:
return pht(
'This %s already has that interact policy.',
$this->getApplicationObjectTypeName());
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
return pht(
'All users are already subscribed to this %s.',
$this->getApplicationObjectTypeName());
case PhabricatorTransactions::TYPE_SPACE:
return pht('This object is already in that space.');
case PhabricatorTransactions::TYPE_EDGE:
return pht('Edges already exist; transaction has no effect.');
case PhabricatorTransactions::TYPE_COLUMNS:
return pht(
'You have not moved this object to any columns it is not '.
'already in.');
case PhabricatorTransactions::TYPE_MFA:
return pht(
'You can not sign a transaction group that has no other '.
'effects.');
}
return pht(
'Transaction (of type "%s") has no effect.',
$this->getTransactionType());
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CREATE:
return pht(
'%s created this object.',
$this->renderHandleLink($author_phid));
case PhabricatorTransactions::TYPE_COMMENT:
return pht(
'%s added a comment.',
$this->renderHandleLink($author_phid));
case PhabricatorTransactions::TYPE_VIEW_POLICY:
if ($this->getIsCreateTransaction()) {
return pht(
'%s created this object with visibility "%s".',
$this->renderHandleLink($author_phid),
$this->renderPolicyName($new, 'new'));
} else {
return pht(
'%s changed the visibility from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->renderPolicyName($old, 'old'),
$this->renderPolicyName($new, 'new'));
}
case PhabricatorTransactions::TYPE_EDIT_POLICY:
if ($this->getIsCreateTransaction()) {
return pht(
'%s created this object with edit policy "%s".',
$this->renderHandleLink($author_phid),
$this->renderPolicyName($new, 'new'));
} else {
return pht(
'%s changed the edit policy from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->renderPolicyName($old, 'old'),
$this->renderPolicyName($new, 'new'));
}
case PhabricatorTransactions::TYPE_JOIN_POLICY:
if ($this->getIsCreateTransaction()) {
return pht(
'%s created this object with join policy "%s".',
$this->renderHandleLink($author_phid),
$this->renderPolicyName($new, 'new'));
} else {
return pht(
'%s changed the join policy from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->renderPolicyName($old, 'old'),
$this->renderPolicyName($new, 'new'));
}
case PhabricatorTransactions::TYPE_INTERACT_POLICY:
if ($this->getIsCreateTransaction()) {
return pht(
'%s created this object with interact policy "%s".',
$this->renderHandleLink($author_phid),
$this->renderPolicyName($new, 'new'));
} else {
return pht(
'%s changed the interact policy from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->renderPolicyName($old, 'old'),
$this->renderPolicyName($new, 'new'));
}
case PhabricatorTransactions::TYPE_SPACE:
if ($this->getIsCreateTransaction()) {
return pht(
'%s created this object in space %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($new));
} else {
return pht(
'%s shifted this object from the %s space to the %s space.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($old),
$this->renderHandleLink($new));
}
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
if ($add && $rem) {
return pht(
'%s edited subscriber(s), added %d: %s; removed %d: %s.',
$this->renderHandleLink($author_phid),
count($add),
$this->renderSubscriberList($add, 'add'),
count($rem),
$this->renderSubscriberList($rem, 'rem'));
} else if ($add) {
return pht(
'%s added %d subscriber(s): %s.',
$this->renderHandleLink($author_phid),
count($add),
$this->renderSubscriberList($add, 'add'));
} else if ($rem) {
return pht(
'%s removed %d subscriber(s): %s.',
$this->renderHandleLink($author_phid),
count($rem),
$this->renderSubscriberList($rem, 'rem'));
} else {
// This is used when rendering previews, before the user actually
// selects any CCs.
return pht(
'%s updated subscribers...',
$this->renderHandleLink($author_phid));
}
+ break;
+ case PhabricatorTransactions::TYPE_FILE:
+ $add = array_diff_key($new, $old);
+ $add = array_keys($add);
+
+ $rem = array_diff_key($old, $new);
+ $rem = array_keys($rem);
+
+ $mod = array();
+ foreach ($old + $new as $key => $ignored) {
+ if (!isset($old[$key])) {
+ continue;
+ }
+
+ if (!isset($new[$key])) {
+ continue;
+ }
+
+ if ($old[$key] === $new[$key]) {
+ continue;
+ }
+
+ $mod[] = $key;
+ }
+
+ // Specialize the specific case of only modifying files and upgrading
+ // references to attachments. This is accessible via the UI and can
+ // be shown more clearly than the generic default transaction shows
+ // it.
+
+ $mode_reference = PhabricatorFileAttachment::MODE_REFERENCE;
+ $mode_attach = PhabricatorFileAttachment::MODE_ATTACH;
+
+ $is_refattach = false;
+ if ($mod && !$add && !$rem) {
+ $all_refattach = true;
+ foreach ($mod as $phid) {
+ if (idx($old, $phid) !== $mode_reference) {
+ $all_refattach = false;
+ break;
+ }
+ if (idx($new, $phid) !== $mode_attach) {
+ $all_refattach = false;
+ break;
+ }
+ }
+ $is_refattach = $all_refattach;
+ }
+
+ if ($is_refattach) {
+ return pht(
+ '%s attached %s referenced file(s): %s.',
+ $this->renderHandleLink($author_phid),
+ phutil_count($mod),
+ $this->renderHandleList($mod));
+ } else if ($add && $rem && $mod) {
+ return pht(
+ '%s updated %s attached file(s), added %s: %s; removed %s: %s; '.
+ 'modified %s: %s.',
+ $this->renderHandleLink($author_phid),
+ new PhutilNumber(count($add) + count($rem)),
+ phutil_count($add),
+ $this->renderHandleList($add),
+ phutil_count($rem),
+ $this->renderHandleList($rem),
+ phutil_count($mod),
+ $this->renderHandleList($mod));
+ } else if ($add && $rem) {
+ return pht(
+ '%s updated %s attached file(s), added %s: %s; removed %s: %s.',
+ $this->renderHandleLink($author_phid),
+ new PhutilNumber(count($add) + count($rem)),
+ phutil_count($add),
+ $this->renderHandleList($add),
+ phutil_count($rem),
+ $this->renderHandleList($rem));
+ } else if ($add && $mod) {
+ return pht(
+ '%s updated %s attached file(s), added %s: %s; modified %s: %s.',
+ $this->renderHandleLink($author_phid),
+ new PhutilNumber(count($add) + count($mod)),
+ phutil_count($add),
+ $this->renderHandleList($add),
+ phutil_count($mod),
+ $this->renderHandleList($mod));
+ } else if ($rem && $mod) {
+ return pht(
+ '%s updated %s attached file(s), removed %s: %s; modified %s: %s.',
+ $this->renderHandleLink($author_phid),
+ new PhutilNumber(count($rem) + count($mod)),
+ phutil_count($rem),
+ $this->renderHandleList($rem),
+ phutil_count($mod),
+ $this->renderHandleList($mod));
+ } else if ($add) {
+ return pht(
+ '%s attached %s file(s): %s.',
+ $this->renderHandleLink($author_phid),
+ phutil_count($add),
+ $this->renderHandleList($add));
+ } else if ($rem) {
+ return pht(
+ '%s removed %s attached file(s): %s.',
+ $this->renderHandleLink($author_phid),
+ phutil_count($rem),
+ $this->renderHandleList($rem));
+ } else if ($mod) {
+ return pht(
+ '%s modified %s attached file(s): %s.',
+ $this->renderHandleLink($author_phid),
+ phutil_count($mod),
+ $this->renderHandleList($mod));
+ } else {
+ return pht(
+ '%s attached files...',
+ $this->renderHandleLink($author_phid));
+ }
+
break;
case PhabricatorTransactions::TYPE_EDGE:
$record = PhabricatorEdgeChangeRecord::newFromTransaction($this);
$add = $record->getAddedPHIDs();
$rem = $record->getRemovedPHIDs();
$type = $this->getMetadata('edge:type');
$type = head($type);
try {
$type_obj = PhabricatorEdgeType::getByConstant($type);
} catch (Exception $ex) {
// Recover somewhat gracefully from edge transactions which
// we don't have the classes for.
return pht(
'%s edited an edge.',
$this->renderHandleLink($author_phid));
}
if ($add && $rem) {
return $type_obj->getTransactionEditString(
$this->renderHandleLink($author_phid),
new PhutilNumber(count($add) + count($rem)),
phutil_count($add),
$this->renderHandleList($add),
phutil_count($rem),
$this->renderHandleList($rem));
} else if ($add) {
return $type_obj->getTransactionAddString(
$this->renderHandleLink($author_phid),
phutil_count($add),
$this->renderHandleList($add));
} else if ($rem) {
return $type_obj->getTransactionRemoveString(
$this->renderHandleLink($author_phid),
phutil_count($rem),
$this->renderHandleList($rem));
} else {
return $type_obj->getTransactionPreviewString(
$this->renderHandleLink($author_phid));
}
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->getApplicationTransactionTitle($this);
} else {
$developer_mode = 'phabricator.developer-mode';
$is_developer = PhabricatorEnv::getEnvConfig($developer_mode);
if ($is_developer) {
return pht(
'%s edited a custom field (with key "%s").',
$this->renderHandleLink($author_phid),
$this->getMetadata('customfield:key'));
} else {
return pht(
'%s edited a custom field.',
$this->renderHandleLink($author_phid));
}
}
case PhabricatorTransactions::TYPE_TOKEN:
if ($old && $new) {
return pht(
'%s updated a token.',
$this->renderHandleLink($author_phid));
} else if ($old) {
return pht(
'%s rescinded a token.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s awarded a token.',
$this->renderHandleLink($author_phid));
}
case PhabricatorTransactions::TYPE_INLINESTATE:
list($done, $undone) = $this->getInterestingInlineStateChangeCounts();
if ($done && $undone) {
return pht(
'%s marked %s inline comment(s) as done and %s inline comment(s) '.
'as not done.',
$this->renderHandleLink($author_phid),
new PhutilNumber($done),
new PhutilNumber($undone));
} else if ($done) {
return pht(
'%s marked %s inline comment(s) as done.',
$this->renderHandleLink($author_phid),
new PhutilNumber($done));
} else {
return pht(
'%s marked %s inline comment(s) as not done.',
$this->renderHandleLink($author_phid),
new PhutilNumber($undone));
}
break;
case PhabricatorTransactions::TYPE_COLUMNS:
$moves = $this->getInterestingMoves($new);
if (count($moves) == 1) {
$move = head($moves);
$from_columns = $move['fromColumnPHIDs'];
$to_column = $move['columnPHID'];
$board_phid = $move['boardPHID'];
if (count($from_columns) == 1) {
return pht(
'%s moved this task from %s to %s on the %s board.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink(head($from_columns)),
$this->renderHandleLink($to_column),
$this->renderHandleLink($board_phid));
} else {
return pht(
'%s moved this task to %s on the %s board.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($to_column),
$this->renderHandleLink($board_phid));
}
} else {
$fragments = array();
foreach ($moves as $move) {
$to_column = $move['columnPHID'];
$board_phid = $move['boardPHID'];
$fragments[] = pht(
'%s (%s)',
$this->renderHandleLink($board_phid),
$this->renderHandleLink($to_column));
}
return pht(
'%s moved this task on %s board(s): %s.',
$this->renderHandleLink($author_phid),
phutil_count($moves),
phutil_implode_html(', ', $fragments));
}
break;
case PhabricatorTransactions::TYPE_MFA:
return pht(
'%s signed these changes with MFA.',
$this->renderHandleLink($author_phid));
default:
// In developer mode, provide a better hint here about which string
// we're missing.
$developer_mode = 'phabricator.developer-mode';
$is_developer = PhabricatorEnv::getEnvConfig($developer_mode);
if ($is_developer) {
return pht(
'%s edited this object (transaction type "%s").',
$this->renderHandleLink($author_phid),
$this->getTransactionType());
} else {
return pht(
'%s edited this %s.',
$this->renderHandleLink($author_phid),
$this->getApplicationObjectTypeName());
}
}
}
public function getTitleForFeed() {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CREATE:
return pht(
'%s created %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_COMMENT:
return pht(
'%s added a comment to %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_VIEW_POLICY:
return pht(
'%s changed the visibility for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_EDIT_POLICY:
return pht(
'%s changed the edit policy for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_JOIN_POLICY:
return pht(
'%s changed the join policy for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_INTERACT_POLICY:
return pht(
'%s changed the interact policy for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
return pht(
'%s updated subscribers of %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_SPACE:
if ($this->getIsCreateTransaction()) {
return pht(
'%s created %s in the %s space.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$this->renderHandleLink($new));
} else {
return pht(
'%s shifted %s from the %s space to the %s space.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$this->renderHandleLink($old),
$this->renderHandleLink($new));
}
case PhabricatorTransactions::TYPE_EDGE:
$record = PhabricatorEdgeChangeRecord::newFromTransaction($this);
$add = $record->getAddedPHIDs();
$rem = $record->getRemovedPHIDs();
$type = $this->getMetadata('edge:type');
$type = head($type);
$type_obj = PhabricatorEdgeType::getByConstant($type);
if ($add && $rem) {
return $type_obj->getFeedEditString(
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
new PhutilNumber(count($add) + count($rem)),
phutil_count($add),
$this->renderHandleList($add),
phutil_count($rem),
$this->renderHandleList($rem));
} else if ($add) {
return $type_obj->getFeedAddString(
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
phutil_count($add),
$this->renderHandleList($add));
} else if ($rem) {
return $type_obj->getFeedRemoveString(
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
phutil_count($rem),
$this->renderHandleList($rem));
} else {
return pht(
'%s edited edge metadata for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->getApplicationTransactionTitleForFeed($this);
} else {
return pht(
'%s edited a custom field on %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
case PhabricatorTransactions::TYPE_COLUMNS:
$moves = $this->getInterestingMoves($new);
if (count($moves) == 1) {
$move = head($moves);
$from_columns = $move['fromColumnPHIDs'];
$to_column = $move['columnPHID'];
$board_phid = $move['boardPHID'];
if (count($from_columns) == 1) {
return pht(
'%s moved %s from %s to %s on the %s board.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$this->renderHandleLink(head($from_columns)),
$this->renderHandleLink($to_column),
$this->renderHandleLink($board_phid));
} else {
return pht(
'%s moved %s to %s on the %s board.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
$this->renderHandleLink($to_column),
$this->renderHandleLink($board_phid));
}
} else {
$fragments = array();
foreach ($moves as $move) {
$fragments[] = pht(
'%s (%s)',
$this->renderHandleLink($board_phid),
$this->renderHandleLink($to_column));
}
return pht(
'%s moved %s on %s board(s): %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
phutil_count($moves),
phutil_implode_html(', ', $fragments));
}
break;
case PhabricatorTransactions::TYPE_MFA:
return null;
}
return $this->getTitle();
}
public function getMarkupFieldsForFeed(PhabricatorFeedStory $story) {
$fields = array();
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$text = $this->getComment()->getContent();
if (strlen($text)) {
$fields[] = 'comment/'.$this->getID();
}
break;
}
return $fields;
}
public function getMarkupTextForFeed(PhabricatorFeedStory $story, $field) {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$text = $this->getComment()->getContent();
return PhabricatorMarkupEngine::summarize($text);
}
return null;
}
public function getBodyForFeed(PhabricatorFeedStory $story) {
$remarkup = $this->getRemarkupBodyForFeed($story);
if ($remarkup !== null) {
$remarkup = PhabricatorMarkupEngine::summarize($remarkup);
return new PHUIRemarkupView($this->viewer, $remarkup);
}
$old = $this->getOldValue();
$new = $this->getNewValue();
$body = null;
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$text = $this->getComment()->getContent();
if (strlen($text)) {
$body = $story->getMarkupFieldOutput('comment/'.$this->getID());
}
break;
}
return $body;
}
public function getRemarkupBodyForFeed(PhabricatorFeedStory $story) {
return null;
}
public function getActionStrength() {
if ($this->isInlineCommentTransaction()) {
return 25;
}
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
return 50;
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
if ($this->isSelfSubscription()) {
// Make this weaker than TYPE_COMMENT.
return 25;
}
// In other cases, subscriptions are more interesting than comments
// (which are shown anyway) but less interesting than any other type of
// transaction.
return 75;
case PhabricatorTransactions::TYPE_MFA:
// We want MFA signatures to render at the top of transaction groups,
// on top of the things they signed.
return 1000;
}
return 100;
}
public function isCommentTransaction() {
if ($this->hasComment()) {
return true;
}
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
return true;
}
return false;
}
public function isInlineCommentTransaction() {
return false;
}
public function getActionName() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
return pht('Commented On');
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
case PhabricatorTransactions::TYPE_INTERACT_POLICY:
return pht('Changed Policy');
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
return pht('Changed Subscribers');
default:
return pht('Updated');
}
}
public function getMailTags() {
return array();
}
public function hasChangeDetails() {
switch ($this->getTransactionType()) {
+ case PhabricatorTransactions::TYPE_FILE:
+ return true;
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->getApplicationTransactionHasChangeDetails($this);
}
break;
}
return false;
}
public function hasChangeDetailsForMail() {
return $this->hasChangeDetails();
}
public function renderChangeDetailsForMail(PhabricatorUser $viewer) {
+ switch ($this->getTransactionType()) {
+ case PhabricatorTransactions::TYPE_FILE:
+ return false;
+ }
+
$view = $this->renderChangeDetails($viewer);
if ($view instanceof PhabricatorApplicationTransactionTextDiffDetailView) {
return $view->renderForMail();
}
return null;
}
public function renderChangeDetails(PhabricatorUser $viewer) {
switch ($this->getTransactionType()) {
+ case PhabricatorTransactions::TYPE_FILE:
+ return $this->newFileTransactionChangeDetails($viewer);
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->getApplicationTransactionChangeDetails($this, $viewer);
}
break;
}
return $this->renderTextCorpusChangeDetails(
$viewer,
$this->getOldValue(),
$this->getNewValue());
}
public function renderTextCorpusChangeDetails(
PhabricatorUser $viewer,
$old,
$new) {
return id(new PhabricatorApplicationTransactionTextDiffDetailView())
->setUser($viewer)
->setOldText($old)
->setNewText($new);
}
public function attachTransactionGroup(array $group) {
assert_instances_of($group, __CLASS__);
$this->transactionGroup = $group;
return $this;
}
public function getTransactionGroup() {
return $this->transactionGroup;
}
/**
* Should this transaction be visually grouped with an existing transaction
* group?
*
* @param list<PhabricatorApplicationTransaction> List of transactions.
* @return bool True to display in a group with the other transactions.
*/
public function shouldDisplayGroupWith(array $group) {
$this_source = null;
if ($this->getContentSource()) {
$this_source = $this->getContentSource()->getSource();
}
$type_mfa = PhabricatorTransactions::TYPE_MFA;
foreach ($group as $xaction) {
// Don't group transactions by different authors.
if ($xaction->getAuthorPHID() != $this->getAuthorPHID()) {
return false;
}
// Don't group transactions for different objects.
if ($xaction->getObjectPHID() != $this->getObjectPHID()) {
return false;
}
// Don't group anything into a group which already has a comment.
if ($xaction->isCommentTransaction()) {
return false;
}
// Don't group transactions from different content sources.
$other_source = null;
if ($xaction->getContentSource()) {
$other_source = $xaction->getContentSource()->getSource();
}
if ($other_source != $this_source) {
return false;
}
// Don't group transactions which happened more than 2 minutes apart.
$apart = abs($xaction->getDateCreated() - $this->getDateCreated());
if ($apart > (60 * 2)) {
return false;
}
// Don't group silent and nonsilent transactions together.
$is_silent = $this->getIsSilentTransaction();
if ($is_silent != $xaction->getIsSilentTransaction()) {
return false;
}
// Don't group MFA and non-MFA transactions together.
$is_mfa = $this->getIsMFATransaction();
if ($is_mfa != $xaction->getIsMFATransaction()) {
return false;
}
// Don't group two "Sign with MFA" transactions together.
if ($this->getTransactionType() === $type_mfa) {
if ($xaction->getTransactionType() === $type_mfa) {
return false;
}
}
// Don't group lock override and non-override transactions together.
$is_override = $this->getIsLockOverrideTransaction();
if ($is_override != $xaction->getIsLockOverrideTransaction()) {
return false;
}
}
return true;
}
public function renderExtraInformationLink() {
$herald_xscript_id = $this->getMetadataValue('herald:transcriptID');
if ($herald_xscript_id) {
return phutil_tag(
'a',
array(
'href' => '/herald/transcript/'.$herald_xscript_id.'/',
),
pht('View Herald Transcript'));
}
return null;
}
public function renderAsTextForDoorkeeper(
DoorkeeperFeedStoryPublisher $publisher,
PhabricatorFeedStory $story,
array $xactions) {
$text = array();
$body = array();
foreach ($xactions as $xaction) {
$xaction_body = $xaction->getBodyForMail();
if ($xaction_body !== null) {
$body[] = $xaction_body;
}
if ($xaction->shouldHideForMail($xactions)) {
continue;
}
$old_target = $xaction->getRenderingTarget();
$new_target = self::TARGET_TEXT;
$xaction->setRenderingTarget($new_target);
if ($publisher->getRenderWithImpliedContext()) {
$text[] = $xaction->getTitle();
} else {
$text[] = $xaction->getTitleForFeed();
}
$xaction->setRenderingTarget($old_target);
}
$text = implode("\n", $text);
$body = implode("\n\n", $body);
return rtrim($text."\n\n".$body);
}
/**
* Test if this transaction is just a user subscribing or unsubscribing
* themselves.
*/
private function isSelfSubscription() {
$type = $this->getTransactionType();
if ($type != PhabricatorTransactions::TYPE_SUBSCRIBERS) {
return false;
}
$old = $this->getOldValue();
$new = $this->getNewValue();
$add = array_diff($old, $new);
$rem = array_diff($new, $old);
if ((count($add) + count($rem)) != 1) {
// More than one user affected.
return false;
}
$affected_phid = head(array_merge($add, $rem));
if ($affected_phid != $this->getAuthorPHID()) {
// Affected user is someone else.
return false;
}
return true;
}
private function isApplicationAuthor() {
$author_phid = $this->getAuthorPHID();
$author_type = phid_get_type($author_phid);
$application_type = PhabricatorApplicationApplicationPHIDType::TYPECONST;
return ($author_type == $application_type);
}
private function getInterestingMoves(array $moves) {
// Remove moves which only shift the position of a task within a column.
foreach ($moves as $key => $move) {
$from_phids = array_fuse($move['fromColumnPHIDs']);
if (isset($from_phids[$move['columnPHID']])) {
unset($moves[$key]);
}
}
return $moves;
}
private function getInterestingInlineStateChangeCounts() {
// See PHI995. Newer inline state transactions have additional details
// which we use to tailor the rendering behavior. These details are not
// present on older transactions.
$details = $this->getMetadataValue('inline.details', array());
$new = $this->getNewValue();
$done = 0;
$undone = 0;
foreach ($new as $phid => $state) {
$is_done = ($state == PhabricatorInlineComment::STATE_DONE);
// See PHI995. If you're marking your own inline comments as "Done",
// don't count them when rendering a timeline story. In the case where
// you're only affecting your own comments, this will hide the
// "alice marked X comments as done" story entirely.
// Usually, this happens when you pre-mark inlines as "done" and submit
// them yourself. We'll still generate an "alice added inline comments"
// story (in most cases/contexts), but the state change story is largely
// just clutter and slightly confusing/misleading.
$inline_details = idx($details, $phid, array());
$inline_author_phid = idx($inline_details, 'authorPHID');
if ($inline_author_phid) {
if ($inline_author_phid == $this->getAuthorPHID()) {
if ($is_done) {
continue;
}
}
}
if ($is_done) {
$done++;
} else {
$undone++;
}
}
return array($done, $undone);
}
public function newGlobalSortVector() {
return id(new PhutilSortVector())
->addInt(-$this->getDateCreated())
->addString($this->getPHID());
}
public function newActionStrengthSortVector() {
return id(new PhutilSortVector())
->addInt(-$this->getActionStrength());
}
+ private function newFileTransactionChangeDetails(PhabricatorUser $viewer) {
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ $phids = array_keys($old + $new);
+ $handles = $viewer->loadHandles($phids);
+
+ $names = array(
+ PhabricatorFileAttachment::MODE_REFERENCE => pht('Referenced'),
+ PhabricatorFileAttachment::MODE_ATTACH => pht('Attached'),
+ );
+
+ $rows = array();
+ foreach ($old + $new as $phid => $ignored) {
+ $handle = $handles[$phid];
+
+ $old_mode = idx($old, $phid);
+ $new_mode = idx($new, $phid);
+
+ if ($old_mode === null) {
+ $old_name = pht('None');
+ } else if (isset($names[$old_mode])) {
+ $old_name = $names[$old_mode];
+ } else {
+ $old_name = pht('Unknown ("%s")', $old_mode);
+ }
+
+ if ($new_mode === null) {
+ $new_name = pht('Detached');
+ } else if (isset($names[$new_mode])) {
+ $new_name = $names[$new_mode];
+ } else {
+ $new_name = pht('Unknown ("%s")', $new_mode);
+ }
+
+ $rows[] = array(
+ $handle->renderLink(),
+ $old_name,
+ $new_name,
+ );
+ }
+
+ $table = id(new AphrontTableView($rows))
+ ->setHeaders(
+ array(
+ pht('File'),
+ pht('Old Mode'),
+ pht('New Mode'),
+ ))
+ ->setColumnClasses(
+ array(
+ 'pri',
+ ));
+
+ return id(new PHUIBoxView())
+ ->addMargin(PHUI::MARGIN_SMALL)
+ ->appendChild($table);
+ }
+
+
/* -( PhabricatorPolicyInterface Implementation )-------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return ($viewer->getPHID() == $this->getAuthorPHID());
}
public function describeAutomaticCapability($capability) {
return pht(
'Transactions are visible to users that can see the object which was '.
'acted upon. Some transactions - in particular, comments - are '.
'editable by the transaction author.');
}
public function getModularType() {
return null;
}
public function setForceNotifyPHIDs(array $phids) {
$this->setMetadataValue('notify.force', $phids);
return $this;
}
public function getForceNotifyPHIDs() {
return $this->getMetadataValue('notify.force', array());
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$comment_template = $this->getApplicationTransactionCommentObject();
if ($comment_template) {
$comments = $comment_template->loadAllWhere(
'transactionPHID = %s',
$this->getPHID());
foreach ($comments as $comment) {
$engine->destroyObject($comment);
}
}
$this->delete();
$this->saveTransaction();
}
-
}
diff --git a/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php b/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php
index d21e2105fb..fafb4d35af 100644
--- a/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php
+++ b/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php
@@ -1,326 +1,326 @@
<?php
/**
* Renders the "HTTP Parameters" help page for edit engines.
*
* This page has a ton of text and specialized rendering on it, this class
* just pulls it out of the main @{class:PhabricatorEditEngine}.
*/
final class PhabricatorApplicationEditHTTPParameterHelpView
extends AphrontView {
private $object;
private $fields;
public function setObject($object) {
$this->object = $object;
return $this;
}
public function getObject() {
return $this->object;
}
public function setFields(array $fields) {
$this->fields = $fields;
return $this;
}
public function getFields() {
return $this->fields;
}
public function render() {
$object = $this->getObject();
$fields = $this->getFields();
$uri = 'https://your.install.com/application/edit/';
// Remove fields which do not expose an HTTP parameter type.
$types = array();
foreach ($fields as $key => $field) {
if (!$field->shouldGenerateTransactionsFromSubmit()) {
unset($fields[$key]);
continue;
}
$type = $field->getHTTPParameterType();
if ($type === null) {
unset($fields[$key]);
continue;
}
$types[$type->getTypeName()] = $type;
}
$intro = pht(<<<EOTEXT
When creating objects in the web interface, you can use HTTP parameters to
prefill fields in the form. This allows you to quickly create a link to a
form with some of the fields already filled in with default values.
To prefill a form, start by finding the URI for the form you want to prefill.
Do this by navigating to the relevant application, clicking the "Create" button
for the type of object you want to create, and then copying the URI out of your
browser's address bar. It will usually look something like this:
```
%s
```
-However, `your.install.com` will be the domain where your copy of Phabricator
+However, `your.install.com` will be the domain where your copy of this software
is installed, and `application/` will be the URI for an application. Some
applications have multiple forms for creating objects or URIs that look a little
different than this example, so the URI may not look exactly like this.
To prefill the form, add properly encoded HTTP parameters to the URI. You
should end up with something like this:
```
%s?title=Platyplus&body=Ornithopter
```
If the form has `title` and `body` fields of the correct types, visiting this
link will prefill those fields with the values "Platypus" and "Ornithopter"
respectively.
The rest of this document shows which parameters you can add to this form and
how to format them.
Supported Fields
----------------
This form supports these fields:
EOTEXT
,
$uri,
$uri);
$rows = array();
foreach ($fields as $field) {
$rows[] = array(
$field->getLabel(),
head($field->getAllReadValueFromRequestKeys()),
$field->getHTTPParameterType()->getTypeName(),
$field->getDescription(),
);
}
$main_table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Label'),
pht('Key'),
pht('Type'),
pht('Description'),
))
->setColumnClasses(
array(
'pri',
null,
null,
'wide',
));
$aliases_text = pht(<<<EOTEXT
Aliases
-------
Aliases are alternate recognized keys for a field. For example, a field with
a complex key like `examplePHIDs` might be have a simple version of that key
as an alias, like `example`.
Aliases work just like the primary key when prefilling forms. They make it
easier to remember and use HTTP parameters by providing more natural ways to do
some prefilling.
For example, if a field has `examplePHIDs` as a key but has aliases `example`
and `examples`, these three URIs will all do the same thing:
```
%s?examplePHIDs=...
%s?examples=...
%s?example=...
```
If a URI specifies multiple default values for a field, the value using the
primary key has precedence. Generally, you can not mix different aliases in
a single URI.
EOTEXT
,
$uri,
$uri,
$uri);
$rows = array();
foreach ($fields as $field) {
$aliases = array_slice($field->getAllReadValueFromRequestKeys(), 1);
if (!$aliases) {
continue;
}
$rows[] = array(
$field->getLabel(),
$field->getKey(),
implode(', ', $aliases),
);
}
$alias_table = id(new AphrontTableView($rows))
->setNoDataString(pht('This object has no fields with aliases.'))
->setHeaders(
array(
pht('Label'),
pht('Key'),
pht('Aliases'),
))
->setColumnClasses(
array(
'pri',
null,
'wide',
));
$template_text = pht(<<<EOTEXT
Template Objects
----------------
Instead of specifying each field value individually, you can specify another
object to use as a template. Some of the initial fields will be copied from the
template object.
Specify a template object with the `template` parameter. You can use an ID,
PHID, or monogram (for objects which have monograms). For example, you might
use URIs like these:
```
%s?template=123
%s?template=PHID-WXYZ-abcdef...
%s?template=T123
```
You can combine the `template` parameter with HTTP parameters: the template
object will be copied first, then any HTTP parameters will be read.
When using `template`, these fields will be copied:
EOTEXT
,
$uri,
$uri,
$uri);
$yes = id(new PHUIIconView())->setIcon('fa-check-circle green');
$no = id(new PHUIIconView())->setIcon('fa-times grey');
$rows = array();
foreach ($fields as $field) {
$rows[] = array(
$field->getLabel(),
$field->getIsCopyable() ? $yes : $no,
);
}
$template_table = id(new AphrontTableView($rows))
->setNoDataString(
pht('None of the fields on this object support templating.'))
->setHeaders(
array(
pht('Field'),
pht('Will Copy'),
))
->setColumnClasses(
array(
'pri',
'wide',
));
$select_text = pht(<<<EOTEXT
Select Fields
-------------
Some fields support selection from a specific set of values. When prefilling
these fields, use the value in the **Value** column to select the appropriate
setting.
EOTEXT
);
$rows = array();
foreach ($fields as $field) {
if (!($field instanceof PhabricatorSelectEditField)) {
continue;
}
$options = $field->getOptions();
$label = $field->getLabel();
foreach ($options as $option_key => $option_value) {
if (strlen($option_key)) {
$option_display = $option_key;
} else {
$option_display = phutil_tag('em', array(), pht('<empty>'));
}
$rows[] = array(
$label,
$option_display,
$option_value,
);
$label = null;
}
}
$select_table = id(new AphrontTableView($rows))
->setNoDataString(pht('This object has no select fields.'))
->setHeaders(
array(
pht('Field'),
pht('Value'),
pht('Label'),
))
->setColumnClasses(
array(
'pri',
null,
'wide',
));
$types_text = pht(<<<EOTEXT
Field Types
-----------
Fields in this form have the types described in the table below. This table
shows how to format values for each field type.
EOTEXT
);
$types_table = id(new PhabricatorHTTPParameterTypeTableView())
->setHTTPParameterTypes($types);
return array(
$this->renderInstructions($intro),
$main_table,
$this->renderInstructions($aliases_text),
$alias_table,
$this->renderInstructions($template_text),
$template_table,
$this->renderInstructions($select_text),
$select_table,
$this->renderInstructions($types_text),
$types_table,
);
}
protected function renderInstructions($corpus) {
$viewer = $this->getUser();
$view = new PHUIRemarkupView($viewer, $corpus);
$view->setRemarkupOptions(
array(
PHUIRemarkupView::OPTION_PRESERVE_LINEBREAKS => false,
));
return $view;
}
}
diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
index 8ec7ef0d8f..caf1ce5183 100644
--- a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
+++ b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
@@ -1,622 +1,635 @@
<?php
final class PhabricatorApplicationTransactionCommentView
extends AphrontView {
private $submitButtonName;
private $action;
private $previewPanelID;
private $previewTimelineID;
private $previewToggleID;
private $formID;
private $statusID;
private $commentID;
private $draft;
private $requestURI;
private $showPreview = true;
private $objectPHID;
private $headerText;
private $noPermission;
private $fullWidth;
private $infoView;
private $editEngineLock;
private $noBorder;
private $requiresMFA;
private $currentVersion;
private $versionedDraft;
private $commentActions;
private $commentActionGroups = array();
private $transactionTimeline;
public function setObjectPHID($object_phid) {
$this->objectPHID = $object_phid;
return $this;
}
public function getObjectPHID() {
return $this->objectPHID;
}
public function setShowPreview($show_preview) {
$this->showPreview = $show_preview;
return $this;
}
public function getShowPreview() {
return $this->showPreview;
}
public function setRequestURI(PhutilURI $request_uri) {
$this->requestURI = $request_uri;
return $this;
}
public function getRequestURI() {
return $this->requestURI;
}
public function setCurrentVersion($current_version) {
$this->currentVersion = $current_version;
return $this;
}
public function getCurrentVersion() {
return $this->currentVersion;
}
public function setVersionedDraft(
PhabricatorVersionedDraft $versioned_draft) {
$this->versionedDraft = $versioned_draft;
return $this;
}
public function getVersionedDraft() {
return $this->versionedDraft;
}
public function setDraft(PhabricatorDraft $draft) {
$this->draft = $draft;
return $this;
}
public function getDraft() {
return $this->draft;
}
public function setSubmitButtonName($submit_button_name) {
$this->submitButtonName = $submit_button_name;
return $this;
}
public function getSubmitButtonName() {
return $this->submitButtonName;
}
public function setAction($action) {
$this->action = $action;
return $this;
}
public function getAction() {
return $this->action;
}
public function setHeaderText($text) {
$this->headerText = $text;
return $this;
}
public function setFullWidth($fw) {
$this->fullWidth = $fw;
return $this;
}
public function setInfoView(PHUIInfoView $info_view) {
$this->infoView = $info_view;
return $this;
}
public function getInfoView() {
return $this->infoView;
}
public function setCommentActions(array $comment_actions) {
assert_instances_of($comment_actions, 'PhabricatorEditEngineCommentAction');
$this->commentActions = $comment_actions;
return $this;
}
public function getCommentActions() {
return $this->commentActions;
}
public function setCommentActionGroups(array $groups) {
assert_instances_of($groups, 'PhabricatorEditEngineCommentActionGroup');
$this->commentActionGroups = $groups;
return $this;
}
public function getCommentActionGroups() {
return $this->commentActionGroups;
}
public function setNoPermission($no_permission) {
$this->noPermission = $no_permission;
return $this;
}
public function getNoPermission() {
return $this->noPermission;
}
public function setEditEngineLock(PhabricatorEditEngineLock $lock) {
$this->editEngineLock = $lock;
return $this;
}
public function getEditEngineLock() {
return $this->editEngineLock;
}
public function setRequiresMFA($requires_mfa) {
$this->requiresMFA = $requires_mfa;
return $this;
}
public function getRequiresMFA() {
return $this->requiresMFA;
}
public function setTransactionTimeline(
PhabricatorApplicationTransactionView $timeline) {
$timeline->setQuoteTargetID($this->getCommentID());
if ($this->getNoPermission() || $this->getEditEngineLock()) {
$timeline->setShouldTerminate(true);
}
$this->transactionTimeline = $timeline;
return $this;
}
public function render() {
if ($this->getNoPermission()) {
return null;
}
$lock = $this->getEditEngineLock();
if ($lock) {
return id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors(
array(
$lock->getLockedObjectDisplayText(),
));
}
$viewer = $this->getViewer();
if (!$viewer->isLoggedIn()) {
$uri = id(new PhutilURI('/login/'))
->replaceQueryParam('next', (string)$this->getRequestURI());
return id(new PHUIObjectBoxView())
->setFlush(true)
->appendChild(
javelin_tag(
'a',
array(
'class' => 'login-to-comment button',
'href' => $uri,
),
pht('Log In to Comment')));
}
if ($this->getRequiresMFA()) {
if (!$viewer->getIsEnrolledInMultiFactor()) {
$viewer->updateMultiFactorEnrollment();
if (!$viewer->getIsEnrolledInMultiFactor()) {
$messages = array();
$messages[] = pht(
'You must provide multi-factor credentials to comment or make '.
'changes, but you do not have multi-factor authentication '.
'configured on your account.');
$messages[] = pht(
'To continue, configure multi-factor authentication in Settings.');
return id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_MFA)
->setErrors($messages);
}
}
}
$data = array();
$comment = $this->renderCommentPanel();
if ($this->getShowPreview()) {
$preview = $this->renderPreviewPanel();
} else {
$preview = null;
}
if (!$this->getCommentActions()) {
Javelin::initBehavior(
'phabricator-transaction-comment-form',
array(
'formID' => $this->getFormID(),
'timelineID' => $this->getPreviewTimelineID(),
'panelID' => $this->getPreviewPanelID(),
'showPreview' => $this->getShowPreview(),
'actionURI' => $this->getAction(),
));
}
require_celerity_resource('phui-comment-form-css');
$image_uri = $viewer->getProfileImageURI();
$image = javelin_tag(
'div',
array(
'style' => 'background-image: url('.$image_uri.')',
'class' => 'phui-comment-image',
'aural' => false,
));
$wedge = phutil_tag(
'div',
array(
'class' => 'phui-timeline-wedge',
),
'');
$badge_view = $this->renderBadgeView();
$anchor = id(new PhabricatorAnchorView())
->setAnchorName('reply');
$comment_box = id(new PHUIObjectBoxView())
->setFlush(true)
->addClass('phui-comment-form-view')
->addSigil('phui-comment-form')
->appendChild($anchor)
->appendChild(
phutil_tag(
'h3',
array(
'class' => 'aural-only',
),
pht('Add Comment')))
->appendChild($image)
->appendChild($badge_view)
->appendChild($wedge)
->appendChild($comment);
return array($comment_box, $preview);
}
private function renderCommentPanel() {
+ $viewer = $this->getViewer();
+
+ $remarkup_control = id(new PhabricatorRemarkupControl())
+ ->setViewer($viewer)
+ ->setID($this->getCommentID())
+ ->addClass('phui-comment-fullwidth-control')
+ ->addClass('phui-comment-textarea-control')
+ ->setCanPin(true)
+ ->setName('comment');
+
$draft_comment = '';
+ $draft_metadata = array();
$draft_key = null;
- if ($this->getDraft()) {
- $draft_comment = $this->getDraft()->getDraft();
- $draft_key = $this->getDraft()->getDraftKey();
+
+ $legacy_draft = $this->getDraft();
+ if ($legacy_draft) {
+ $draft_comment = $legacy_draft->getDraft();
+ $draft_key = $legacy_draft->getDraftKey();
}
$versioned_draft = $this->getVersionedDraft();
if ($versioned_draft) {
- $draft_comment = $versioned_draft->getProperty('comment', '');
+ $draft_comment = $versioned_draft->getProperty(
+ 'comment',
+ $draft_comment);
+ $draft_metadata = $versioned_draft->getProperty(
+ 'metadata',
+ $draft_metadata);
}
+ $remarkup_control->setValue($draft_comment);
+ $remarkup_control->setRemarkupMetadata($draft_metadata);
+
if (!$this->getObjectPHID()) {
throw new PhutilInvalidStateException('setObjectPHID', 'render');
}
$version_key = PhabricatorVersionedDraft::KEY_VERSION;
$version_value = $this->getCurrentVersion();
$form = id(new AphrontFormView())
- ->setUser($this->getUser())
+ ->setUser($viewer)
->addSigil('transaction-append')
->setWorkflow(true)
->setFullWidth($this->fullWidth)
->setMetadata(
array(
'objectPHID' => $this->getObjectPHID(),
))
->setAction($this->getAction())
->setID($this->getFormID())
->addHiddenInput('__draft__', $draft_key)
->addHiddenInput($version_key, $version_value);
$comment_actions = $this->getCommentActions();
if ($comment_actions) {
$action_map = array();
$type_map = array();
$comment_actions = mpull($comment_actions, null, 'getKey');
$draft_actions = array();
$draft_keys = array();
if ($versioned_draft) {
$draft_actions = $versioned_draft->getProperty('actions', array());
if (!is_array($draft_actions)) {
$draft_actions = array();
}
foreach ($draft_actions as $action) {
$type = idx($action, 'type');
$comment_action = idx($comment_actions, $type);
if (!$comment_action) {
continue;
}
$value = idx($action, 'value');
$comment_action->setValue($value);
$draft_keys[] = $type;
}
}
foreach ($comment_actions as $key => $comment_action) {
$key = $comment_action->getKey();
$label = $comment_action->getLabel();
$action_map[$key] = array(
'key' => $key,
'label' => $label,
'type' => $comment_action->getPHUIXControlType(),
'spec' => $comment_action->getPHUIXControlSpecification(),
'initialValue' => $comment_action->getInitialValue(),
'groupKey' => $comment_action->getGroupKey(),
'conflictKey' => $comment_action->getConflictKey(),
'auralLabel' => pht('Remove Action: %s', $label),
'buttonText' => $comment_action->getSubmitButtonText(),
);
$type_map[$key] = $comment_action;
}
$options = $this->newCommentActionOptions($action_map);
$action_id = celerity_generate_unique_node_id();
$input_id = celerity_generate_unique_node_id();
$place_id = celerity_generate_unique_node_id();
$form->appendChild(
phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => 'editengine.actions',
'id' => $input_id,
)));
$invisi_bar = phutil_tag(
'div',
array(
'id' => $place_id,
'class' => 'phui-comment-control-stack',
));
$action_select = id(new AphrontFormSelectControl())
->addClass('phui-comment-fullwidth-control')
->addClass('phui-comment-action-control')
->setID($action_id)
->setOptions($options);
$action_bar = phutil_tag(
'div',
array(
'class' => 'phui-comment-action-bar grouped',
),
array(
$action_select,
));
$form->appendChild($action_bar);
$info_view = $this->getInfoView();
if ($info_view) {
$form->appendChild($info_view);
}
if ($this->getRequiresMFA()) {
$message = pht(
'You will be required to provide multi-factor credentials to '.
'comment or make changes.');
$form->appendChild(
id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_MFA)
->setErrors(array($message)));
}
$form->appendChild($invisi_bar);
$form->addClass('phui-comment-has-actions');
$timeline = $this->transactionTimeline;
$view_data = array();
if ($timeline) {
$view_data = $timeline->getViewData();
}
Javelin::initBehavior(
'comment-actions',
array(
'actionID' => $action_id,
'inputID' => $input_id,
'formID' => $this->getFormID(),
'placeID' => $place_id,
'panelID' => $this->getPreviewPanelID(),
'timelineID' => $this->getPreviewTimelineID(),
'actions' => $action_map,
'showPreview' => $this->getShowPreview(),
'actionURI' => $this->getAction(),
'drafts' => $draft_keys,
'defaultButtonText' => $this->getSubmitButtonName(),
'viewData' => $view_data,
));
}
$submit_button = id(new AphrontFormSubmitControl())
->addClass('phui-comment-fullwidth-control')
->addClass('phui-comment-submit-control')
->setValue($this->getSubmitButtonName());
$form
- ->appendChild(
- id(new PhabricatorRemarkupControl())
- ->setID($this->getCommentID())
- ->addClass('phui-comment-fullwidth-control')
- ->addClass('phui-comment-textarea-control')
- ->setCanPin(true)
- ->setName('comment')
- ->setUser($this->getUser())
- ->setValue($draft_comment))
+ ->appendChild($remarkup_control)
->appendChild(
id(new AphrontFormSubmitControl())
->addClass('phui-comment-fullwidth-control')
->addClass('phui-comment-submit-control')
->addSigil('submit-transactions')
->setValue($this->getSubmitButtonName()));
return $form;
}
private function renderPreviewPanel() {
$preview = id(new PHUITimelineView())
->setID($this->getPreviewTimelineID());
return phutil_tag(
'div',
array(
'id' => $this->getPreviewPanelID(),
'style' => 'display: none',
'class' => 'phui-comment-preview-view',
),
$preview);
}
private function getPreviewPanelID() {
if (!$this->previewPanelID) {
$this->previewPanelID = celerity_generate_unique_node_id();
}
return $this->previewPanelID;
}
private function getPreviewTimelineID() {
if (!$this->previewTimelineID) {
$this->previewTimelineID = celerity_generate_unique_node_id();
}
return $this->previewTimelineID;
}
public function setFormID($id) {
$this->formID = $id;
return $this;
}
private function getFormID() {
if (!$this->formID) {
$this->formID = celerity_generate_unique_node_id();
}
return $this->formID;
}
private function getStatusID() {
if (!$this->statusID) {
$this->statusID = celerity_generate_unique_node_id();
}
return $this->statusID;
}
private function getCommentID() {
if (!$this->commentID) {
$this->commentID = celerity_generate_unique_node_id();
}
return $this->commentID;
}
private function newCommentActionOptions(array $action_map) {
$options = array();
$options['+'] = pht('Add Action...');
// Merge options into groups.
$groups = array();
foreach ($action_map as $key => $item) {
$group_key = $item['groupKey'];
if (!isset($groups[$group_key])) {
$groups[$group_key] = array();
}
$groups[$group_key][$key] = $item;
}
$group_specs = $this->getCommentActionGroups();
$group_labels = mpull($group_specs, 'getLabel', 'getKey');
// Reorder groups to put them in the same order as the recognized
// group definitions.
$groups = array_select_keys($groups, array_keys($group_labels)) + $groups;
// Move options with no group to the end.
$default_group = idx($groups, '');
if ($default_group) {
unset($groups['']);
$groups[''] = $default_group;
}
foreach ($groups as $group_key => $group_items) {
if (strlen($group_key)) {
$group_label = idx($group_labels, $group_key, $group_key);
$options[$group_label] = ipull($group_items, 'label');
} else {
foreach ($group_items as $key => $item) {
$options[$key] = $item['label'];
}
}
}
return $options;
}
private function renderBadgeView() {
$user = $this->getUser();
$can_use_badges = PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorBadgesApplication',
$user);
if (!$can_use_badges) {
return null;
}
// Pull Badges from UserCache
$badges = $user->getRecentBadgeAwards();
$badge_view = null;
if ($badges) {
$badge_list = array();
foreach ($badges as $badge) {
$badge_view = id(new PHUIBadgeMiniView())
->setIcon($badge['icon'])
->setQuality($badge['quality'])
->setHeader($badge['name'])
->setTipDirection('E')
->setHref('/badges/view/'.$badge['id'].'/');
$badge_list[] = $badge_view;
}
$flex = new PHUIBadgeBoxView();
$flex->addItems($badge_list);
$flex->setCollapsed(true);
$badge_view = phutil_tag(
'div',
array(
'class' => 'phui-timeline-badges',
),
$flex);
}
return $badge_view;
}
}
diff --git a/src/applications/uiexample/examples/PHUIBadgeExample.php b/src/applications/uiexample/examples/PHUIBadgeExample.php
index c001a3c751..40f549e305 100644
--- a/src/applications/uiexample/examples/PHUIBadgeExample.php
+++ b/src/applications/uiexample/examples/PHUIBadgeExample.php
@@ -1,173 +1,173 @@
<?php
final class PHUIBadgeExample extends PhabricatorUIExample {
public function getName() {
return pht('Badge');
}
public function getDescription() {
return pht('Celebrate the moments of your life.');
}
public function getCategory() {
return pht('Single Use');
}
public function renderExample() {
$badges1 = array();
$badges1[] = id(new PHUIBadgeView())
->setIcon('fa-users')
->setHeader(pht('High Command'))
->setHref('/')
->setSource('Projects (automatic)')
->addByline(pht('Dec 31, 1969'))
->addByline('3 Members');
$badges1[] = id(new PHUIBadgeView())
->setIcon('fa-lock')
->setHeader(pht('Blessed Committers'))
->setSource('Projects (automatic)')
->addByline(pht('Dec 31, 1969'))
->addByline('12 Members');
$badges1[] = id(new PHUIBadgeView())
->setIcon('fa-camera-retro')
->setHeader(pht('Design'))
->setSource('Projects (automatic)')
->addByline(pht('Dec 31, 1969'))
->addByline('2 Members');
$badges1[] = id(new PHUIBadgeView())
->setIcon('fa-lock')
->setHeader(pht('Blessed Reviewers'))
->setSource('Projects (automatic)')
->addByline(pht('Dec 31, 1969'))
->addByline('3 Members');
$badges1[] = id(new PHUIBadgeView())
->setIcon('fa-umbrella')
->setHeader(pht('Wikipedia'))
->setSource('Projects (automatic)')
->addByline(pht('Dec 31, 1969'))
->addByline('22 Members');
$badges2 = array();
$badges2[] = id(new PHUIBadgeView())
->setIcon('fa-user')
- ->setHeader(pht('Phabricator User'))
+ ->setHeader(pht('User'))
->setSubhead(pht('Confirmed your account.'))
->setQuality(PhabricatorBadgesQuality::POOR)
->setSource(pht('People (automatic)'))
->addByline(pht('Dec 31, 1969'))
->addByline('212 Issued (100%)');
$badges2[] = id(new PHUIBadgeView())
->setIcon('fa-code')
->setHeader(pht('Code Contributor'))
->setSubhead(pht('Wrote code that was acceptable'))
->setQuality(PhabricatorBadgesQuality::COMMON)
->setSource('Diffusion (automatic)')
->addByline(pht('Dec 31, 1969'))
->addByline('200 Awarded (98%)');
$badges2[] = id(new PHUIBadgeView())
->setIcon('fa-bug')
->setHeader(pht('Task Master'))
->setSubhead(pht('Closed over 100 tasks'))
->setQuality(PhabricatorBadgesQuality::UNCOMMON)
->setSource('Maniphest (automatic)')
->addByline(pht('Dec 31, 1969'))
->addByline('56 Awarded (43%)');
$badges2[] = id(new PHUIBadgeView())
->setIcon('fa-star')
->setHeader(pht('Code Weaver'))
->setSubhead(pht('Landed 1,000 Commits'))
->setQuality(PhabricatorBadgesQuality::RARE)
->setSource('Diffusion (automatic)')
->addByline(pht('Dec 31, 1969'))
->addByline('42 Awarded (20%)');
$badges2[] = id(new PHUIBadgeView())
->setIcon('fa-users')
->setHeader(pht('Security Team'))
->setSubhead(pht('<script>alert(1);</script>'))
->setQuality(PhabricatorBadgesQuality::EPIC)
->setSource('Projects (automatic)')
->addByline(pht('Dec 31, 1969'))
->addByline('21 Awarded (10%)');
$badges2[] = id(new PHUIBadgeView())
->setIcon('fa-user')
->setHeader(pht('Administrator'))
->setSubhead(pht('Drew the short stick'))
->setQuality(PhabricatorBadgesQuality::LEGENDARY)
->setSource(pht('People (automatic)'))
->addByline(pht('Dec 31, 1969'))
->addByline('3 Awarded (1.4%)');
$badges2[] = id(new PHUIBadgeView())
->setIcon('fa-compass')
->setHeader(pht('Lead Developer'))
- ->setSubhead(pht('Lead Developer of Phabricator'))
+ ->setSubhead(pht('Lead Developer of Software'))
->setQuality(PhabricatorBadgesQuality::HEIRLOOM)
->setSource(pht('Direct Award'))
->addByline(pht('Dec 31, 1969'))
->addByline('1 Awarded (0.4%)');
$badges3 = array();
$badges3[] = id(new PHUIBadgeMiniView())
->setIcon('fa-book')
->setHeader(pht('Documenter'));
$badges3[] = id(new PHUIBadgeMiniView())
->setIcon('fa-star')
->setHeader(pht('Contributor'));
$badges3[] = id(new PHUIBadgeMiniView())
->setIcon('fa-bug')
->setHeader(pht('Bugmeister'));
$badges3[] = id(new PHUIBadgeMiniView())
->setIcon('fa-heart')
->setHeader(pht('Funder'))
->setQuality(PhabricatorBadgesQuality::UNCOMMON);
$badges3[] = id(new PHUIBadgeMiniView())
->setIcon('fa-user')
->setHeader(pht('Administrator'))
->setQuality(PhabricatorBadgesQuality::RARE);
$badges3[] = id(new PHUIBadgeMiniView())
->setIcon('fa-camera-retro')
->setHeader(pht('Designer'))
->setQuality(PhabricatorBadgesQuality::EPIC);
$flex1 = new PHUIBadgeBoxView();
$flex1->addItems($badges1);
$box1 = id(new PHUIObjectBoxView())
->setHeaderText(pht('Project Membership'))
->appendChild($flex1);
$flex2 = new PHUIBadgeBoxView();
$flex2->addItems($badges2);
$box2 = id(new PHUIObjectBoxView())
->setHeaderText(pht('Achievements'))
->appendChild($flex2);
$flex3 = new PHUIBadgeBoxView();
$flex3->addItems($badges3);
$flex3->setCollapsed(true);
$flex3->addClass('ml');
$box3 = id(new PHUIObjectBoxView())
->setHeaderText(pht('PHUIBadgeMiniView'))
->appendChild($flex3);
return array($box1, $box2, $box3);
}
}
diff --git a/src/applications/uiexample/examples/PHUIHovercardUIExample.php b/src/applications/uiexample/examples/PHUIHovercardUIExample.php
index b88fdc6639..2e68976ae0 100644
--- a/src/applications/uiexample/examples/PHUIHovercardUIExample.php
+++ b/src/applications/uiexample/examples/PHUIHovercardUIExample.php
@@ -1,83 +1,83 @@
<?php
final class PHUIHovercardUIExample extends PhabricatorUIExample {
public function getName() {
return pht('Hovercard');
}
public function getDescription() {
return pht(
'Use %s to render hovercards.',
phutil_tag('tt', array(), 'PHUIHovercardView'));
}
public function getCategory() {
return pht('Single Use');
}
public function renderExample() {
$request = $this->getRequest();
$user = $request->getUser();
$elements = array();
$diff_handle = $this->createBasicDummyHandle(
'D123',
DifferentialRevisionPHIDType::TYPECONST,
pht('Introduce cooler Differential Revisions'));
$panel = $this->createPanel(pht('Differential Hovercard'));
$panel->appendChild(id(new PHUIHovercardView())
->setObjectHandle($diff_handle)
->addField(pht('Author'), $user->getUsername())
->addField(pht('Updated'), phabricator_datetime(time(), $user))
->addAction(pht('Subscribe'), '/dev/random')
->setUser($user));
$elements[] = $panel;
$task_handle = $this->createBasicDummyHandle(
'T123',
ManiphestTaskPHIDType::TYPECONST,
- pht('Improve Mobile Experience for Phabricator'));
+ pht('Improve Mobile Experience'));
$tag = id(new PHUITagView())
->setType(PHUITagView::TYPE_STATE)
->setName(pht('Closed, Resolved'));
$panel = $this->createPanel(pht('Maniphest Hovercard'));
$panel->appendChild(id(new PHUIHovercardView())
->setObjectHandle($task_handle)
->setUser($user)
->addField(pht('Assigned to'), $user->getUsername())
->addField(pht('Dependent Tasks'), 'T123, T124, T125')
->addAction(pht('Subscribe'), '/dev/random')
->addAction(pht('Create Subtask'), '/dev/urandom')
->addTag($tag));
$elements[] = $panel;
$user_handle = $this->createBasicDummyHandle(
'gwashington',
PhabricatorPeopleUserPHIDType::TYPECONST,
'George Washington');
$user_handle->setImageURI(
celerity_get_resource_uri('/rsrc/image/people/washington.png'));
$panel = $this->createPanel(pht('Whatevery Hovercard'));
$panel->appendChild(id(new PHUIHovercardView())
->setObjectHandle($user_handle)
->addField(pht('Status'), pht('Available'))
->addField(pht('Member since'), '30. February 1750')
->addAction(pht('Send a Message'), '/dev/null')
->setUser($user));
$elements[] = $panel;
return phutil_implode_html('', $elements);
}
private function createPanel($header) {
$panel = new PHUIBoxView();
$panel->addClass('grouped');
$panel->addClass('ml');
return $panel;
}
}
diff --git a/src/applications/uiexample/examples/PhabricatorProjectBuiltinsExample.php b/src/applications/uiexample/examples/PhabricatorProjectBuiltinsExample.php
index 2cc89b56d5..9eb90f6de1 100644
--- a/src/applications/uiexample/examples/PhabricatorProjectBuiltinsExample.php
+++ b/src/applications/uiexample/examples/PhabricatorProjectBuiltinsExample.php
@@ -1,71 +1,71 @@
<?php
final class PhabricatorProjectBuiltinsExample extends PhabricatorUIExample {
public function getName() {
return pht('Project Builtin Images');
}
public function getDescription() {
- return pht('Builtin Project Images that ship with Phabricator.');
+ return pht('Builtin Project Images.');
}
public function getCategory() {
return pht('Catalogs');
}
public function renderExample() {
$viewer = $this->getRequest()->getUser();
$root = dirname(phutil_get_library_root('phabricator'));
$root = $root.'/resources/builtin/projects/v3/';
Javelin::initBehavior('phabricator-tooltips', array());
$map = array();
$builtin_map = id(new FileFinder($root))
->withType('f')
->withFollowSymlinks(true)
->find();
$images = array();
foreach ($builtin_map as $image) {
$file = PhabricatorFile::loadBuiltin($viewer, 'projects/v3/'.$image);
$images[$file->getPHID()] = array(
'uri' => $file->getBestURI(),
'tip' => 'v3/'.$image,
);
}
$buttons = array();
foreach ($images as $phid => $spec) {
$button = javelin_tag(
'img',
array(
'height' => 100,
'width' => 100,
'src' => $spec['uri'],
'style' => 'float: left; padding: 4px;',
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $spec['tip'],
'size' => 300,
),
));
$buttons[] = $button;
}
$wrap1 = id(new PHUIObjectBoxView())
->setHeaderText(pht('Images'))
->appendChild($buttons)
->addClass('grouped');
return phutil_tag(
'div',
array(),
array(
$wrap1,
));
}
}
diff --git a/src/applications/xhprof/query/PhabricatorXHProfSampleQuery.php b/src/applications/xhprof/query/PhabricatorXHProfSampleQuery.php
index 3e9643a7fc..2001fec0c7 100644
--- a/src/applications/xhprof/query/PhabricatorXHProfSampleQuery.php
+++ b/src/applications/xhprof/query/PhabricatorXHProfSampleQuery.php
@@ -1,51 +1,47 @@
<?php
final class PhabricatorXHProfSampleQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function newResultObject() {
return new PhabricatorXHProfSample();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorXHProfApplication';
}
}
diff --git a/src/docs/book/phabricator.book b/src/docs/book/phabricator.book
index 2beef23023..d109a9b70e 100644
--- a/src/docs/book/phabricator.book
+++ b/src/docs/book/phabricator.book
@@ -1,343 +1,335 @@
{
"name": "phabdev",
"title": "Phabricator Technical Documentation",
"short": "Phabricator Tech Docs",
"preface": "Technical reference material for Phabricator developers.",
"root": "../../../",
"uri.source":
"https://secure.phabricator.com/diffusion/P/browse/master/%f$%l",
"rules": {
"(\\.diviner$)": "DivinerArticleAtomizer",
"(\\.php$)": "DivinerPHPAtomizer"
},
"exclude": [
"(^externals/)",
"(^resources/)",
"(^scripts/)",
"(^src/docs/contributor/)",
"(^src/docs/flavor/)",
"(^src/docs/user/)",
"(^support/)",
"(^webroot/rsrc/externals/)"
],
"groups": {
"aphront": {
"name": "Aphront",
"include": "(^src/aphront/)"
},
"almanac": {
"name": "Almanac",
"include": "(^src/applications/almanac/)"
},
"aphlict": {
"name": "Aphlict",
"include": "(^src/applications/aphlict/)"
},
"arcanist": {
"name": "Arcanist Integration",
"include": "(^src/applications/arcanist/)"
},
"auth": {
"name": "Auth",
"include": "(^src/applications/auth/)"
},
"baseapp": {
"name": "Application Basics",
"include": "(^src/applications/base/)"
},
"cache": {
"name": "Cache",
"include": "(^src/applications/cache/)"
},
"calendar": {
"name": "Calendar",
"include": "(^src/applications/calendar/)"
},
"celerity": {
"name": "Celerity",
"include": "(^src/applications/celerity/)"
},
"chatlog": {
"name": "Chatlog",
"include": "(^src/applications/chatlog/)"
},
"conduit": {
"name": "Conduit",
"include": "(^src/applications/conduit/)"
},
"config": {
"name": "Config",
"include": "(^src/applications/config/)"
},
"conpherence": {
"name": "Conpherence",
"include": "(^src/applications/conpherence/)"
},
"console": {
"name": "Console",
"include": "(^src/applications/console/)"
},
"countdown": {
"name": "Countdown",
"include": "(^src/applications/countdown/)"
},
"customfield": {
"name": "Custom Fields",
"include": "(^src/infrastructure/customfield/)"
},
"daemon": {
"name": "Daemons",
"include": [
"(^src/applications/daemon/)",
"(^src/infrastructure/daemon/)"
]
},
"dashboard": {
"name": "Dashboard",
"include": "(^src/applications/dashboard/)"
},
"differential": {
"name": "Differential",
"include": "(^src/applications/differential/)"
},
"diffusion": {
"name": "Diffusion",
"include": "(^src/applications/diffusion/)"
},
"diviner": {
"name": "Diviner",
"include": "(^src/applications/diviner/)"
},
"doorkeeper": {
"name": "Doorkeeper",
"include": "(^src/applications/doorkeeper/)"
},
"draft": {
"name": "Draft",
"include": "(^src/applications/draft/)"
},
"drydock": {
"name": "Drydock",
"include": "(^src/applications/drydock/)"
},
"edges": {
"name": "Edges",
"include": "(^src/infrastructure/edges/)"
},
"events": {
"name": "Events",
"include": "(^src/infrastructure/events/)"
},
"fact": {
"name": "Fact",
"include": "(^src/applications/fact/)"
},
"feed": {
"name": "Feed",
"include": "(^src/applications/feed/)"
},
"files": {
"name": "Files",
"include": "(^src/applications/files/)"
},
"flag": {
"name": "Flags",
"include": "(^src/applications/flag/)"
},
"fund": {
"name": "Fund",
"include": "(^src/applications/fund/)"
},
"harbormaster": {
"name": "Harbormaster",
"include": "(^src/applications/harbormaster/)"
},
"help": {
"name": "Help",
"include": "(^src/applications/help/)"
},
"herald": {
"name": "Herald",
"include": "(^src/applications/herald/)"
},
"home": {
"name": "Home",
"include": "(^src/applications/home/)"
},
"legalpad": {
"name": "Legalpad",
"include": "(^src/applications/legalpad/)"
},
"lipsum": {
"name": "Lipsum",
"include": "(^src/applications/lipsum/)"
},
"macro": {
"name": "Macro",
"include": "(^src/applications/macro/)"
},
"maniphest": {
"name": "Maniphest",
"include": "(^src/applications/maniphest/)"
},
"meta": {
"name": "Applications",
"include": "(^src/applications/meta/)"
},
"metamta": {
"name": "MetaMTA",
"include": "(^src/applications/metamta/)"
},
"multimeter": {
"name": "Multimeter",
"include": "(^src/applications/multimeter/)"
},
"notification": {
"name": "Notifications",
"include": "(^src/applications/notification/)"
},
"nuance": {
"name": "Nuance",
"include": "(^src/applications/nuance/)"
},
"oauthserver": {
"name": "OAuth Server",
"include": "(^src/applications/oauthserver/)"
},
"owners": {
"name": "Owners",
"include": "(^src/applications/owners/)"
},
"passphrase": {
"name": "Passphrase",
"include": "(^src/applications/passphrase/)"
},
"paste": {
"name": "Paste",
"include": "(^src/applications/paste/)"
},
"people": {
"name": "People",
"include": "(^src/applications/people/)"
},
"phame": {
"name": "Phame",
"include": "(^src/applications/phame/)"
},
"phid": {
"name": "PHIDs",
"include": "(^src/applications/phid/)"
},
"phlux": {
"name": "Phlux",
"include": "(^src/applications/phlux/)"
},
"pholio": {
"name": "Pholio",
"include": "(^src/applications/pholio/)"
},
"phortune": {
"name": "Phortune",
"include": "(^src/applications/phortune/)"
},
"phpast": {
"name": "PHPAST",
"include": "(^src/applications/phpast/)"
},
- "phragment": {
- "name": "Phragment",
- "include": "(^src/applications/phragment/)"
- },
"phrequent": {
"name": "Phrequent",
"include": "(^src/applications/phrequent/)"
},
"phriction": {
"name": "Phriction",
"include": "(^src/applications/phriction/)"
},
"phui": {
"name": "PHUI",
"include": "(^src/view/phui/)"
},
"policy": {
"name": "Policy",
"include": "(^src/applications/policy/)"
},
"ponder": {
"name": "Ponder",
"include": "(^src/applications/ponder/)"
},
"project": {
"name": "Projects",
"include": "(^src/applications/project/)"
},
- "releeph": {
- "name": "Releeph",
- "include": "(^src/applications/releeph/)"
- },
"remarkup": {
"name": "Remarkup",
"include": [
"(^src/applications/remarkup/)",
"(^src/infrastructure/markup/)"
]
},
"repository": {
"name": "Repositories",
"include": "(^src/applications/repository/)"
},
"search": {
"name": "Search",
"include": "(^src/applications/search/)"
},
"settings": {
"name": "Settings",
"include": "(^src/applications/settings/)"
},
"slowvote": {
"name": "Slowvote",
"include": "(^src/applications/slowvote/)"
},
"spaces": {
"name": "Spaces",
"include": "(^src/applications/spaces/)"
},
"storage": {
"name": "Storage",
"include": "(^src/infrastructure/storage/)"
},
"subscriptions": {
"name": "Subscriptions",
"include": "(^src/applications/subscriptions/)"
},
"support": {
"name": "Support",
"include": "(^src/applications/support/)"
},
"system": {
"name": "System",
"include": "(^src/applications/system/)"
},
"tokens": {
"name": "Tokens",
"include": "(^src/applications/tokens/)"
},
"transactions": {
"name": "Transactions",
"include": "(^src/applications/transactions/)"
},
"typeahead": {
"name": "Typeahead",
"include": "(^src/applications/typeahead/)"
},
"uiexample": {
"name": "UI Examples",
"include": "(^src/applications/uiexample/)"
},
"xhprof": {
"name": "XHProf",
"include": "(^src/applications/xhprof/)"
}
}
}
diff --git a/src/infrastructure/cluster/PhabricatorDatabaseRef.php b/src/infrastructure/cluster/PhabricatorDatabaseRef.php
index da9f5e0d5e..1eb232ad86 100644
--- a/src/infrastructure/cluster/PhabricatorDatabaseRef.php
+++ b/src/infrastructure/cluster/PhabricatorDatabaseRef.php
@@ -1,748 +1,748 @@
<?php
final class PhabricatorDatabaseRef
extends Phobject {
const STATUS_OKAY = 'okay';
const STATUS_FAIL = 'fail';
const STATUS_AUTH = 'auth';
const STATUS_REPLICATION_CLIENT = 'replication-client';
const REPLICATION_OKAY = 'okay';
const REPLICATION_MASTER_REPLICA = 'master-replica';
const REPLICATION_REPLICA_NONE = 'replica-none';
const REPLICATION_SLOW = 'replica-slow';
const REPLICATION_NOT_REPLICATING = 'not-replicating';
const KEY_HEALTH = 'cluster.db.health';
const KEY_REFS = 'cluster.db.refs';
const KEY_INDIVIDUAL = 'cluster.db.individual';
private $host;
private $port;
private $user;
private $pass;
private $disabled;
private $isMaster;
private $isIndividual;
private $connectionLatency;
private $connectionStatus;
private $connectionMessage;
private $connectionException;
private $replicaStatus;
private $replicaMessage;
private $replicaDelay;
private $healthRecord;
private $didFailToConnect;
private $isDefaultPartition;
private $applicationMap = array();
private $masterRef;
private $replicaRefs = array();
private $usePersistentConnections;
public function setHost($host) {
$this->host = $host;
return $this;
}
public function getHost() {
return $this->host;
}
public function setPort($port) {
$this->port = $port;
return $this;
}
public function getPort() {
return $this->port;
}
public function setUser($user) {
$this->user = $user;
return $this;
}
public function getUser() {
return $this->user;
}
public function setPass(PhutilOpaqueEnvelope $pass) {
$this->pass = $pass;
return $this;
}
public function getPass() {
return $this->pass;
}
public function setIsMaster($is_master) {
$this->isMaster = $is_master;
return $this;
}
public function getIsMaster() {
return $this->isMaster;
}
public function setDisabled($disabled) {
$this->disabled = $disabled;
return $this;
}
public function getDisabled() {
return $this->disabled;
}
public function setConnectionLatency($connection_latency) {
$this->connectionLatency = $connection_latency;
return $this;
}
public function getConnectionLatency() {
return $this->connectionLatency;
}
public function setConnectionStatus($connection_status) {
$this->connectionStatus = $connection_status;
return $this;
}
public function getConnectionStatus() {
if ($this->connectionStatus === null) {
throw new PhutilInvalidStateException('queryAll');
}
return $this->connectionStatus;
}
public function setConnectionMessage($connection_message) {
$this->connectionMessage = $connection_message;
return $this;
}
public function getConnectionMessage() {
return $this->connectionMessage;
}
public function setReplicaStatus($replica_status) {
$this->replicaStatus = $replica_status;
return $this;
}
public function getReplicaStatus() {
return $this->replicaStatus;
}
public function setReplicaMessage($replica_message) {
$this->replicaMessage = $replica_message;
return $this;
}
public function getReplicaMessage() {
return $this->replicaMessage;
}
public function setReplicaDelay($replica_delay) {
$this->replicaDelay = $replica_delay;
return $this;
}
public function getReplicaDelay() {
return $this->replicaDelay;
}
public function setIsIndividual($is_individual) {
$this->isIndividual = $is_individual;
return $this;
}
public function getIsIndividual() {
return $this->isIndividual;
}
public function setIsDefaultPartition($is_default_partition) {
$this->isDefaultPartition = $is_default_partition;
return $this;
}
public function getIsDefaultPartition() {
return $this->isDefaultPartition;
}
public function setUsePersistentConnections($use_persistent_connections) {
$this->usePersistentConnections = $use_persistent_connections;
return $this;
}
public function getUsePersistentConnections() {
return $this->usePersistentConnections;
}
public function setApplicationMap(array $application_map) {
$this->applicationMap = $application_map;
return $this;
}
public function getApplicationMap() {
return $this->applicationMap;
}
public function getPartitionStateForCommit() {
$state = PhabricatorEnv::getEnvConfig('cluster.databases');
foreach ($state as $key => $value) {
// Don't store passwords, since we don't care if they differ and
// users may find it surprising.
unset($state[$key]['pass']);
}
return phutil_json_encode($state);
}
public function setMasterRef(PhabricatorDatabaseRef $master_ref) {
$this->masterRef = $master_ref;
return $this;
}
public function getMasterRef() {
return $this->masterRef;
}
public function addReplicaRef(PhabricatorDatabaseRef $replica_ref) {
$this->replicaRefs[] = $replica_ref;
return $this;
}
public function getReplicaRefs() {
return $this->replicaRefs;
}
public function getDisplayName() {
return $this->getRefKey();
}
public function getRefKey() {
$host = $this->getHost();
$port = $this->getPort();
if (strlen($port)) {
return "{$host}:{$port}";
}
return $host;
}
public static function getConnectionStatusMap() {
return array(
self::STATUS_OKAY => array(
'icon' => 'fa-exchange',
'color' => 'green',
'label' => pht('Okay'),
),
self::STATUS_FAIL => array(
'icon' => 'fa-times',
'color' => 'red',
'label' => pht('Failed'),
),
self::STATUS_AUTH => array(
'icon' => 'fa-key',
'color' => 'red',
'label' => pht('Invalid Credentials'),
),
self::STATUS_REPLICATION_CLIENT => array(
'icon' => 'fa-eye-slash',
'color' => 'yellow',
'label' => pht('Missing Permission'),
),
);
}
public static function getReplicaStatusMap() {
return array(
self::REPLICATION_OKAY => array(
'icon' => 'fa-download',
'color' => 'green',
'label' => pht('Okay'),
),
self::REPLICATION_MASTER_REPLICA => array(
'icon' => 'fa-database',
'color' => 'red',
'label' => pht('Replicating Master'),
),
self::REPLICATION_REPLICA_NONE => array(
'icon' => 'fa-download',
'color' => 'red',
'label' => pht('Not A Replica'),
),
self::REPLICATION_SLOW => array(
'icon' => 'fa-hourglass',
'color' => 'red',
'label' => pht('Slow Replication'),
),
self::REPLICATION_NOT_REPLICATING => array(
'icon' => 'fa-exclamation-triangle',
'color' => 'red',
'label' => pht('Not Replicating'),
),
);
}
public static function getClusterRefs() {
$cache = PhabricatorCaches::getRequestCache();
$refs = $cache->getKey(self::KEY_REFS);
if (!$refs) {
$refs = self::newRefs();
$cache->setKey(self::KEY_REFS, $refs);
}
return $refs;
}
public static function getLiveIndividualRef() {
$cache = PhabricatorCaches::getRequestCache();
$ref = $cache->getKey(self::KEY_INDIVIDUAL);
if (!$ref) {
$ref = self::newIndividualRef();
$cache->setKey(self::KEY_INDIVIDUAL, $ref);
}
return $ref;
}
public static function newRefs() {
$default_port = PhabricatorEnv::getEnvConfig('mysql.port');
$default_port = nonempty($default_port, 3306);
$default_user = PhabricatorEnv::getEnvConfig('mysql.user');
$default_pass = PhabricatorEnv::getEnvConfig('mysql.pass');
$default_pass = phutil_string_cast($default_pass);
$default_pass = new PhutilOpaqueEnvelope($default_pass);
$config = PhabricatorEnv::getEnvConfig('cluster.databases');
return id(new PhabricatorDatabaseRefParser())
->setDefaultPort($default_port)
->setDefaultUser($default_user)
->setDefaultPass($default_pass)
->newRefs($config);
}
public static function queryAll() {
$refs = self::getActiveDatabaseRefs();
return self::queryRefs($refs);
}
private static function queryRefs(array $refs) {
foreach ($refs as $ref) {
$conn = $ref->newManagementConnection();
$t_start = microtime(true);
$replica_status = false;
try {
$replica_status = queryfx_one($conn, 'SHOW SLAVE STATUS');
$ref->setConnectionStatus(self::STATUS_OKAY);
} catch (AphrontAccessDeniedQueryException $ex) {
$ref->setConnectionStatus(self::STATUS_REPLICATION_CLIENT);
$ref->setConnectionMessage(
pht(
'No permission to run "SHOW SLAVE STATUS". Grant this user '.
- '"REPLICATION CLIENT" permission to allow Phabricator to '.
+ '"REPLICATION CLIENT" permission to allow this server to '.
'monitor replica health.'));
} catch (AphrontInvalidCredentialsQueryException $ex) {
$ref->setConnectionStatus(self::STATUS_AUTH);
$ref->setConnectionMessage($ex->getMessage());
} catch (AphrontQueryException $ex) {
$ref->setConnectionStatus(self::STATUS_FAIL);
$class = get_class($ex);
$message = $ex->getMessage();
$ref->setConnectionMessage(
pht(
'%s: %s',
get_class($ex),
$ex->getMessage()));
}
$t_end = microtime(true);
$ref->setConnectionLatency($t_end - $t_start);
if ($replica_status !== false) {
$is_replica = (bool)$replica_status;
if ($ref->getIsMaster() && $is_replica) {
$ref->setReplicaStatus(self::REPLICATION_MASTER_REPLICA);
$ref->setReplicaMessage(
pht(
'This host has a "master" role, but is replicating data from '.
'another host ("%s")!',
idx($replica_status, 'Master_Host')));
} else if (!$ref->getIsMaster() && !$is_replica) {
$ref->setReplicaStatus(self::REPLICATION_REPLICA_NONE);
$ref->setReplicaMessage(
pht(
'This host has a "replica" role, but is not replicating data '.
'from a master (no output from "SHOW SLAVE STATUS").'));
} else {
$ref->setReplicaStatus(self::REPLICATION_OKAY);
}
if ($is_replica) {
$latency = idx($replica_status, 'Seconds_Behind_Master');
if (!strlen($latency)) {
$ref->setReplicaStatus(self::REPLICATION_NOT_REPLICATING);
} else {
$latency = (int)$latency;
$ref->setReplicaDelay($latency);
if ($latency > 30) {
$ref->setReplicaStatus(self::REPLICATION_SLOW);
$ref->setReplicaMessage(
pht(
'This replica is lagging far behind the master. Data is at '.
'risk!'));
}
}
}
}
}
return $refs;
}
public function newManagementConnection() {
return $this->newConnection(
array(
'retries' => 0,
'timeout' => 2,
));
}
public function newApplicationConnection($database) {
return $this->newConnection(
array(
'database' => $database,
));
}
public function isSevered() {
// If we only have an individual database, never sever our connection to
// it, at least for now. It's possible that using the same severing rules
// might eventually make sense to help alleviate load-related failures,
// but we should wait for all the cluster stuff to stabilize first.
if ($this->getIsIndividual()) {
return false;
}
if ($this->didFailToConnect) {
return true;
}
$record = $this->getHealthRecord();
$is_healthy = $record->getIsHealthy();
if (!$is_healthy) {
return true;
}
return false;
}
public function isReachable(AphrontDatabaseConnection $connection) {
$record = $this->getHealthRecord();
$should_check = $record->getShouldCheck();
if ($this->isSevered() && !$should_check) {
return false;
}
$this->connectionException = null;
try {
$connection->openConnection();
$reachable = true;
} catch (AphrontSchemaQueryException $ex) {
// We get one of these if the database we're trying to select does not
// exist. In this case, just re-throw the exception. This is expected
// during first-time setup, when databases like "config" will not exist
// yet.
throw $ex;
} catch (Exception $ex) {
$this->connectionException = $ex;
$reachable = false;
}
if ($should_check) {
$record->didHealthCheck($reachable);
}
if (!$reachable) {
$this->didFailToConnect = true;
}
return $reachable;
}
public function checkHealth() {
$health = $this->getHealthRecord();
$should_check = $health->getShouldCheck();
if ($should_check) {
// This does an implicit health update.
$connection = $this->newManagementConnection();
$this->isReachable($connection);
}
return $this;
}
private function getHealthRecordCacheKey() {
$host = $this->getHost();
$port = $this->getPort();
$key = self::KEY_HEALTH;
return "{$key}({$host}, {$port})";
}
public function getHealthRecord() {
if (!$this->healthRecord) {
$this->healthRecord = new PhabricatorClusterServiceHealthRecord(
$this->getHealthRecordCacheKey());
}
return $this->healthRecord;
}
public function getConnectionException() {
return $this->connectionException;
}
public static function getActiveDatabaseRefs() {
$refs = array();
foreach (self::getMasterDatabaseRefs() as $ref) {
$refs[] = $ref;
}
foreach (self::getReplicaDatabaseRefs() as $ref) {
$refs[] = $ref;
}
return $refs;
}
public static function getAllMasterDatabaseRefs() {
$refs = self::getClusterRefs();
if (!$refs) {
return array(self::getLiveIndividualRef());
}
$masters = array();
foreach ($refs as $ref) {
if ($ref->getIsMaster()) {
$masters[] = $ref;
}
}
return $masters;
}
public static function getMasterDatabaseRefs() {
$refs = self::getAllMasterDatabaseRefs();
return self::getEnabledRefs($refs);
}
public function isApplicationHost($database) {
return isset($this->applicationMap[$database]);
}
public function loadRawMySQLConfigValue($key) {
$conn = $this->newManagementConnection();
try {
$value = queryfx_one($conn, 'SELECT @@%C', $key);
// NOTE: Although MySQL allows us to escape configuration values as if
// they are column names, the escaping is included in the column name
// of the return value: if we select "@@`x`", we get back a column named
// "@@`x`", not "@@x" as we might expect.
$value = head($value);
} catch (AphrontQueryException $ex) {
$value = null;
}
return $value;
}
public static function getMasterDatabaseRefForApplication($application) {
$masters = self::getMasterDatabaseRefs();
$application_master = null;
$default_master = null;
foreach ($masters as $master) {
if ($master->isApplicationHost($application)) {
$application_master = $master;
break;
}
if ($master->getIsDefaultPartition()) {
$default_master = $master;
}
}
if ($application_master) {
$masters = array($application_master);
} else if ($default_master) {
$masters = array($default_master);
} else {
$masters = array();
}
$masters = self::getEnabledRefs($masters);
$master = head($masters);
return $master;
}
public static function newIndividualRef() {
$default_user = PhabricatorEnv::getEnvConfig('mysql.user');
$default_pass = new PhutilOpaqueEnvelope(
PhabricatorEnv::getEnvConfig('mysql.pass'));
$default_host = PhabricatorEnv::getEnvConfig('mysql.host');
$default_port = PhabricatorEnv::getEnvConfig('mysql.port');
return id(new self())
->setUser($default_user)
->setPass($default_pass)
->setHost($default_host)
->setPort($default_port)
->setIsIndividual(true)
->setIsMaster(true)
->setIsDefaultPartition(true)
->setUsePersistentConnections(false);
}
public static function getAllReplicaDatabaseRefs() {
$refs = self::getClusterRefs();
if (!$refs) {
return array();
}
$replicas = array();
foreach ($refs as $ref) {
if ($ref->getIsMaster()) {
continue;
}
$replicas[] = $ref;
}
return $replicas;
}
public static function getReplicaDatabaseRefs() {
$refs = self::getAllReplicaDatabaseRefs();
return self::getEnabledRefs($refs);
}
private static function getEnabledRefs(array $refs) {
foreach ($refs as $key => $ref) {
if ($ref->getDisabled()) {
unset($refs[$key]);
}
}
return $refs;
}
public static function getReplicaDatabaseRefForApplication($application) {
$replicas = self::getReplicaDatabaseRefs();
$application_replicas = array();
$default_replicas = array();
foreach ($replicas as $replica) {
$master = $replica->getMasterRef();
if ($master->isApplicationHost($application)) {
$application_replicas[] = $replica;
}
if ($master->getIsDefaultPartition()) {
$default_replicas[] = $replica;
}
}
if ($application_replicas) {
$replicas = $application_replicas;
} else {
$replicas = $default_replicas;
}
$replicas = self::getEnabledRefs($replicas);
// TODO: We may have multiple replicas to choose from, and could make
// more of an effort to pick the "best" one here instead of always
// picking the first one. Once we've picked one, we should try to use
// the same replica for the rest of the request, though.
return head($replicas);
}
private function newConnection(array $options) {
// If we believe the database is unhealthy, don't spend as much time
// trying to connect to it, since it's likely to continue to fail and
// hammering it can only make the problem worse.
$record = $this->getHealthRecord();
if ($record->getIsHealthy()) {
$default_retries = 3;
$default_timeout = 10;
} else {
$default_retries = 0;
$default_timeout = 2;
}
$spec = $options + array(
'user' => $this->getUser(),
'pass' => $this->getPass(),
'host' => $this->getHost(),
'port' => $this->getPort(),
'database' => null,
'retries' => $default_retries,
'timeout' => $default_timeout,
'persistent' => $this->getUsePersistentConnections(),
);
$is_cli = (php_sapi_name() == 'cli');
$use_persistent = false;
if (!empty($spec['persistent']) && !$is_cli) {
$use_persistent = true;
}
unset($spec['persistent']);
$connection = self::newRawConnection($spec);
// If configured, use persistent connections. See T11672 for details.
if ($use_persistent) {
$connection->setPersistent($use_persistent);
}
// Unless this is a script running from the CLI, prevent any query from
// running for more than 30 seconds. See T10849 for details.
if (!$is_cli) {
$connection->setQueryTimeout(30);
}
return $connection;
}
public static function newRawConnection(array $options) {
if (extension_loaded('mysqli')) {
return new AphrontMySQLiDatabaseConnection($options);
} else {
return new AphrontMySQLDatabaseConnection($options);
}
}
}
diff --git a/src/infrastructure/cluster/PhabricatorDatabaseRefParser.php b/src/infrastructure/cluster/PhabricatorDatabaseRefParser.php
index db498e8a82..83eaa096b8 100644
--- a/src/infrastructure/cluster/PhabricatorDatabaseRefParser.php
+++ b/src/infrastructure/cluster/PhabricatorDatabaseRefParser.php
@@ -1,221 +1,221 @@
<?php
final class PhabricatorDatabaseRefParser
extends Phobject {
private $defaultPort = 3306;
private $defaultUser;
private $defaultPass;
public function setDefaultPort($default_port) {
$this->defaultPort = $default_port;
return $this;
}
public function getDefaultPort() {
return $this->defaultPort;
}
public function setDefaultUser($default_user) {
$this->defaultUser = $default_user;
return $this;
}
public function getDefaultUser() {
return $this->defaultUser;
}
public function setDefaultPass($default_pass) {
$this->defaultPass = $default_pass;
return $this;
}
public function getDefaultPass() {
return $this->defaultPass;
}
public function newRefs(array $config) {
$default_port = $this->getDefaultPort();
$default_user = $this->getDefaultUser();
$default_pass = $this->getDefaultPass();
$refs = array();
$master_count = 0;
foreach ($config as $key => $server) {
$host = $server['host'];
$port = idx($server, 'port', $default_port);
$user = idx($server, 'user', $default_user);
$disabled = idx($server, 'disabled', false);
$pass = idx($server, 'pass');
if ($pass) {
$pass = new PhutilOpaqueEnvelope($pass);
} else {
$pass = clone $default_pass;
}
$role = $server['role'];
$is_master = ($role == 'master');
$use_persistent = (bool)idx($server, 'persistent', false);
$ref = id(new PhabricatorDatabaseRef())
->setHost($host)
->setPort($port)
->setUser($user)
->setPass($pass)
->setDisabled($disabled)
->setIsMaster($is_master)
->setUsePersistentConnections($use_persistent);
if ($is_master) {
$master_count++;
}
$refs[$key] = $ref;
}
$is_partitioned = ($master_count > 1);
if ($is_partitioned) {
$default_ref = null;
$partition_map = array();
foreach ($refs as $key => $ref) {
if (!$ref->getIsMaster()) {
continue;
}
$server = $config[$key];
$partition = idx($server, 'partition');
if (!is_array($partition)) {
throw new Exception(
pht(
- 'Phabricator is configured with multiple master databases, '.
+ 'This server is configured with multiple master databases, '.
'but master "%s" is missing a "partition" configuration key to '.
'define application partitioning.',
$ref->getRefKey()));
}
$application_map = array();
foreach ($partition as $application) {
if ($application === 'default') {
if ($default_ref) {
throw new Exception(
pht(
'Multiple masters (databases "%s" and "%s") specify that '.
'they are the "default" partition. Only one master may be '.
'the default.',
$ref->getRefKey(),
$default_ref->getRefKey()));
} else {
$default_ref = $ref;
$ref->setIsDefaultPartition(true);
}
} else if (isset($partition_map[$application])) {
throw new Exception(
pht(
'Multiple masters (databases "%s" and "%s") specify that '.
'they are the partition for application "%s". Each '.
'application may be allocated to only one partition.',
$partition_map[$application]->getRefKey(),
$ref->getRefKey(),
$application));
} else {
// TODO: We should check that the application is valid, to
// prevent typos in application names. However, we do not
// currently have an efficient way to enumerate all of the valid
// application database names.
$partition_map[$application] = $ref;
$application_map[$application] = $application;
}
}
$ref->setApplicationMap($application_map);
}
} else {
// If we only have one master, make it the default.
foreach ($refs as $ref) {
if ($ref->getIsMaster()) {
$ref->setIsDefaultPartition(true);
}
}
}
$ref_map = array();
$master_keys = array();
foreach ($refs as $ref) {
$ref_key = $ref->getRefKey();
if (isset($ref_map[$ref_key])) {
throw new Exception(
pht(
'Multiple configured databases have the same internal '.
'key, "%s". You may have listed a database multiple times.',
$ref_key));
} else {
$ref_map[$ref_key] = $ref;
if ($ref->getIsMaster()) {
$master_keys[] = $ref_key;
}
}
}
foreach ($refs as $key => $ref) {
if ($ref->getIsMaster()) {
continue;
}
$server = $config[$key];
$partition = idx($server, 'partition');
if ($partition !== null) {
throw new Exception(
pht(
'Database "%s" is configured as a replica, but specifies a '.
'"partition". Only master databases may have a partition '.
'configuration. Replicas use the same configuration as the '.
'master they follow.',
$ref->getRefKey()));
}
$master_key = idx($server, 'master');
if ($master_key === null) {
if ($is_partitioned) {
throw new Exception(
pht(
'Database "%s" is configured as a replica, but does not '.
'specify which "master" it follows in configuration. Valid '.
'masters are: %s.',
$ref->getRefKey(),
implode(', ', $master_keys)));
} else if ($master_keys) {
$master_key = head($master_keys);
} else {
throw new Exception(
pht(
'Database "%s" is configured as a replica, but there is no '.
'master configured.',
$ref->getRefKey()));
}
}
if (!isset($ref_map[$master_key])) {
throw new Exception(
pht(
'Database "%s" is configured as a replica and specifies a '.
'master ("%s"), but that master is not a valid master. Valid '.
'masters are: %s.',
$ref->getRefKey(),
$master_key,
implode(', ', $master_keys)));
}
$master_ref = $ref_map[$master_key];
$ref->setMasterRef($ref_map[$master_key]);
$master_ref->addReplicaRef($ref);
}
return array_values($refs);
}
}
diff --git a/src/infrastructure/contentsource/PhabricatorContentSource.php b/src/infrastructure/contentsource/PhabricatorContentSource.php
index 3a5ea19f57..ee77052113 100644
--- a/src/infrastructure/contentsource/PhabricatorContentSource.php
+++ b/src/infrastructure/contentsource/PhabricatorContentSource.php
@@ -1,92 +1,92 @@
<?php
abstract class PhabricatorContentSource extends Phobject {
private $source;
private $params = array();
abstract public function getSourceName();
abstract public function getSourceDescription();
final public function getSourceTypeConstant() {
return $this->getPhobjectClassConstant('SOURCECONST', 32);
}
final public static function getAllContentSources() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getSourceTypeConstant')
->execute();
}
/**
* Construct a new content source object.
*
* @param const The source type constant to build a source for.
* @param array Source parameters.
* @param bool True to suppress errors and force construction of a source
* even if the source type is not valid.
* @return PhabricatorContentSource New source object.
*/
final public static function newForSource(
$source,
array $params = array(),
$force = false) {
$map = self::getAllContentSources();
if (isset($map[$source])) {
$obj = clone $map[$source];
} else {
if ($force) {
$obj = new PhabricatorUnknownContentSource();
} else {
throw new Exception(
pht(
- 'Content source type "%s" is not known to Phabricator!',
+ 'Content source type "%s" is unknown.',
$source));
}
}
$obj->source = $source;
$obj->params = $params;
return $obj;
}
public static function newFromSerialized($serialized) {
$dict = json_decode($serialized, true);
if (!is_array($dict)) {
$dict = array();
}
$source = idx($dict, 'source');
$params = idx($dict, 'params');
if (!is_array($params)) {
$params = array();
}
return self::newForSource($source, $params, true);
}
public static function newFromRequest(AphrontRequest $request) {
return self::newForSource(
PhabricatorWebContentSource::SOURCECONST);
}
final public function serialize() {
return phutil_json_encode(
array(
'source' => $this->getSource(),
'params' => $this->params,
));
}
final public function getSource() {
return $this->source;
}
final public function getContentSourceParameter($key, $default = null) {
return idx($this->params, $key, $default);
}
}
diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php
index 78c8caa5a9..a96ebefda1 100644
--- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php
+++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php
@@ -1,271 +1,275 @@
<?php
/**
* Common code for standard field types which store lists of PHIDs.
*/
abstract class PhabricatorStandardCustomFieldPHIDs
extends PhabricatorStandardCustomField {
public function buildFieldIndexes() {
$indexes = array();
$value = $this->getFieldValue();
if (is_array($value)) {
foreach ($value as $phid) {
$indexes[] = $this->newStringIndex($phid);
}
}
return $indexes;
}
public function readValueFromRequest(AphrontRequest $request) {
$value = $request->getArr($this->getFieldKey());
$this->setFieldValue($value);
}
public function getValueForStorage() {
$value = $this->getFieldValue();
if (!$value) {
return null;
}
return json_encode(array_values($value));
}
public function setValueFromStorage($value) {
// NOTE: We're accepting either a JSON string (a real storage value) or
// an array (from HTTP parameter prefilling). This is a little hacky, but
// should hold until this can get cleaned up more thoroughly.
// TODO: Clean this up.
$result = array();
if (!is_array($value)) {
$value = json_decode($value, true);
if (is_array($value)) {
$result = array_values($value);
}
}
$this->setFieldValue($value);
return $this;
}
public function readApplicationSearchValueFromRequest(
PhabricatorApplicationSearchEngine $engine,
AphrontRequest $request) {
return $request->getArr($this->getFieldKey());
}
public function applyApplicationSearchConstraintToQuery(
PhabricatorApplicationSearchEngine $engine,
PhabricatorCursorPagedPolicyAwareQuery $query,
$value) {
if ($value) {
$query->withApplicationSearchContainsConstraint(
$this->newStringIndex(null),
$value);
}
}
public function getRequiredHandlePHIDsForPropertyView() {
$value = $this->getFieldValue();
if ($value) {
return $value;
}
return array();
}
public function renderPropertyViewValue(array $handles) {
$value = $this->getFieldValue();
if (!$value) {
return null;
}
$handles = mpull($handles, 'renderHovercardLink');
$handles = phutil_implode_html(', ', $handles);
return $handles;
}
public function getRequiredHandlePHIDsForEdit() {
$value = $this->getFieldValue();
if ($value) {
return $value;
} else {
return array();
}
}
public function getApplicationTransactionRequiredHandlePHIDs(
PhabricatorApplicationTransaction $xaction) {
$old = $this->decodeValue($xaction->getOldValue());
$new = $this->decodeValue($xaction->getNewValue());
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
return array_merge($add, $rem);
}
public function getApplicationTransactionTitle(
PhabricatorApplicationTransaction $xaction) {
$author_phid = $xaction->getAuthorPHID();
$old = $this->decodeValue($xaction->getOldValue());
$new = $this->decodeValue($xaction->getNewValue());
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
if ($add && !$rem) {
return pht(
'%s updated %s, added %d: %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
phutil_count($add),
$xaction->renderHandleList($add));
} else if ($rem && !$add) {
return pht(
'%s updated %s, removed %s: %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
phutil_count($rem),
$xaction->renderHandleList($rem));
} else {
return pht(
'%s updated %s, added %s: %s; removed %s: %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
phutil_count($add),
$xaction->renderHandleList($add),
phutil_count($rem),
$xaction->renderHandleList($rem));
}
}
public function getApplicationTransactionTitleForFeed(
PhabricatorApplicationTransaction $xaction) {
$author_phid = $xaction->getAuthorPHID();
$object_phid = $xaction->getObjectPHID();
$old = $this->decodeValue($xaction->getOldValue());
$new = $this->decodeValue($xaction->getNewValue());
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
if ($add && !$rem) {
return pht(
'%s updated %s for %s, added %d: %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
$xaction->renderHandleLink($object_phid),
phutil_count($add),
$xaction->renderHandleList($add));
} else if ($rem && !$add) {
return pht(
'%s updated %s for %s, removed %s: %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
$xaction->renderHandleLink($object_phid),
phutil_count($rem),
$xaction->renderHandleList($rem));
} else {
return pht(
'%s updated %s for %s, added %s: %s; removed %s: %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
$xaction->renderHandleLink($object_phid),
phutil_count($add),
$xaction->renderHandleList($add),
phutil_count($rem),
$xaction->renderHandleList($rem));
}
}
public function validateApplicationTransactions(
PhabricatorApplicationTransactionEditor $editor,
$type,
array $xactions) {
$errors = parent::validateApplicationTransactions(
$editor,
$type,
$xactions);
// If the user is adding PHIDs, make sure the new PHIDs are valid and
// visible to the actor. It's OK for a user to edit a field which includes
// some invalid or restricted values, but they can't add new ones.
foreach ($xactions as $xaction) {
$old = $this->decodeValue($xaction->getOldValue());
$new = $this->decodeValue($xaction->getNewValue());
$add = array_diff($new, $old);
$invalid = PhabricatorObjectQuery::loadInvalidPHIDsForViewer(
$editor->getActor(),
$add);
if ($invalid) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
'Some of the selected PHIDs in field "%s" are invalid or '.
'restricted: %s.',
$this->getFieldName(),
implode(', ', $invalid)),
$xaction);
$errors[] = $error;
$this->setFieldError(pht('Invalid'));
}
}
return $errors;
}
public function shouldAppearInHerald() {
return true;
}
public function getHeraldFieldConditions() {
return array(
HeraldAdapter::CONDITION_INCLUDE_ALL,
HeraldAdapter::CONDITION_INCLUDE_ANY,
HeraldAdapter::CONDITION_INCLUDE_NONE,
HeraldAdapter::CONDITION_EXISTS,
HeraldAdapter::CONDITION_NOT_EXISTS,
);
}
public function getHeraldFieldStandardType() {
return HeraldField::STANDARD_PHID_NULLABLE;
}
public function getHeraldFieldValue() {
// If the field has a `null` value, make sure we hand an `array()` to
// Herald.
$value = parent::getHeraldFieldValue();
if ($value) {
return $value;
}
return array();
}
protected function decodeValue($value) {
+ if ($value === null) {
+ return array();
+ }
+
$value = json_decode($value);
if (!is_array($value)) {
$value = array();
}
return $value;
}
protected function getHTTPParameterType() {
return new AphrontPHIDListHTTPParameterType();
}
}
diff --git a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementFloodWorkflow.php b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementFloodWorkflow.php
index fb067ddaac..5719f82b18 100644
--- a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementFloodWorkflow.php
+++ b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementFloodWorkflow.php
@@ -1,50 +1,50 @@
<?php
final class PhabricatorWorkerManagementFloodWorkflow
extends PhabricatorWorkerManagementWorkflow {
protected function didConstruct() {
$this
->setName('flood')
->setExamples('**flood**')
->setSynopsis(
pht(
'Flood the queue with test tasks. This command is intended for '.
- 'use when developing and debugging Phabricator.'))
+ 'use during development and debugging.'))
->setArguments(
array(
array(
'name' => 'duration',
'param' => 'seconds',
'help' => pht(
'Queue tasks which require a specific amount of wall time to '.
'complete. By default, tasks complete as quickly as possible.'),
'default' => 0,
),
));
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$duration = (float)$args->getArg('duration');
$console->writeOut(
"%s\n",
pht('Adding many test tasks to worker queue. Use ^C to exit.'));
$n = 0;
while (true) {
PhabricatorWorker::scheduleTask(
'PhabricatorTestWorker',
array(
'duration' => $duration,
));
if (($n++ % 100) === 0) {
$console->writeOut('.');
}
}
}
}
diff --git a/src/infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobQuery.php b/src/infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobQuery.php
index 32a9419a33..b359157e06 100644
--- a/src/infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobQuery.php
+++ b/src/infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobQuery.php
@@ -1,106 +1,102 @@
<?php
final class PhabricatorWorkerBulkJobQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $authorPHIDs;
private $bulkJobTypes;
private $statuses;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withAuthorPHIDs(array $author_phids) {
$this->authorPHIDs = $author_phids;
return $this;
}
public function withBulkJobTypes(array $job_types) {
$this->bulkJobTypes = $job_types;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function newResultObject() {
return new PhabricatorWorkerBulkJob();
}
- protected function loadPage() {
- return $this->loadStandardPage($this->newResultObject());
- }
-
protected function willFilterPage(array $page) {
$map = PhabricatorWorkerBulkJobType::getAllJobTypes();
foreach ($page as $key => $job) {
$implementation = idx($map, $job->getJobTypeKey());
if (!$implementation) {
$this->didRejectResult($job);
unset($page[$key]);
continue;
}
$job->attachJobImplementation($implementation);
}
return $page;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->authorPHIDs !== null) {
$where[] = qsprintf(
$conn,
'authorPHID IN (%Ls)',
$this->authorPHIDs);
}
if ($this->bulkJobTypes !== null) {
$where[] = qsprintf(
$conn,
'bulkJobType IN (%Ls)',
$this->bulkJobTypes);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'status IN (%Ls)',
$this->statuses);
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorDaemonsApplication';
}
}
diff --git a/src/infrastructure/diff/engine/PhabricatorInlineCommentAdjustmentEngine.php b/src/infrastructure/diff/engine/PhabricatorInlineCommentAdjustmentEngine.php
index f1888cc363..0589cc0234 100644
--- a/src/infrastructure/diff/engine/PhabricatorInlineCommentAdjustmentEngine.php
+++ b/src/infrastructure/diff/engine/PhabricatorInlineCommentAdjustmentEngine.php
@@ -1,372 +1,370 @@
<?php
final class PhabricatorInlineCommentAdjustmentEngine
extends Phobject {
private $viewer;
private $inlines;
private $revision;
private $oldChangesets;
private $newChangesets;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setInlines(array $inlines) {
assert_instances_of($inlines, 'DifferentialInlineComment');
$this->inlines = $inlines;
return $this;
}
public function getInlines() {
return $this->inlines;
}
public function setOldChangesets(array $old_changesets) {
assert_instances_of($old_changesets, 'DifferentialChangeset');
$this->oldChangesets = $old_changesets;
return $this;
}
public function getOldChangesets() {
return $this->oldChangesets;
}
public function setNewChangesets(array $new_changesets) {
assert_instances_of($new_changesets, 'DifferentialChangeset');
$this->newChangesets = $new_changesets;
return $this;
}
public function getNewChangesets() {
return $this->newChangesets;
}
public function setRevision(DifferentialRevision $revision) {
$this->revision = $revision;
return $this;
}
public function getRevision() {
return $this->revision;
}
public function execute() {
$viewer = $this->getViewer();
$inlines = $this->getInlines();
$revision = $this->getRevision();
$old = $this->getOldChangesets();
$new = $this->getNewChangesets();
$no_ghosts = $viewer->compareUserSetting(
PhabricatorOlderInlinesSetting::SETTINGKEY,
PhabricatorOlderInlinesSetting::VALUE_GHOST_INLINES_DISABLED);
if ($no_ghosts) {
return $inlines;
}
$all = array_merge($old, $new);
$changeset_ids = mpull($inlines, 'getChangesetID');
$changeset_ids = array_unique($changeset_ids);
$all_map = mpull($all, null, 'getID');
// We already have at least some changesets, and we might not need to do
// any more data fetching. Remove everything we already have so we can
// tell if we need new stuff.
foreach ($changeset_ids as $key => $id) {
if (isset($all_map[$id])) {
unset($changeset_ids[$key]);
}
}
if ($changeset_ids) {
$changesets = id(new DifferentialChangesetQuery())
->setViewer($viewer)
->withIDs($changeset_ids)
->execute();
$changesets = mpull($changesets, null, 'getID');
} else {
$changesets = array();
}
$changesets += $all_map;
$id_map = array();
foreach ($all as $changeset) {
$id_map[$changeset->getID()] = $changeset->getID();
}
// Generate filename maps for older and newer comments. If we're bringing
// an older comment forward in a diff-of-diffs, we want to put it on the
// left side of the screen, not the right side. Both sides are "new" files
// with the same name, so they're both appropriate targets, but the left
// is a better target conceptually for users because it's more consistent
// with the rest of the UI, which shows old information on the left and
// new information on the right.
$move_here = DifferentialChangeType::TYPE_MOVE_HERE;
$name_map_old = array();
$name_map_new = array();
$move_map = array();
foreach ($all as $changeset) {
$changeset_id = $changeset->getID();
$filenames = array();
$filenames[] = $changeset->getFilename();
// If this is the target of a move, also map comments on the old filename
// to this changeset.
if ($changeset->getChangeType() == $move_here) {
$old_file = $changeset->getOldFile();
$filenames[] = $old_file;
$move_map[$changeset_id][$old_file] = true;
}
foreach ($filenames as $filename) {
// We update the old map only if we don't already have an entry (oldest
// changeset persists).
if (empty($name_map_old[$filename])) {
$name_map_old[$filename] = $changeset_id;
}
// We always update the new map (newest changeset overwrites).
$name_map_new[$changeset->getFilename()] = $changeset_id;
}
}
- // Find the smallest "new" changeset ID. We'll consider everything
- // larger than this to be "newer", and everything smaller to be "older".
- $first_new_id = min(mpull($new, 'getID'));
+ $new_id_map = mpull($new, null, 'getID');
$results = array();
foreach ($inlines as $inline) {
$changeset_id = $inline->getChangesetID();
if (isset($id_map[$changeset_id])) {
// This inline is legitimately on one of the current changesets, so
// we can include it in the result set unmodified.
$results[] = $inline;
continue;
}
$changeset = idx($changesets, $changeset_id);
if (!$changeset) {
// Just discard this inline, as it has bogus data.
continue;
}
$target_id = null;
- if ($changeset_id >= $first_new_id) {
+ if (isset($new_id_map[$changeset_id])) {
$name_map = $name_map_new;
$is_new = true;
} else {
$name_map = $name_map_old;
$is_new = false;
}
$filename = $changeset->getFilename();
if (isset($name_map[$filename])) {
// This changeset is on a file with the same name as the current
// changeset, so we're going to port it forward or backward.
$target_id = $name_map[$filename];
$is_move = isset($move_map[$target_id][$filename]);
if ($is_new) {
if ($is_move) {
$reason = pht(
'This comment was made on a file with the same name as the '.
'file this file was moved from, but in a newer diff.');
} else {
$reason = pht(
'This comment was made on a file with the same name, but '.
'in a newer diff.');
}
} else {
if ($is_move) {
$reason = pht(
'This comment was made on a file with the same name as the '.
'file this file was moved from, but in an older diff.');
} else {
$reason = pht(
'This comment was made on a file with the same name, but '.
'in an older diff.');
}
}
}
// If we didn't find a target and this change is the target of a move,
// look for a match against the old filename.
if (!$target_id) {
if ($changeset->getChangeType() == $move_here) {
$filename = $changeset->getOldFile();
if (isset($name_map[$filename])) {
$target_id = $name_map[$filename];
if ($is_new) {
$reason = pht(
'This comment was made on a file which this file was moved '.
'to, but in a newer diff.');
} else {
$reason = pht(
'This comment was made on a file which this file was moved '.
'to, but in an older diff.');
}
}
}
}
// If we found a changeset to port this comment to, bring it forward
// or backward and mark it.
if ($target_id) {
$diff_id = $changeset->getDiffID();
$inline_id = $inline->getID();
$revision_id = $revision->getID();
$href = "/D{$revision_id}?id={$diff_id}#inline-{$inline_id}";
$inline
->makeEphemeral(true)
->setChangesetID($target_id)
->setIsGhost(
array(
'new' => $is_new,
'reason' => $reason,
'href' => $href,
'originalID' => $changeset->getID(),
));
$results[] = $inline;
}
}
// Filter out the inlines we ported forward which won't be visible because
// they appear on the wrong side of a file.
$keep_map = array();
foreach ($old as $changeset) {
$keep_map[$changeset->getID()][0] = true;
}
foreach ($new as $changeset) {
$keep_map[$changeset->getID()][1] = true;
}
foreach ($results as $key => $inline) {
$is_new = (int)$inline->getIsNewFile();
$changeset_id = $inline->getChangesetID();
if (!isset($keep_map[$changeset_id][$is_new])) {
unset($results[$key]);
continue;
}
}
// Adjust inline line numbers to account for content changes across
// updates and rebases.
$plan = array();
$need = array();
foreach ($results as $inline) {
$ghost = $inline->getIsGhost();
if (!$ghost) {
// If this isn't a "ghost" inline, ignore it.
continue;
}
$src_id = $ghost['originalID'];
$dst_id = $inline->getChangesetID();
$xforms = array();
// If the comment is on the right, transform it through the inverse map
// back to the left.
if ($inline->getIsNewFile()) {
$xforms[] = array($src_id, $src_id, true);
}
// Transform it across rebases.
$xforms[] = array($src_id, $dst_id, false);
// If the comment is on the right, transform it back onto the right.
if ($inline->getIsNewFile()) {
$xforms[] = array($dst_id, $dst_id, false);
}
$key = array();
foreach ($xforms as $xform) {
list($u, $v, $inverse) = $xform;
$short = $u.'/'.$v;
$need[$short] = array($u, $v);
$part = $u.($inverse ? '<' : '>').$v;
$key[] = $part;
}
$key = implode(',', $key);
if (empty($plan[$key])) {
$plan[$key] = array(
'xforms' => $xforms,
'inlines' => array(),
);
}
$plan[$key]['inlines'][] = $inline;
}
if ($need) {
$maps = DifferentialLineAdjustmentMap::loadMaps($need);
} else {
$maps = array();
}
foreach ($plan as $step) {
$xforms = $step['xforms'];
$chain = null;
foreach ($xforms as $xform) {
list($u, $v, $inverse) = $xform;
$map = idx(idx($maps, $u, array()), $v);
if (!$map) {
continue 2;
}
if ($inverse) {
$map = DifferentialLineAdjustmentMap::newInverseMap($map);
} else {
$map = clone $map;
}
if ($chain) {
$chain->addMapToChain($map);
} else {
$chain = $map;
}
}
foreach ($step['inlines'] as $inline) {
$head_line = $inline->getLineNumber();
$tail_line = ($head_line + $inline->getLineLength());
$head_info = $chain->mapLine($head_line, false);
$tail_info = $chain->mapLine($tail_line, true);
list($head_deleted, $head_offset, $head_line) = $head_info;
list($tail_deleted, $tail_offset, $tail_line) = $tail_info;
if ($head_offset !== false) {
$inline->setLineNumber($head_line + $head_offset);
} else {
$inline->setLineNumber($head_line);
$inline->setLineLength($tail_line - $head_line);
}
}
}
return $results;
}
}
diff --git a/src/infrastructure/env/PhabricatorEnv.php b/src/infrastructure/env/PhabricatorEnv.php
index 5e265b6dce..0bd69a6d2d 100644
--- a/src/infrastructure/env/PhabricatorEnv.php
+++ b/src/infrastructure/env/PhabricatorEnv.php
@@ -1,998 +1,998 @@
<?php
/**
* Manages the execution environment configuration, exposing APIs to read
* configuration settings and other similar values that are derived directly
* from configuration settings.
*
*
* = Reading Configuration =
*
* The primary role of this class is to provide an API for reading
* Phabricator configuration, @{method:getEnvConfig}:
*
* $value = PhabricatorEnv::getEnvConfig('some.key', $default);
*
* The class also handles some URI construction based on configuration, via
* the methods @{method:getURI}, @{method:getProductionURI},
* @{method:getCDNURI}, and @{method:getDoclink}.
*
* For configuration which allows you to choose a class to be responsible for
* some functionality (e.g., which mail adapter to use to deliver email),
* @{method:newObjectFromConfig} provides a simple interface that validates
* the configured value.
*
*
* = Unit Test Support =
*
* In unit tests, you can use @{method:beginScopedEnv} to create a temporary,
* mutable environment. The method returns a scope guard object which restores
* the environment when it is destroyed. For example:
*
* public function testExample() {
* $env = PhabricatorEnv::beginScopedEnv();
* $env->overrideEnv('some.key', 'new-value-for-this-test');
*
* // Some test which depends on the value of 'some.key'.
*
* }
*
* Your changes will persist until the `$env` object leaves scope or is
* destroyed.
*
* You should //not// use this in normal code.
*
*
* @task read Reading Configuration
* @task uri URI Validation
* @task test Unit Test Support
* @task internal Internals
*/
final class PhabricatorEnv extends Phobject {
private static $sourceStack;
private static $repairSource;
private static $overrideSource;
private static $requestBaseURI;
private static $cache;
private static $localeCode;
private static $readOnly;
private static $readOnlyReason;
const READONLY_CONFIG = 'config';
const READONLY_UNREACHABLE = 'unreachable';
const READONLY_SEVERED = 'severed';
const READONLY_MASTERLESS = 'masterless';
/**
* @phutil-external-symbol class PhabricatorStartup
*/
public static function initializeWebEnvironment() {
self::initializeCommonEnvironment(false);
}
public static function initializeScriptEnvironment($config_optional) {
self::initializeCommonEnvironment($config_optional);
// NOTE: This is dangerous in general, but we know we're in a script context
// and are not vulnerable to CSRF.
AphrontWriteGuard::allowDangerousUnguardedWrites(true);
// There are several places where we log information (about errors, events,
// service calls, etc.) for analysis via DarkConsole or similar. These are
// useful for web requests, but grow unboundedly in long-running scripts and
// daemons. Discard data as it arrives in these cases.
PhutilServiceProfiler::getInstance()->enableDiscardMode();
DarkConsoleErrorLogPluginAPI::enableDiscardMode();
DarkConsoleEventPluginAPI::enableDiscardMode();
}
private static function initializeCommonEnvironment($config_optional) {
PhutilErrorHandler::initialize();
self::resetUmask();
self::buildConfigurationSourceStack($config_optional);
// Force a valid timezone. If both PHP and Phabricator configuration are
// invalid, use UTC.
$tz = self::getEnvConfig('phabricator.timezone');
if ($tz) {
@date_default_timezone_set($tz);
}
$ok = @date_default_timezone_set(date_default_timezone_get());
if (!$ok) {
date_default_timezone_set('UTC');
}
// Prepend '/support/bin' and append any paths to $PATH if we need to.
$env_path = getenv('PATH');
$phabricator_path = dirname(phutil_get_library_root('phabricator'));
$support_path = $phabricator_path.'/support/bin';
$env_path = $support_path.PATH_SEPARATOR.$env_path;
$append_dirs = self::getEnvConfig('environment.append-paths');
if (!empty($append_dirs)) {
$append_path = implode(PATH_SEPARATOR, $append_dirs);
$env_path = $env_path.PATH_SEPARATOR.$append_path;
}
putenv('PATH='.$env_path);
// Write this back into $_ENV, too, so ExecFuture picks it up when creating
// subprocess environments.
$_ENV['PATH'] = $env_path;
// If an instance identifier is defined, write it into the environment so
// it's available to subprocesses.
$instance = self::getEnvConfig('cluster.instance');
if (strlen($instance)) {
putenv('PHABRICATOR_INSTANCE='.$instance);
$_ENV['PHABRICATOR_INSTANCE'] = $instance;
}
PhabricatorEventEngine::initialize();
// TODO: Add a "locale.default" config option once we have some reasonable
// defaults which aren't silly nonsense.
self::setLocaleCode('en_US');
// Load the preamble utility library if we haven't already. On web
// requests this loaded earlier, but we want to load it for non-web
// requests so that unit tests can call these functions.
require_once $phabricator_path.'/support/startup/preamble-utils.php';
}
public static function beginScopedLocale($locale_code) {
return new PhabricatorLocaleScopeGuard($locale_code);
}
public static function getLocaleCode() {
return self::$localeCode;
}
public static function setLocaleCode($locale_code) {
if (!$locale_code) {
return;
}
if ($locale_code == self::$localeCode) {
return;
}
try {
$locale = PhutilLocale::loadLocale($locale_code);
$translations = PhutilTranslation::getTranslationMapForLocale(
$locale_code);
$override = self::getEnvConfig('translation.override');
if (!is_array($override)) {
$override = array();
}
PhutilTranslator::getInstance()
->setLocale($locale)
->setTranslations($override + $translations);
self::$localeCode = $locale_code;
} catch (Exception $ex) {
// Just ignore this; the user likely has an out-of-date locale code.
}
}
private static function buildConfigurationSourceStack($config_optional) {
self::dropConfigCache();
$stack = new PhabricatorConfigStackSource();
self::$sourceStack = $stack;
$default_source = id(new PhabricatorConfigDefaultSource())
->setName(pht('Global Default'));
$stack->pushSource($default_source);
$env = self::getSelectedEnvironmentName();
if ($env) {
$stack->pushSource(
id(new PhabricatorConfigFileSource($env))
->setName(pht("File '%s'", $env)));
}
$stack->pushSource(
id(new PhabricatorConfigLocalSource())
->setName(pht('Local Config')));
// If the install overrides the database adapter, we might need to load
// the database adapter class before we can push on the database config.
// This config is locked and can't be edited from the web UI anyway.
foreach (self::getEnvConfig('load-libraries') as $library) {
phutil_load_library($library);
}
// Drop any class map caches, since they will have generated without
// any classes from libraries. Without this, preflight setup checks can
// cause generation of a setup check cache that omits checks defined in
// libraries, for example.
PhutilClassMapQuery::deleteCaches();
// If custom libraries specify config options, they won't get default
// values as the Default source has already been loaded, so we get it to
// pull in all options from non-phabricator libraries now they are loaded.
$default_source->loadExternalOptions();
// If this install has site config sources, load them now.
$site_sources = id(new PhutilClassMapQuery())
->setAncestorClass('PhabricatorConfigSiteSource')
->setSortMethod('getPriority')
->execute();
foreach ($site_sources as $site_source) {
$stack->pushSource($site_source);
// If the site source did anything which reads config, throw it away
// to make sure any additional site sources get clean reads.
self::dropConfigCache();
}
$masters = PhabricatorDatabaseRef::getMasterDatabaseRefs();
if (!$masters) {
self::setReadOnly(true, self::READONLY_MASTERLESS);
} else {
// If any master is severed, we drop to readonly mode. In theory we
// could try to continue if we're only missing some applications, but
// this is very complex and we're unlikely to get it right.
foreach ($masters as $master) {
// Give severed masters one last chance to get healthy.
if ($master->isSevered()) {
$master->checkHealth();
}
if ($master->isSevered()) {
self::setReadOnly(true, self::READONLY_SEVERED);
break;
}
}
}
try {
// See T13403. If we're starting up in "config optional" mode, suppress
// messages about connection retries.
if ($config_optional) {
$database_source = @new PhabricatorConfigDatabaseSource('default');
} else {
$database_source = new PhabricatorConfigDatabaseSource('default');
}
$database_source->setName(pht('Database'));
$stack->pushSource($database_source);
} catch (AphrontSchemaQueryException $exception) {
// If the database is not available, just skip this configuration
// source. This happens during `bin/storage upgrade`, `bin/conf` before
// schema setup, etc.
} catch (PhabricatorClusterStrandedException $ex) {
// This means we can't connect to any database host. That's fine as
// long as we're running a setup script like `bin/storage`.
if (!$config_optional) {
throw $ex;
}
}
// Drop the config cache one final time to make sure we're getting clean
// reads now that we've finished building the stack.
self::dropConfigCache();
}
public static function repairConfig($key, $value) {
if (!self::$repairSource) {
self::$repairSource = id(new PhabricatorConfigDictionarySource(array()))
->setName(pht('Repaired Config'));
self::$sourceStack->pushSource(self::$repairSource);
}
self::$repairSource->setKeys(array($key => $value));
self::dropConfigCache();
}
public static function overrideConfig($key, $value) {
if (!self::$overrideSource) {
self::$overrideSource = id(new PhabricatorConfigDictionarySource(array()))
->setName(pht('Overridden Config'));
self::$sourceStack->pushSource(self::$overrideSource);
}
self::$overrideSource->setKeys(array($key => $value));
self::dropConfigCache();
}
public static function getUnrepairedEnvConfig($key, $default = null) {
foreach (self::$sourceStack->getStack() as $source) {
if ($source === self::$repairSource) {
continue;
}
$result = $source->getKeys(array($key));
if ($result) {
return $result[$key];
}
}
return $default;
}
public static function getSelectedEnvironmentName() {
$env_var = 'PHABRICATOR_ENV';
$env = idx($_SERVER, $env_var);
if (!$env) {
$env = getenv($env_var);
}
if (!$env) {
$env = idx($_ENV, $env_var);
}
if (!$env) {
$root = dirname(phutil_get_library_root('phabricator'));
$path = $root.'/conf/local/ENVIRONMENT';
if (Filesystem::pathExists($path)) {
$env = trim(Filesystem::readFile($path));
}
}
return $env;
}
/* -( Reading Configuration )---------------------------------------------- */
/**
* Get the current configuration setting for a given key.
*
* If the key is not found, then throw an Exception.
*
* @task read
*/
public static function getEnvConfig($key) {
if (!self::$sourceStack) {
throw new Exception(
pht(
'Trying to read configuration "%s" before configuration has been '.
'initialized.',
$key));
}
if (isset(self::$cache[$key])) {
return self::$cache[$key];
}
if (array_key_exists($key, self::$cache)) {
return self::$cache[$key];
}
if (!self::$sourceStack) {
throw new Exception(
pht(
'Trying to read configuration "%s" before configuration has been '.
'initialized.',
$key));
}
$result = self::$sourceStack->getKeys(array($key));
if (array_key_exists($key, $result)) {
self::$cache[$key] = $result[$key];
return $result[$key];
} else {
throw new Exception(
pht(
"No config value specified for key '%s'.",
$key));
}
}
/**
* Get the current configuration setting for a given key. If the key
* does not exist, return a default value instead of throwing. This is
* primarily useful for migrations involving keys which are slated for
* removal.
*
* @task read
*/
public static function getEnvConfigIfExists($key, $default = null) {
try {
return self::getEnvConfig($key);
} catch (Exception $ex) {
return $default;
}
}
/**
* Get the fully-qualified URI for a path.
*
* @task read
*/
public static function getURI($path) {
return rtrim(self::getAnyBaseURI(), '/').$path;
}
/**
* Get the fully-qualified production URI for a path.
*
* @task read
*/
public static function getProductionURI($path) {
// If we're passed a URI which already has a domain, simply return it
// unmodified. In particular, files may have URIs which point to a CDN
// domain.
$uri = new PhutilURI($path);
if ($uri->getDomain()) {
return $path;
}
$production_domain = self::getEnvConfig('phabricator.production-uri');
if (!$production_domain) {
$production_domain = self::getAnyBaseURI();
}
return rtrim($production_domain, '/').$path;
}
public static function isSelfURI($raw_uri) {
$uri = new PhutilURI($raw_uri);
$host = $uri->getDomain();
if (!strlen($host)) {
return false;
}
$host = phutil_utf8_strtolower($host);
$self_map = self::getSelfURIMap();
return isset($self_map[$host]);
}
private static function getSelfURIMap() {
$self_uris = array();
$self_uris[] = self::getProductionURI('/');
$self_uris[] = self::getURI('/');
$allowed_uris = self::getEnvConfig('phabricator.allowed-uris');
foreach ($allowed_uris as $allowed_uri) {
$self_uris[] = $allowed_uri;
}
$self_map = array();
foreach ($self_uris as $self_uri) {
$host = id(new PhutilURI($self_uri))->getDomain();
if (!strlen($host)) {
continue;
}
$host = phutil_utf8_strtolower($host);
$self_map[$host] = $host;
}
return $self_map;
}
/**
* Get the fully-qualified production URI for a static resource path.
*
* @task read
*/
public static function getCDNURI($path) {
$alt = self::getEnvConfig('security.alternate-file-domain');
if (!$alt) {
$alt = self::getAnyBaseURI();
}
$uri = new PhutilURI($alt);
$uri->setPath($path);
return (string)$uri;
}
/**
* Get the fully-qualified production URI for a documentation resource.
*
* @task read
*/
public static function getDoclink($resource, $type = 'article') {
$params = array(
'name' => $resource,
'type' => $type,
'jump' => true,
);
$uri = new PhutilURI(
'https://secure.phabricator.com/diviner/find/',
$params);
return phutil_string_cast($uri);
}
/**
* Build a concrete object from a configuration key.
*
* @task read
*/
public static function newObjectFromConfig($key, $args = array()) {
$class = self::getEnvConfig($key);
return newv($class, $args);
}
public static function getAnyBaseURI() {
$base_uri = self::getEnvConfig('phabricator.base-uri');
if (!$base_uri) {
$base_uri = self::getRequestBaseURI();
}
if (!$base_uri) {
throw new Exception(
pht(
"Define '%s' in your configuration to continue.",
'phabricator.base-uri'));
}
return $base_uri;
}
public static function getRequestBaseURI() {
return self::$requestBaseURI;
}
public static function setRequestBaseURI($uri) {
self::$requestBaseURI = $uri;
}
public static function isReadOnly() {
if (self::$readOnly !== null) {
return self::$readOnly;
}
return self::getEnvConfig('cluster.read-only');
}
public static function setReadOnly($read_only, $reason) {
self::$readOnly = $read_only;
self::$readOnlyReason = $reason;
}
public static function getReadOnlyMessage() {
$reason = self::getReadOnlyReason();
switch ($reason) {
case self::READONLY_MASTERLESS:
return pht(
- 'Phabricator is in read-only mode (no writable database '.
+ 'This server is in read-only mode (no writable database '.
'is configured).');
case self::READONLY_UNREACHABLE:
return pht(
- 'Phabricator is in read-only mode (unreachable master).');
+ 'This server is in read-only mode (unreachable master).');
case self::READONLY_SEVERED:
return pht(
- 'Phabricator is in read-only mode (major interruption).');
+ 'This server is in read-only mode (major interruption).');
}
- return pht('Phabricator is in read-only mode.');
+ return pht('This server is in read-only mode.');
}
public static function getReadOnlyURI() {
return urisprintf(
'/readonly/%s/',
self::getReadOnlyReason());
}
public static function getReadOnlyReason() {
if (!self::isReadOnly()) {
return null;
}
if (self::$readOnlyReason !== null) {
return self::$readOnlyReason;
}
return self::READONLY_CONFIG;
}
/* -( Unit Test Support )-------------------------------------------------- */
/**
* @task test
*/
public static function beginScopedEnv() {
return new PhabricatorScopedEnv(self::pushTestEnvironment());
}
/**
* @task test
*/
private static function pushTestEnvironment() {
self::dropConfigCache();
$source = new PhabricatorConfigDictionarySource(array());
self::$sourceStack->pushSource($source);
return spl_object_hash($source);
}
/**
* @task test
*/
public static function popTestEnvironment($key) {
self::dropConfigCache();
$source = self::$sourceStack->popSource();
$stack_key = spl_object_hash($source);
if ($stack_key !== $key) {
self::$sourceStack->pushSource($source);
throw new Exception(
pht(
'Scoped environments were destroyed in a different order than they '.
'were initialized.'));
}
}
/* -( URI Validation )----------------------------------------------------- */
/**
* Detect if a URI satisfies either @{method:isValidLocalURIForLink} or
* @{method:isValidRemoteURIForLink}, i.e. is a page on this server or the
* URI of some other resource which has a valid protocol. This rejects
* garbage URIs and URIs with protocols which do not appear in the
* `uri.allowed-protocols` configuration, notably 'javascript:' URIs.
*
* NOTE: This method is generally intended to reject URIs which it may be
* unsafe to put in an "href" link attribute.
*
* @param string URI to test.
* @return bool True if the URI identifies a web resource.
* @task uri
*/
public static function isValidURIForLink($uri) {
return self::isValidLocalURIForLink($uri) ||
self::isValidRemoteURIForLink($uri);
}
/**
* Detect if a URI identifies some page on this server.
*
* NOTE: This method is generally intended to reject URIs which it may be
* unsafe to issue a "Location:" redirect to.
*
* @param string URI to test.
* @return bool True if the URI identifies a local page.
* @task uri
*/
public static function isValidLocalURIForLink($uri) {
$uri = (string)$uri;
if (!strlen($uri)) {
return false;
}
if (preg_match('/\s/', $uri)) {
// PHP hasn't been vulnerable to header injection attacks for a bunch of
// years, but we can safely reject these anyway since they're never valid.
return false;
}
// Chrome (at a minimum) interprets backslashes in Location headers and the
// URL bar as forward slashes. This is probably intended to reduce user
// error caused by confusion over which key is "forward slash" vs "back
// slash".
//
// However, it means a URI like "/\evil.com" is interpreted like
// "//evil.com", which is a protocol relative remote URI.
//
// Since we currently never generate URIs with backslashes in them, reject
// these unconditionally rather than trying to figure out how browsers will
// interpret them.
if (preg_match('/\\\\/', $uri)) {
return false;
}
// Valid URIs must begin with '/', followed by the end of the string or some
// other non-'/' character. This rejects protocol-relative URIs like
// "//evil.com/evil_stuff/".
return (bool)preg_match('@^/([^/]|$)@', $uri);
}
/**
* Detect if a URI identifies some valid linkable remote resource.
*
* @param string URI to test.
* @return bool True if a URI identifies a remote resource with an allowed
* protocol.
* @task uri
*/
public static function isValidRemoteURIForLink($uri) {
try {
self::requireValidRemoteURIForLink($uri);
return true;
} catch (Exception $ex) {
return false;
}
}
/**
* Detect if a URI identifies a valid linkable remote resource, throwing a
* detailed message if it does not.
*
* A valid linkable remote resource can be safely linked or redirected to.
* This is primarily a protocol whitelist check.
*
* @param string URI to test.
* @return void
* @task uri
*/
public static function requireValidRemoteURIForLink($raw_uri) {
$uri = new PhutilURI($raw_uri);
$proto = $uri->getProtocol();
if (!strlen($proto)) {
throw new Exception(
pht(
'URI "%s" is not a valid linkable resource. A valid linkable '.
'resource URI must specify a protocol.',
$raw_uri));
}
$protocols = self::getEnvConfig('uri.allowed-protocols');
if (!isset($protocols[$proto])) {
throw new Exception(
pht(
'URI "%s" is not a valid linkable resource. A valid linkable '.
'resource URI must use one of these protocols: %s.',
$raw_uri,
implode(', ', array_keys($protocols))));
}
$domain = $uri->getDomain();
if (!strlen($domain)) {
throw new Exception(
pht(
'URI "%s" is not a valid linkable resource. A valid linkable '.
'resource URI must specify a domain.',
$raw_uri));
}
}
/**
* Detect if a URI identifies a valid fetchable remote resource.
*
* @param string URI to test.
* @param list<string> Allowed protocols.
* @return bool True if the URI is a valid fetchable remote resource.
* @task uri
*/
public static function isValidRemoteURIForFetch($uri, array $protocols) {
try {
self::requireValidRemoteURIForFetch($uri, $protocols);
return true;
} catch (Exception $ex) {
return false;
}
}
/**
* Detect if a URI identifies a valid fetchable remote resource, throwing
* a detailed message if it does not.
*
* A valid fetchable remote resource can be safely fetched using a request
* originating on this server. This is a primarily an address check against
* the outbound address blacklist.
*
* @param string URI to test.
* @param list<string> Allowed protocols.
* @return pair<string, string> Pre-resolved URI and domain.
* @task uri
*/
public static function requireValidRemoteURIForFetch(
$raw_uri,
array $protocols) {
$uri = new PhutilURI($raw_uri);
$proto = $uri->getProtocol();
if (!strlen($proto)) {
throw new Exception(
pht(
'URI "%s" is not a valid fetchable resource. A valid fetchable '.
'resource URI must specify a protocol.',
$raw_uri));
}
$protocols = array_fuse($protocols);
if (!isset($protocols[$proto])) {
throw new Exception(
pht(
'URI "%s" is not a valid fetchable resource. A valid fetchable '.
'resource URI must use one of these protocols: %s.',
$raw_uri,
implode(', ', array_keys($protocols))));
}
$domain = $uri->getDomain();
if (!strlen($domain)) {
throw new Exception(
pht(
'URI "%s" is not a valid fetchable resource. A valid fetchable '.
'resource URI must specify a domain.',
$raw_uri));
}
$addresses = gethostbynamel($domain);
if (!$addresses) {
throw new Exception(
pht(
'URI "%s" is not a valid fetchable resource. The domain "%s" could '.
'not be resolved.',
$raw_uri,
$domain));
}
foreach ($addresses as $address) {
if (self::isBlacklistedOutboundAddress($address)) {
throw new Exception(
pht(
'URI "%s" is not a valid fetchable resource. The domain "%s" '.
'resolves to the address "%s", which is blacklisted for '.
'outbound requests.',
$raw_uri,
$domain,
$address));
}
}
$resolved_uri = clone $uri;
$resolved_uri->setDomain(head($addresses));
return array($resolved_uri, $domain);
}
/**
* Determine if an IP address is in the outbound address blacklist.
*
* @param string IP address.
* @return bool True if the address is blacklisted.
*/
public static function isBlacklistedOutboundAddress($address) {
$blacklist = self::getEnvConfig('security.outbound-blacklist');
return PhutilCIDRList::newList($blacklist)->containsAddress($address);
}
public static function isClusterRemoteAddress() {
$cluster_addresses = self::getEnvConfig('cluster.addresses');
if (!$cluster_addresses) {
return false;
}
$address = self::getRemoteAddress();
if (!$address) {
throw new Exception(
pht(
'Unable to test remote address against cluster whitelist: '.
'REMOTE_ADDR is not defined or not valid.'));
}
return self::isClusterAddress($address);
}
public static function isClusterAddress($address) {
$cluster_addresses = self::getEnvConfig('cluster.addresses');
if (!$cluster_addresses) {
throw new Exception(
pht(
- 'Phabricator is not configured to serve cluster requests. '.
+ 'This server is not configured to serve cluster requests. '.
'Set `cluster.addresses` in the configuration to whitelist '.
'cluster hosts before sending requests that use a cluster '.
'authentication mechanism.'));
}
return PhutilCIDRList::newList($cluster_addresses)
->containsAddress($address);
}
public static function getRemoteAddress() {
$address = idx($_SERVER, 'REMOTE_ADDR');
if (!$address) {
return null;
}
try {
return PhutilIPAddress::newAddress($address);
} catch (Exception $ex) {
return null;
}
}
/* -( Internals )---------------------------------------------------------- */
/**
* @task internal
*/
public static function envConfigExists($key) {
return array_key_exists($key, self::$sourceStack->getKeys(array($key)));
}
/**
* @task internal
*/
public static function getAllConfigKeys() {
return self::$sourceStack->getAllKeys();
}
public static function getConfigSourceStack() {
return self::$sourceStack;
}
/**
* @task internal
*/
public static function overrideTestEnvConfig($stack_key, $key, $value) {
$tmp = array();
// If we don't have the right key, we'll throw when popping the last
// source off the stack.
do {
$source = self::$sourceStack->popSource();
array_unshift($tmp, $source);
if (spl_object_hash($source) == $stack_key) {
$source->setKeys(array($key => $value));
break;
}
} while (true);
foreach ($tmp as $source) {
self::$sourceStack->pushSource($source);
}
self::dropConfigCache();
}
private static function dropConfigCache() {
self::$cache = array();
}
private static function resetUmask() {
// Reset the umask to the common standard umask. The umask controls default
// permissions when files are created and propagates to subprocesses.
// "022" is the most common umask, but sometimes it is set to something
// unusual by the calling environment.
// Since various things rely on this umask to work properly and we are
// not aware of any legitimate reasons to adjust it, unconditionally
// normalize it until such reasons arise. See T7475 for discussion.
umask(022);
}
/**
* Get the path to an empty directory which is readable by all of the system
* user accounts that Phabricator acts as.
*
* In some cases, a binary needs some valid HOME or CWD to continue, but not
* all user accounts have valid home directories and even if they do they
* may not be readable after a `sudo` operation.
*
* @return string Path to an empty directory suitable for use as a CWD.
*/
public static function getEmptyCWD() {
$root = dirname(phutil_get_library_root('phabricator'));
return $root.'/support/empty/';
}
}
diff --git a/src/infrastructure/export/format/PhabricatorExcelExportFormat.php b/src/infrastructure/export/format/PhabricatorExcelExportFormat.php
index e7135bd9db..dd33813384 100644
--- a/src/infrastructure/export/format/PhabricatorExcelExportFormat.php
+++ b/src/infrastructure/export/format/PhabricatorExcelExportFormat.php
@@ -1,183 +1,182 @@
<?php
final class PhabricatorExcelExportFormat
extends PhabricatorExportFormat {
const EXPORTKEY = 'excel';
private $workbook;
private $sheet;
private $rowCursor;
public function getExportFormatName() {
return pht('Excel (.xlsx)');
}
public function isExportFormatEnabled() {
if (!extension_loaded('zip')) {
return false;
}
return @include_once 'PHPExcel.php';
}
public function getInstallInstructions() {
if (!extension_loaded('zip')) {
return pht(<<<EOHELP
Data can not be exported to Excel because the "zip" PHP extension is not
installed. Consult the setup issue in the Config application for guidance on
installing the extension.
EOHELP
);
}
return pht(<<<EOHELP
Data can not be exported to Excel because the PHPExcel library is not
-installed. This software component is required for Phabricator to create
-Excel files.
+installed. This software component is required to create Excel files.
You can install PHPExcel from GitHub:
> https://github.com/PHPOffice/PHPExcel
Briefly:
- Clone that repository somewhere on the sever
(like `/path/to/example/PHPExcel`).
- Update your PHP `%s` setting (in `php.ini`) to include the PHPExcel
`Classes` directory (like `/path/to/example/PHPExcel/Classes`).
EOHELP
,
'include_path');
}
public function getFileExtension() {
return 'xlsx';
}
public function getMIMEContentType() {
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
}
/**
* @phutil-external-symbol class PHPExcel_Cell_DataType
*/
public function addHeaders(array $fields) {
$sheet = $this->getSheet();
$header_format = array(
'font' => array(
'bold' => true,
),
);
$row = 1;
$col = 0;
foreach ($fields as $field) {
$cell_value = $field->getLabel();
$cell_name = $this->getCellName($col, $row);
$cell = $sheet->setCellValue(
$cell_name,
$cell_value,
$return_cell = true);
$sheet->getStyle($cell_name)->applyFromArray($header_format);
$cell->setDataType(PHPExcel_Cell_DataType::TYPE_STRING);
$width = $field->getCharacterWidth();
if ($width !== null) {
$col_name = $this->getCellName($col);
$sheet->getColumnDimension($col_name)
->setWidth($width);
}
$col++;
}
}
public function addObject($object, array $fields, array $map) {
$sheet = $this->getSheet();
$col = 0;
foreach ($fields as $key => $field) {
$cell_value = $map[$key];
$cell_value = $field->getPHPExcelValue($cell_value);
$cell_name = $this->getCellName($col, $this->rowCursor);
$cell = $sheet->setCellValue(
$cell_name,
$cell_value,
$return_cell = true);
$style = $sheet->getStyle($cell_name);
$field->formatPHPExcelCell($cell, $style);
$col++;
}
$this->rowCursor++;
}
/**
* @phutil-external-symbol class PHPExcel_IOFactory
*/
public function newFileData() {
$workbook = $this->getWorkbook();
$writer = PHPExcel_IOFactory::createWriter($workbook, 'Excel2007');
ob_start();
$writer->save('php://output');
$data = ob_get_clean();
return $data;
}
private function getWorkbook() {
if (!$this->workbook) {
$this->workbook = $this->newWorkbook();
}
return $this->workbook;
}
/**
* @phutil-external-symbol class PHPExcel
*/
private function newWorkbook() {
include_once 'PHPExcel.php';
return new PHPExcel();
}
private function getSheet() {
if (!$this->sheet) {
$workbook = $this->getWorkbook();
$sheet = $workbook->setActiveSheetIndex(0);
$sheet->setTitle($this->getTitle());
$this->sheet = $sheet;
// The row cursor starts on the second row, after the header row.
$this->rowCursor = 2;
}
return $this->sheet;
}
/**
* @phutil-external-symbol class PHPExcel_Cell
*/
private function getCellName($col, $row = null) {
$col_name = PHPExcel_Cell::stringFromColumnIndex($col);
if ($row === null) {
return $col_name;
}
return $col_name.$row;
}
}
diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php
index 280a34e7b6..7b9a4c27e1 100644
--- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php
+++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php
@@ -1,1795 +1,1816 @@
<?php
final class PhabricatorUSEnglishTranslation
extends PhutilTranslation {
public function getLocaleCode() {
return 'en_US';
}
protected function getTranslations() {
return array(
'These %d configuration value(s) are related:' => array(
'This configuration value is related:',
'These configuration values are related:',
),
'%s Task(s)' => array('Task', 'Tasks'),
'%s ERROR(S)' => array('ERROR', 'ERRORS'),
'%d Error(s)' => array('%d Error', '%d Errors'),
'%d Warning(s)' => array('%d Warning', '%d Warnings'),
'%d Auto-Fix(es)' => array('%d Auto-Fix', '%d Auto-Fixes'),
'%d Advice(s)' => array('%d Advice', '%d Pieces of Advice'),
'%d Detail(s)' => array('%d Detail', '%d Details'),
'(%d line(s))' => array('(%d line)', '(%d lines)'),
'%d line(s)' => array('%d line', '%d lines'),
'%d path(s)' => array('%d path', '%d paths'),
'%d diff(s)' => array('%d diff', '%d diffs'),
'%s Answer(s)' => array('%s Answer', '%s Answers'),
'Show %d Comment(s)' => array('Show %d Comment', 'Show %d Comments'),
'%s DIFF LINK(S)' => array('DIFF LINK', 'DIFF LINKS'),
'You successfully created %d diff(s).' => array(
'You successfully created %d diff.',
'You successfully created %d diffs.',
),
'Diff creation failed; see body for %s error(s).' => array(
'Diff creation failed; see body for error.',
'Diff creation failed; see body for errors.',
),
'There are %d raw fact(s) in storage.' => array(
'There is %d raw fact in storage.',
'There are %d raw facts in storage.',
),
'There are %d aggregate fact(s) in storage.' => array(
'There is %d aggregate fact in storage.',
'There are %d aggregate facts in storage.',
),
'%s Commit(s) Awaiting Audit' => array(
'%s Commit Awaiting Audit',
'%s Commits Awaiting Audit',
),
'%s Problem Commit(s)' => array(
'%s Problem Commit',
'%s Problem Commits',
),
'%s Review(s) Blocking Others' => array(
'%s Review Blocking Others',
'%s Reviews Blocking Others',
),
'%s Review(s) Need Attention' => array(
'%s Review Needs Attention',
'%s Reviews Need Attention',
),
'%s Review(s) Waiting on Others' => array(
'%s Review Waiting on Others',
'%s Reviews Waiting on Others',
),
'%s Active Review(s)' => array(
'%s Active Review',
'%s Active Reviews',
),
'%s Flagged Object(s)' => array(
'%s Flagged Object',
'%s Flagged Objects',
),
'%s Object(s) Tracked' => array(
'%s Object Tracked',
'%s Objects Tracked',
),
'%s Assigned Task(s)' => array(
'%s Assigned Task',
'%s Assigned Tasks',
),
'Show %d Lint Message(s)' => array(
'Show %d Lint Message',
'Show %d Lint Messages',
),
'Hide %d Lint Message(s)' => array(
'Hide %d Lint Message',
'Hide %d Lint Messages',
),
'This is a binary file. It is %s byte(s) in length.' => array(
'This is a binary file. It is %s byte in length.',
'This is a binary file. It is %s bytes in length.',
),
'%s Action(s) Have No Effect' => array(
'Action Has No Effect',
'Actions Have No Effect',
),
'%s Action(s) With No Effect' => array(
'Action With No Effect',
'Actions With No Effect',
),
'Some of your %s action(s) have no effect:' => array(
'One of your actions has no effect:',
'Some of your actions have no effect:',
),
'Apply remaining %d action(s)?' => array(
'Apply remaining action?',
'Apply remaining actions?',
),
'Apply %d Other Action(s)' => array(
'Apply Remaining Action',
'Apply Remaining Actions',
),
'The %s action(s) you are taking have no effect:' => array(
'The action you are taking has no effect:',
'The actions you are taking have no effect:',
),
'%s edited member(s), added %d: %s; removed %d: %s.' =>
'%s edited members, added: %3$s; removed: %5$s.',
'%s added %s member(s): %s.' => array(
array(
'%s added a member: %3$s.',
'%s added members: %3$s.',
),
),
'%s removed %s member(s): %s.' => array(
array(
'%s removed a member: %3$s.',
'%s removed members: %3$s.',
),
),
'%s edited project(s), added %s: %s; removed %s: %s.' =>
'%s edited projects, added: %3$s; removed: %5$s.',
'%s added %s project(s): %s.' => array(
array(
'%s added a project: %3$s.',
'%s added projects: %3$s.',
),
),
'%s removed %s project(s): %s.' => array(
array(
'%s removed a project: %3$s.',
'%s removed projects: %3$s.',
),
),
'%s merged %s task(s): %s.' => array(
array(
'%s merged a task: %3$s.',
'%s merged tasks: %3$s.',
),
),
'%s merged %s task(s) %s into %s.' => array(
array(
'%s merged %3$s into %4$s.',
'%s merged tasks %3$s into %4$s.',
),
),
'%s added %s voting user(s): %s.' => array(
array(
'%s added a voting user: %3$s.',
'%s added voting users: %3$s.',
),
),
'%s removed %s voting user(s): %s.' => array(
array(
'%s removed a voting user: %3$s.',
'%s removed voting users: %3$s.',
),
),
'%s added %s subtask(s): %s.' => array(
array(
'%s added a subtask: %3$s.',
'%s added subtasks: %3$s.',
),
),
'%s added %s parent task(s): %s.' => array(
array(
'%s added a parent task: %3$s.',
'%s added parent tasks: %3$s.',
),
),
'%s removed %s subtask(s): %s.' => array(
array(
'%s removed a subtask: %3$s.',
'%s removed subtasks: %3$s.',
),
),
'%s removed %s parent task(s): %s.' => array(
array(
'%s removed a parent task: %3$s.',
'%s removed parent tasks: %3$s.',
),
),
'%s added %s subtask(s) for %s: %s.' => array(
array(
'%s added a subtask for %3$s: %4$s.',
'%s added subtasks for %3$s: %4$s.',
),
),
'%s added %s parent task(s) for %s: %s.' => array(
array(
'%s added a parent task for %3$s: %4$s.',
'%s added parent tasks for %3$s: %4$s.',
),
),
'%s removed %s subtask(s) for %s: %s.' => array(
array(
'%s removed a subtask for %3$s: %4$s.',
'%s removed subtasks for %3$s: %4$s.',
),
),
'%s removed %s parent task(s) for %s: %s.' => array(
array(
'%s removed a parent task for %3$s: %4$s.',
'%s removed parent tasks for %3$s: %4$s.',
),
),
'%s edited subtask(s), added %s: %s; removed %s: %s.' =>
'%s edited subtasks, added: %3$s; removed: %5$s.',
'%s edited subtask(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited subtasks for %s, added: %4$s; removed: %6$s.',
'%s edited parent task(s), added %s: %s; removed %s: %s.' =>
'%s edited parent tasks, added: %3$s; removed: %5$s.',
'%s edited parent task(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited parent tasks for %s, added: %4$s; removed: %6$s.',
'%s edited answer(s), added %s: %s; removed %d: %s.' =>
'%s edited answers, added: %3$s; removed: %5$s.',
'%s added %s answer(s): %s.' => array(
array(
'%s added an answer: %3$s.',
'%s added answers: %3$s.',
),
),
'%s removed %s answer(s): %s.' => array(
array(
'%s removed a answer: %3$s.',
'%s removed answers: %3$s.',
),
),
'%s edited question(s), added %s: %s; removed %s: %s.' =>
'%s edited questions, added: %3$s; removed: %5$s.',
'%s added %s question(s): %s.' => array(
array(
'%s added a question: %3$s.',
'%s added questions: %3$s.',
),
),
'%s removed %s question(s): %s.' => array(
array(
'%s removed a question: %3$s.',
'%s removed questions: %3$s.',
),
),
'%s edited mock(s), added %s: %s; removed %s: %s.' =>
'%s edited mocks, added: %3$s; removed: %5$s.',
'%s added %s mock(s): %s.' => array(
array(
'%s added a mock: %3$s.',
'%s added mocks: %3$s.',
),
),
'%s removed %s mock(s): %s.' => array(
array(
'%s removed a mock: %3$s.',
'%s removed mocks: %3$s.',
),
),
'%s added %s task(s): %s.' => array(
array(
'%s added a task: %3$s.',
'%s added tasks: %3$s.',
),
),
'%s removed %s task(s): %s.' => array(
array(
'%s removed a task: %3$s.',
'%s removed tasks: %3$s.',
),
),
'%s edited file(s), added %s: %s; removed %s: %s.' =>
'%s edited files, added: %3$s; removed: %5$s.',
'%s added %s file(s): %s.' => array(
array(
'%s added a file: %3$s.',
'%s added files: %3$s.',
),
),
'%s removed %s file(s): %s.' => array(
array(
'%s removed a file: %3$s.',
'%s removed files: %3$s.',
),
),
'%s edited contributor(s), added %s: %s; removed %s: %s.' =>
'%s edited contributors, added: %3$s; removed: %5$s.',
'%s added %s contributor(s): %s.' => array(
array(
'%s added a contributor: %3$s.',
'%s added contributors: %3$s.',
),
),
'%s removed %s contributor(s): %s.' => array(
array(
'%s removed a contributor: %3$s.',
'%s removed contributors: %3$s.',
),
),
'%s edited %s reviewer(s), added %s: %s; removed %s: %s.' =>
'%s edited reviewers, added: %4$s; removed: %6$s.',
'%s edited %s reviewer(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited reviewers for %3$s, added: %5$s; removed: %7$s.',
'%s added %s reviewer(s): %s.' => array(
array(
'%s added a reviewer: %3$s.',
'%s added reviewers: %3$s.',
),
),
'%s added %s reviewer(s) for %s: %s.' => array(
array(
'%s added a reviewer for %3$s: %4$s.',
'%s added reviewers for %3$s: %4$s.',
),
),
'%s removed %s reviewer(s): %s.' => array(
array(
'%s removed a reviewer: %3$s.',
'%s removed reviewers: %3$s.',
),
),
'%s removed %s reviewer(s) for %s: %s.' => array(
array(
'%s removed a reviewer for %3$s: %4$s.',
'%s removed reviewers for %3$s: %4$s.',
),
),
'%d other(s)' => array(
'1 other',
'%d others',
),
'%s edited subscriber(s), added %d: %s; removed %d: %s.' =>
'%s edited subscribers, added: %3$s; removed: %5$s.',
'%s added %d subscriber(s): %s.' => array(
array(
'%s added a subscriber: %3$s.',
'%s added subscribers: %3$s.',
),
),
'%s removed %d subscriber(s): %s.' => array(
array(
'%s removed a subscriber: %3$s.',
'%s removed subscribers: %3$s.',
),
),
'%s edited watcher(s), added %s: %s; removed %d: %s.' =>
'%s edited watchers, added: %3$s; removed: %5$s.',
'%s added %s watcher(s): %s.' => array(
array(
'%s added a watcher: %3$s.',
'%s added watchers: %3$s.',
),
),
'%s removed %s watcher(s): %s.' => array(
array(
'%s removed a watcher: %3$s.',
'%s removed watchers: %3$s.',
),
),
'%s edited participant(s), added %d: %s; removed %d: %s.' =>
'%s edited participants, added: %3$s; removed: %5$s.',
'%s added %d participant(s): %s.' => array(
array(
'%s added a participant: %3$s.',
'%s added participants: %3$s.',
),
),
'%s removed %d participant(s): %s.' => array(
array(
'%s removed a participant: %3$s.',
'%s removed participants: %3$s.',
),
),
'%s edited image(s), added %d: %s; removed %d: %s.' =>
'%s edited images, added: %3$s; removed: %5$s',
'%s added %d image(s): %s.' => array(
array(
'%s added an image: %3$s.',
'%s added images: %3$s.',
),
),
'%s removed %d image(s): %s.' => array(
array(
'%s removed an image: %3$s.',
'%s removed images: %3$s.',
),
),
'%s Line(s)' => array(
'%s Line',
'%s Lines',
),
'Indexing %d object(s) of type %s.' => array(
'Indexing %d object of type %s.',
'Indexing %d object of type %s.',
),
'Run these %d command(s):' => array(
'Run this command:',
'Run these commands:',
),
'Install these %d PHP extension(s):' => array(
'Install this PHP extension:',
'Install these PHP extensions:',
),
'The current Phabricator configuration has these %d value(s):' => array(
'The current Phabricator configuration has this value:',
'The current Phabricator configuration has these values:',
),
'The current MySQL configuration has these %d value(s):' => array(
'The current MySQL configuration has this value:',
'The current MySQL configuration has these values:',
),
'You can update these %d value(s) here:' => array(
'You can update this value here:',
'You can update these values here:',
),
'The current PHP configuration has these %d value(s):' => array(
'The current PHP configuration has this value:',
'The current PHP configuration has these values:',
),
'To update these %d value(s), edit your PHP configuration file.' => array(
'To update this %d value, edit your PHP configuration file.',
'To update these %d values, edit your PHP configuration file.',
),
'To update these %d value(s), edit your PHP configuration file, located '.
'here:' => array(
'To update this value, edit your PHP configuration file, located '.
'here:',
'To update these values, edit your PHP configuration file, located '.
'here:',
),
'PHP also loaded these %s configuration file(s):' => array(
'PHP also loaded this configuration file:',
'PHP also loaded these configuration files:',
),
'%s added %d inline comment(s).' => array(
array(
'%s added an inline comment.',
'%s added inline comments.',
),
),
'%s comment(s)' => array('%s comment', '%s comments'),
'%s rejection(s)' => array('%s rejection', '%s rejections'),
'%s update(s)' => array('%s update', '%s updates'),
'This configuration value is defined in these %d '.
'configuration source(s): %s.' => array(
'This configuration value is defined in this '.
'configuration source: %2$s.',
'This configuration value is defined in these %d '.
'configuration sources: %s.',
),
'%s Open Pull Request(s)' => array(
'%s Open Pull Request',
'%s Open Pull Requests',
),
'Stale (%s day(s))' => array(
'Stale (%s day)',
'Stale (%s days)',
),
'Old (%s day(s))' => array(
'Old (%s day)',
'Old (%s days)',
),
'%s Commit(s)' => array(
'%s Commit',
'%s Commits',
),
'%s attached %d file(s): %s.' => array(
array(
'%s attached a file: %3$s.',
'%s attached files: %3$s.',
),
),
'%s detached %d file(s): %s.' => array(
array(
'%s detached a file: %3$s.',
'%s detached files: %3$s.',
),
),
'%s changed file(s), attached %d: %s; detached %d: %s.' =>
'%s changed files, attached: %3$s; detached: %5$s.',
'%s added %s parent revision(s): %s.' => array(
array(
'%s added a parent revision: %3$s.',
'%s added parent revisions: %3$s.',
),
),
'%s added %s parent revision(s) for %s: %s.' => array(
array(
'%s added a parent revision for %3$s: %4$s.',
'%s added parent revisions for %3$s: %4$s.',
),
),
'%s removed %s parent revision(s): %s.' => array(
array(
'%s removed a parent revision: %3$s.',
'%s removed parent revisions: %3$s.',
),
),
'%s removed %s parent revision(s) for %s: %s.' => array(
array(
'%s removed a parent revision for %3$s: %4$s.',
'%s removed parent revisions for %3$s: %4$s.',
),
),
'%s edited parent revision(s), added %s: %s; removed %s: %s.' => array(
'%s edited parent revisions, added: %3$s; removed: %5$s.',
),
'%s edited parent revision(s) for %s, '.
'added %s: %s; removed %s: %s.' => array(
'%s edited parent revisions for %s, added: %3$s; removed: %5$s.',
),
'%s added %s child revision(s): %s.' => array(
array(
'%s added a child revision: %3$s.',
'%s added child revisions: %3$s.',
),
),
'%s added %s child revision(s) for %s: %s.' => array(
array(
'%s added a child revision for %3$s: %4$s.',
'%s added child revisions for %3$s: %4$s.',
),
),
'%s removed %s child revision(s): %s.' => array(
array(
'%s removed a child revision: %3$s.',
'%s removed child revisions: %3$s.',
),
),
'%s removed %s child revision(s) for %s: %s.' => array(
array(
'%s removed a child revision for %3$s: %4$s.',
'%s removed child revisions for %3$s: %4$s.',
),
),
'%s edited child revision(s), added %s: %s; removed %s: %s.' => array(
'%s edited child revisions, added: %3$s; removed: %5$s.',
),
'%s edited child revision(s) for %s, '.
'added %s: %s; removed %s: %s.' => array(
'%s edited child revisions for %s, added: %3$s; removed: %5$s.',
),
'%s added %s commit(s): %s.' => array(
array(
'%s added a commit: %3$s.',
'%s added commits: %3$s.',
),
),
'%s removed %s commit(s): %s.' => array(
array(
'%s removed a commit: %3$s.',
'%s removed commits: %3$s.',
),
),
'%s edited commit(s), added %s: %s; removed %s: %s.' =>
'%s edited commits, added %3$s; removed %5$s.',
'%s added %s reverted change(s): %s.' => array(
array(
'%s added a reverted change: %3$s.',
'%s added reverted changes: %3$s.',
),
),
'%s removed %s reverted change(s): %s.' => array(
array(
'%s removed a reverted change: %3$s.',
'%s removed reverted changes: %3$s.',
),
),
'%s edited reverted change(s), added %s: %s; removed %s: %s.' =>
'%s edited reverted changes, added %3$s; removed %5$s.',
'%s added %s reverted change(s) for %s: %s.' => array(
array(
'%s added a reverted change for %3$s: %4$s.',
'%s added reverted changes for %3$s: %4$s.',
),
),
'%s removed %s reverted change(s) for %s: %s.' => array(
array(
'%s removed a reverted change for %3$s: %4$s.',
'%s removed reverted changes for %3$s: %4$s.',
),
),
'%s edited reverted change(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited reverted changes for %2$s, added %4$s; removed %6$s.',
'%s added %s reverting change(s): %s.' => array(
array(
'%s added a reverting change: %3$s.',
'%s added reverting changes: %3$s.',
),
),
'%s removed %s reverting change(s): %s.' => array(
array(
'%s removed a reverting change: %3$s.',
'%s removed reverting changes: %3$s.',
),
),
'%s edited reverting change(s), added %s: %s; removed %s: %s.' =>
'%s edited reverting changes, added %3$s; removed %5$s.',
'%s added %s reverting change(s) for %s: %s.' => array(
array(
'%s added a reverting change for %3$s: %4$s.',
'%s added reverting changes for %3$s: %4$s.',
),
),
'%s removed %s reverting change(s) for %s: %s.' => array(
array(
'%s removed a reverting change for %3$s: %4$s.',
'%s removed reverting changes for %3$s: %4$s.',
),
),
'%s edited reverting change(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited reverting changes for %s, added %4$s; removed %6$s.',
'%s changed project member(s), added %d: %s; removed %d: %s.' =>
'%s changed project members, added %3$s; removed %5$s.',
'%s added %d project member(s): %s.' => array(
array(
'%s added a member: %3$s.',
'%s added members: %3$s.',
),
),
'%s removed %d project member(s): %s.' => array(
array(
'%s removed a member: %3$s.',
'%s removed members: %3$s.',
),
),
'%s project hashtag(s) are already used by other projects: %s.' => array(
'Project hashtag "%2$s" is already used by another project.',
'Some project hashtags are already used by other projects: %2$s.',
),
'%s changed project hashtag(s), added %d: %s; removed %d: %s.' =>
'%s changed project hashtags, added %3$s; removed %5$s.',
'Hashtags must contain at least one letter or number. %s '.
'project hashtag(s) are invalid: %s.' => array(
'Hashtags must contain at least one letter or number. The '.
'hashtag "%2$s" is not valid.',
'Hashtags must contain at least one letter or number. These '.
'hashtags are invalid: %2$s.',
),
'%s added %d project hashtag(s): %s.' => array(
array(
'%s added a hashtag: %3$s.',
'%s added hashtags: %3$s.',
),
),
'%s removed %d project hashtag(s): %s.' => array(
array(
'%s removed a hashtag: %3$s.',
'%s removed hashtags: %3$s.',
),
),
'%s changed %s hashtag(s), added %d: %s; removed %d: %s.' =>
'%s changed hashtags for %s, added %4$s; removed %6$s.',
'%s added %d %s hashtag(s): %s.' => array(
array(
'%s added a hashtag to %3$s: %4$s.',
'%s added hashtags to %3$s: %4$s.',
),
),
'%s removed %d %s hashtag(s): %s.' => array(
array(
'%s removed a hashtag from %3$s: %4$s.',
'%s removed hashtags from %3$s: %4$s.',
),
),
'%d User(s) Need Approval' => array(
'%d User Needs Approval',
'%d Users Need Approval',
),
'%s, %s line(s)' => array(
array(
'%s, %s line',
'%s, %s lines',
),
),
'%s pushed %d commit(s) to %s.' => array(
array(
'%s pushed a commit to %3$s.',
'%s pushed %d commits to %s.',
),
),
'%s commit(s)' => array(
'1 commit',
'%s commits',
),
'%s removed %s JIRA issue(s): %s.' => array(
array(
'%s removed a JIRA issue: %3$s.',
'%s removed JIRA issues: %3$s.',
),
),
'%s added %s JIRA issue(s): %s.' => array(
array(
'%s added a JIRA issue: %3$s.',
'%s added JIRA issues: %3$s.',
),
),
'%s added %s required legal document(s): %s.' => array(
array(
'%s added a required legal document: %3$s.',
'%s added required legal documents: %3$s.',
),
),
'%s updated JIRA issue(s): added %s %s; removed %d %s.' =>
'%s updated JIRA issues: added %3$s; removed %5$s.',
'%s edited %s task(s), added %s: %s; removed %s: %s.' =>
'%s edited tasks, added %4$s; removed %6$s.',
'%s added %s task(s) to %s: %s.' => array(
array(
'%s added a task to %3$s: %4$s.',
'%s added tasks to %3$s: %4$s.',
),
),
'%s removed %s task(s) from %s: %s.' => array(
array(
'%s removed a task from %3$s: %4$s.',
'%s removed tasks from %3$s: %4$s.',
),
),
'%s edited %s task(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited tasks for %3$s, added: %5$s; removed %7$s.',
'%s edited %s commit(s), added %s: %s; removed %s: %s.' =>
'%s edited commits, added %4$s; removed %6$s.',
'%s added %s commit(s) to %s: %s.' => array(
array(
'%s added a commit to %3$s: %4$s.',
'%s added commits to %3$s: %4$s.',
),
),
'%s removed %s commit(s) from %s: %s.' => array(
array(
'%s removed a commit from %3$s: %4$s.',
'%s removed commits from %3$s: %4$s.',
),
),
'%s edited %s commit(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited commits for %3$s, added: %5$s; removed %7$s.',
'%s added %s revision(s): %s.' => array(
array(
'%s added a revision: %3$s.',
'%s added revisions: %3$s.',
),
),
'%s removed %s revision(s): %s.' => array(
array(
'%s removed a revision: %3$s.',
'%s removed revisions: %3$s.',
),
),
'%s edited %s revision(s), added %s: %s; removed %s: %s.' =>
'%s edited revisions, added %4$s; removed %6$s.',
'%s added %s revision(s) to %s: %s.' => array(
array(
'%s added a revision to %3$s: %4$s.',
'%s added revisions to %3$s: %4$s.',
),
),
'%s removed %s revision(s) from %s: %s.' => array(
array(
'%s removed a revision from %3$s: %4$s.',
'%s removed revisions from %3$s: %4$s.',
),
),
'%s edited %s revision(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited revisions for %3$s, added: %5$s; removed %7$s.',
'%s edited %s project(s), added %s: %s; removed %s: %s.' =>
'%s edited projects, added %4$s; removed %6$s.',
'%s added %s project(s) to %s: %s.' => array(
array(
'%s added a project to %3$s: %4$s.',
'%s added projects to %3$s: %4$s.',
),
),
'%s removed %s project(s) from %s: %s.' => array(
array(
'%s removed a project from %3$s: %4$s.',
'%s removed projects from %3$s: %4$s.',
),
),
'%s edited %s project(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited projects for %3$s, added: %5$s; removed %7$s.',
'%s added %s panel(s): %s.' => array(
array(
'%s added a panel: %3$s.',
'%s added panels: %3$s.',
),
),
'%s removed %s panel(s): %s.' => array(
array(
'%s removed a panel: %3$s.',
'%s removed panels: %3$s.',
),
),
'%s edited %s panel(s), added %s: %s; removed %s: %s.' =>
'%s edited panels, added %4$s; removed %6$s.',
'%s added %s dashboard(s): %s.' => array(
array(
'%s added a dashboard: %3$s.',
'%s added dashboards: %3$s.',
),
),
'%s removed %s dashboard(s): %s.' => array(
array(
'%s removed a dashboard: %3$s.',
'%s removed dashboards: %3$s.',
),
),
'%s edited %s dashboard(s), added %s: %s; removed %s: %s.' =>
'%s edited dashboards, added %4$s; removed %6$s.',
'%s added %s edge(s): %s.' => array(
array(
'%s added an edge: %3$s.',
'%s added edges: %3$s.',
),
),
'%s added %s edge(s) to %s: %s.' => array(
array(
'%s added an edge to %3$s: %4$s.',
'%s added edges to %3$s: %4$s.',
),
),
'%s removed %s edge(s): %s.' => array(
array(
'%s removed an edge: %3$s.',
'%s removed edges: %3$s.',
),
),
'%s removed %s edge(s) from %s: %s.' => array(
array(
'%s removed an edge from %3$s: %4$s.',
'%s removed edges from %3$s: %4$s.',
),
),
'%s edited edge(s), added %s: %s; removed %s: %s.' =>
'%s edited edges, added: %3$s; removed: %5$s.',
'%s edited %s edge(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited edges for %3$s, added: %5$s; removed %7$s.',
'%s added %s member(s) for %s: %s.' => array(
array(
'%s added a member for %3$s: %4$s.',
'%s added members for %3$s: %4$s.',
),
),
'%s removed %s member(s) for %s: %s.' => array(
array(
'%s removed a member for %3$s: %4$s.',
'%s removed members for %3$s: %4$s.',
),
),
'%s edited %s member(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited members for %3$s, added: %5$s; removed %7$s.',
'%d related link(s):' => array(
'Related link:',
'Related links:',
),
'You have %d unpaid invoice(s).' => array(
'You have an unpaid invoice.',
'You have unpaid invoices.',
),
'The configurations differ in the following %s way(s):' => array(
'The configurations differ:',
'The configurations differ in these ways:',
),
'Phabricator is configured with an email domain whitelist (in %s), so '.
'only users with a verified email address at one of these %s '.
'allowed domain(s) will be able to register an account: %s' => array(
array(
'Phabricator is configured with an email domain whitelist (in %s), '.
'so only users with a verified email address at %3$s will be '.
'allowed to register an account.',
'Phabricator is configured with an email domain whitelist (in %s), '.
'so only users with a verified email address at one of these '.
'allowed domains will be able to register an account: %3$s',
),
),
'Show First %s Line(s)' => array(
'Show First Line',
'Show First %s Lines',
),
'Show First %s Block(s)' => array(
'Show First Block',
'Show First %s Blocks',
),
"\xE2\x96\xB2 Show %s Line(s)" => array(
"\xE2\x96\xB2 Show Line",
"\xE2\x96\xB2 Show %s Lines",
),
"\xE2\x96\xB2 Show %s Block(s)" => array(
"\xE2\x96\xB2 Show Block",
"\xE2\x96\xB2 Show %s Blocks",
),
'Show All %s Line(s)' => array(
'Show Line',
'Show All %s Lines',
),
'Show All %s Block(s)' => array(
'Show Block',
'Show All %s Blocks',
),
"\xE2\x96\xBC Show %s Line(s)" => array(
"\xE2\x96\xBC Show Line",
"\xE2\x96\xBC Show %s Lines",
),
"\xE2\x96\xBC Show %s Block(s)" => array(
"\xE2\x96\xBC Show Block",
"\xE2\x96\xBC Show %s Blocks",
),
'Show Last %s Line(s)' => array(
'Show Last Line',
'Show Last %s Lines',
),
'Show Last %s Block(s)' => array(
'Show Last Block',
'Show Last %s Blocks',
),
'%s marked %s inline comment(s) as done and %s inline comment(s) as '.
'not done.' => array(
array(
array(
'%s marked an inline comment as done and an inline comment '.
'as not done.',
'%s marked an inline comment as done and %3$s inline comments '.
'as not done.',
),
array(
'%s marked %s inline comments as done and an inline comment '.
'as not done.',
'%s marked %s inline comments as done and %s inline comments '.
'as done.',
),
),
),
'%s marked %s inline comment(s) as done.' => array(
array(
'%s marked an inline comment as done.',
'%s marked %s inline comments as done.',
),
),
'%s marked %s inline comment(s) as not done.' => array(
array(
'%s marked an inline comment as not done.',
'%s marked %s inline comments as not done.',
),
),
'These %s object(s) will be destroyed forever:' => array(
'This object will be destroyed forever:',
'These objects will be destroyed forever:',
),
'Are you absolutely certain you want to destroy these %s '.
'object(s)?' => array(
'Are you absolutely certain you want to destroy this object?',
'Are you absolutely certain you want to destroy these objects?',
),
'%s added %s owner(s): %s.' => array(
array(
'%s added an owner: %3$s.',
'%s added owners: %3$s.',
),
),
'%s removed %s owner(s): %s.' => array(
array(
'%s removed an owner: %3$s.',
'%s removed owners: %3$s.',
),
),
'%s changed %s package owner(s), added %s: %s; removed %s: %s.' => array(
'%s changed package owners, added: %4$s; removed: %6$s.',
),
'Found %s book(s).' => array(
'Found %s book.',
'Found %s books.',
),
'Found %s file(s)...' => array(
'Found %s file...',
'Found %s files...',
),
'Found %s file(s) in project.' => array(
'Found %s file in project.',
'Found %s files in project.',
),
'Found %s unatomized, uncached file(s).' => array(
'Found %s unatomized, uncached file.',
'Found %s unatomized, uncached files.',
),
'Found %s file(s) to atomize.' => array(
'Found %s file to atomize.',
'Found %s files to atomize.',
),
'Atomizing %s file(s).' => array(
'Atomizing %s file.',
'Atomizing %s files.',
),
'Creating %s document(s).' => array(
'Creating %s document.',
'Creating %s documents.',
),
'Deleting %s document(s).' => array(
'Deleting %s document.',
'Deleting %s documents.',
),
'Found %s obsolete atom(s) in graph.' => array(
'Found %s obsolete atom in graph.',
'Found %s obsolete atoms in graph.',
),
'Found %s new atom(s) in graph.' => array(
'Found %s new atom in graph.',
'Found %s new atoms in graph.',
),
'This call takes %s parameter(s), but only %s are documented.' => array(
array(
'This call takes %s parameter, but only %s is documented.',
'This call takes %s parameter, but only %s are documented.',
),
array(
'This call takes %s parameters, but only %s is documented.',
'This call takes %s parameters, but only %s are documented.',
),
),
'%s Passed Test(s)' => '%s Passed',
'%s Failed Test(s)' => '%s Failed',
'%s Skipped Test(s)' => '%s Skipped',
'%s Broken Test(s)' => '%s Broken',
'%s Unsound Test(s)' => '%s Unsound',
'%s Other Test(s)' => '%s Other',
'%s Bulk Task(s)' => array(
'%s Task',
'%s Tasks',
),
'%s added %s badge(s) for %s: %s.' => array(
array(
'%s added a badge for %s: %3$s.',
'%s added badges for %s: %3$s.',
),
),
'%s added %s badge(s): %s.' => array(
array(
'%s added a badge: %3$s.',
'%s added badges: %3$s.',
),
),
'%s awarded %s recipient(s) for %s: %s.' => array(
array(
'%s awarded %3$s to %4$s.',
'%s awarded %3$s to multiple recipients: %4$s.',
),
),
'%s awarded %s recipients(s): %s.' => array(
array(
'%s awarded a recipient: %3$s.',
'%s awarded multiple recipients: %3$s.',
),
),
'%s edited badge(s) for %s, added %s: %s; revoked %s: %s.' => array(
array(
'%s edited badges for %s, added %s: %s; revoked %s: %s.',
'%s edited badges for %s, added %s: %s; revoked %s: %s.',
),
),
'%s edited badge(s), added %s: %s; revoked %s: %s.' => array(
array(
'%s edited badges, added %s: %s; revoked %s: %s.',
'%s edited badges, added %s: %s; revoked %s: %s.',
),
),
'%s edited recipient(s) for %s, awarded %s: %s; revoked %s: %s.' => array(
array(
'%s edited recipients for %s, awarded %s: %s; revoked %s: %s.',
'%s edited recipients for %s, awarded %s: %s; revoked %s: %s.',
),
),
'%s edited recipient(s), awarded %s: %s; revoked %s: %s.' => array(
array(
'%s edited recipients, awarded %s: %s; revoked %s: %s.',
'%s edited recipients, awarded %s: %s; revoked %s: %s.',
),
),
'%s revoked %s badge(s) for %s: %s.' => array(
array(
'%s revoked a badge for %3$s: %4$s.',
'%s revoked multiple badges for %3$s: %4$s.',
),
),
'%s revoked %s badge(s): %s.' => array(
array(
'%s revoked a badge: %3$s.',
'%s revoked multiple badges: %3$s.',
),
),
'%s revoked %s recipient(s) for %s: %s.' => array(
array(
'%s revoked %3$s from %4$s.',
'%s revoked multiple recipients for %3$s: %4$s.',
),
),
'%s revoked %s recipients(s): %s.' => array(
array(
'%s revoked a recipient: %3$s.',
'%s revoked multiple recipients: %3$s.',
),
),
'%s automatically subscribed target(s) were not affected: %s.' => array(
'An automatically subscribed target was not affected: %2$s.',
'Automatically subscribed targets were not affected: %2$s.',
),
'Declined to resubscribe %s target(s) because they previously '.
'unsubscribed: %s.' => array(
'Delined to resubscribe a target because they previously '.
'unsubscribed: %2$s.',
'Declined to resubscribe targets because they previously '.
'unsubscribed: %2$s.',
),
'%s target(s) are not subscribed: %s.' => array(
'A target is not subscribed: %2$s.',
'Targets are not subscribed: %2$s.',
),
'%s target(s) are already subscribed: %s.' => array(
'A target is already subscribed: %2$s.',
'Targets are already subscribed: %2$s.',
),
'Added %s subscriber(s): %s.' => array(
'Added a subscriber: %2$s.',
'Added subscribers: %2$s.',
),
'Removed %s subscriber(s): %s.' => array(
'Removed a subscriber: %2$s.',
'Removed subscribers: %2$s.',
),
'Queued email to be delivered to %s target(s): %s.' => array(
'Queued email to be delivered to target: %2$s.',
'Queued email to be delivered to targets: %2$s.',
),
'Queued email to be delivered to %s target(s), ignoring their '.
'notification preferences: %s.' => array(
'Queued email to be delivered to target, ignoring notification '.
'preferences: %2$s.',
'Queued email to be delivered to targets, ignoring notification '.
'preferences: %2$s.',
),
'%s project(s) are not associated: %s.' => array(
'A project is not associated: %2$s.',
'Projects are not associated: %2$s.',
),
'%s project(s) are already associated: %s.' => array(
'A project is already associated: %2$s.',
'Projects are already associated: %2$s.',
),
'Added %s project(s): %s.' => array(
'Added a project: %2$s.',
'Added projects: %2$s.',
),
'Removed %s project(s): %s.' => array(
'Removed a project: %2$s.',
'Removed projects: %2$s.',
),
'Added %s reviewer(s): %s.' => array(
'Added a reviewer: %2$s.',
'Added reviewers: %2$s.',
),
'Added %s blocking reviewer(s): %s.' => array(
'Added a blocking reviewer: %2$s.',
'Added blocking reviewers: %2$s.',
),
'Required %s signature(s): %s.' => array(
'Required a signature: %2$s.',
'Required signatures: %2$s.',
),
'Started %s build(s): %s.' => array(
'Started a build: %2$s.',
'Started builds: %2$s.',
),
'Added %s auditor(s): %s.' => array(
'Added an auditor: %2$s.',
'Added auditors: %2$s.',
),
'%s target(s) do not have permission to see this object: %s.' => array(
'A target does not have permission to see this object: %2$s.',
'Targets do not have permission to see this object: %2$s.',
),
'This action has no effect on %s target(s): %s.' => array(
'This action has no effect on a target: %2$s.',
'This action has no effect on targets: %2$s.',
),
'Mail sent in the last %s day(s).' => array(
'Mail sent in the last day.',
'Mail sent in the last %s days.',
),
'%s Day(s)' => array(
'%s Day',
'%s Days',
),
'%s Day(s) Ago' => array(
'%s Day Ago',
'%s Days Ago',
),
'Setting retention policy for "%s" to %s day(s).' => array(
array(
'Setting retention policy for "%s" to one day.',
'Setting retention policy for "%s" to %s days.',
),
),
'Waiting %s second(s) for lease to activate.' => array(
'Waiting a second for lease to activate.',
'Waiting %s seconds for lease to activate.',
),
'%s changed %s automation blueprint(s), added %s: %s; removed %s: %s.' =>
'%s changed automation blueprints, added: %4$s; removed: %6$s.',
'%s added %s automation blueprint(s): %s.' => array(
array(
'%s added an automation blueprint: %3$s.',
'%s added automation blueprints: %3$s.',
),
),
'%s removed %s automation blueprint(s): %s.' => array(
array(
'%s removed an automation blueprint: %3$s.',
'%s removed automation blueprints: %3$s.',
),
),
'WARNING: There are %s unapproved authorization(s)!' => array(
'WARNING: There is an unapproved authorization!',
'WARNING: There are unapproved authorizations!',
),
'Found %s Open Resource(s)' => array(
'Found %s Open Resource',
'Found %s Open Resources',
),
'%s Open Resource(s) Remain' => array(
'%s Open Resource Remain',
'%s Open Resources Remain',
),
'Found %s Blueprint(s)' => array(
'Found %s Blueprint',
'Found %s Blueprints',
),
'%s Blueprint(s) Can Allocate' => array(
'%s Blueprint Can Allocate',
'%s Blueprints Can Allocate',
),
'%s Blueprint(s) Enabled' => array(
'%s Blueprint Enabled',
'%s Blueprints Enabled',
),
'%s Event(s)' => array(
'%s Event',
'%s Events',
),
'%s Unit(s)' => array(
'%s Unit',
'%s Units',
),
'QUEUEING TASKS (%s Commit(s)):' => array(
'QUEUEING TASKS (%s Commit):',
'QUEUEING TASKS (%s Commits):',
),
'Found %s total commit(s); updating...' => array(
'Found %s total commit; updating...',
'Found %s total commits; updating...',
),
'Not enough process slots to schedule the other %s '.
'repository(s) for updates yet.' => array(
'Not enough process slots to schedule the other '.'
repository for update yet.',
'Not enough process slots to schedule the other %s '.
'repositories for updates yet.',
),
'%s updated %s, added %d: %s.' =>
'%s updated %s, added: %4$s.',
'%s updated %s, removed %s: %s.' =>
'%s updated %s, removed: %4$s.',
'%s updated %s, added %s: %s; removed %s: %s.' =>
'%s updated %s, added: %4$s; removed: %6$s.',
'%s updated %s for %s, added %d: %s.' =>
'%s updated %s for %s, added: %5$s.',
'%s updated %s for %s, removed %s: %s.' =>
'%s updated %s for %s, removed: %5$s.',
'%s updated %s for %s, added %s: %s; removed %s: %s.' =>
'%s updated %s for %s, added: %5$s; removed; %7$s.',
'Permanently destroyed %s object(s).' => array(
'Permanently destroyed %s object.',
'Permanently destroyed %s objects.',
),
'%s added %s watcher(s) for %s: %s.' => array(
array(
'%s added a watcher for %3$s: %4$s.',
'%s added watchers for %3$s: %4$s.',
),
),
'%s removed %s watcher(s) for %s: %s.' => array(
array(
'%s removed a watcher for %3$s: %4$s.',
'%s removed watchers for %3$s: %4$s.',
),
),
'%s awarded this badge to %s recipient(s): %s.' => array(
array(
'%s awarded this badge to recipient: %3$s.',
'%s awarded this badge to recipients: %3$s.',
),
),
'%s revoked this badge from %s recipient(s): %s.' => array(
array(
'%s revoked this badge from recipient: %3$s.',
'%s revoked this badge from recipients: %3$s.',
),
),
'%s awarded %s to %s recipient(s): %s.' => array(
array(
array(
'%s awarded %s to recipient: %4$s.',
'%s awarded %s to recipients: %4$s.',
),
),
),
'%s revoked %s from %s recipient(s): %s.' => array(
array(
array(
'%s revoked %s from recipient: %4$s.',
'%s revoked %s from recipients: %4$s.',
),
),
),
'%s invited %s attendee(s): %s.' =>
'%s invited: %3$s.',
'%s uninvited %s attendee(s): %s.' =>
'%s uninvited: %3$s.',
'%s invited %s attendee(s): %s; uninvited %s attendee(s): %s.' =>
'%s invited: %3$s; uninvited: %5$s.',
'%s invited %s attendee(s) to %s: %s.' =>
'%s added invites for %3$s: %4$s.',
'%s uninvited %s attendee(s) to %s: %s.' =>
'%s removed invites for %3$s: %4$s.',
'%s updated the invite list for %s, invited %s: %s; uninvited %s: %s.' =>
'%s updated the invite list for %s, invited: %4$s; uninvited: %6$s.',
'Restart %s build(s)?' => array(
'Restart %s build?',
'Restart %s builds?',
),
'%s is starting in %s minute(s), at %s.' => array(
array(
'%s is starting in one minute, at %3$s.',
'%s is starting in %s minutes, at %s.',
),
),
'%s added %s auditor(s): %s.' => array(
array(
'%s added an auditor: %3$s.',
'%s added auditors: %3$s.',
),
),
'%s removed %s auditor(s): %s.' => array(
array(
'%s removed an auditor: %3$s.',
'%s removed auditors: %3$s.',
),
),
'%s edited %s auditor(s), removed %s: %s; added %s: %s.' => array(
array(
'%s edited auditors, removed: %4$s; added: %6$s.',
),
),
'%s accepted this revision as %s reviewer(s): %s.' =>
'%s accepted this revision as: %3$s.',
'%s added %s merchant manager(s): %s.' => array(
array(
'%s added a merchant manager: %3$s.',
'%s added merchant managers: %3$s.',
),
),
'%s removed %s merchant manager(s): %s.' => array(
array(
'%s removed a merchant manager: %3$s.',
'%s removed merchant managers: %3$s.',
),
),
'%s added %s account manager(s): %s.' => array(
array(
'%s added an account manager: %3$s.',
'%s added account managers: %3$s.',
),
),
'%s removed %s account manager(s): %s.' => array(
array(
'%s removed an account manager: %3$s.',
'%s removed account managers: %3$s.',
),
),
'You are about to apply a bulk edit which will affect '.
'%s object(s).' => array(
'You are about to apply a bulk edit to a single object.',
'You are about to apply a bulk edit which will affect '.
'%s objects.',
),
'Destroyed %s credential(s) of type "%s".' => array(
'Destroyed one credential of type "%2$s".',
'Destroyed %s credentials of type "%s".',
),
'%s notification(s) about objects which no longer exist or which '.
'you can no longer see were discarded.' => array(
'One notification about an object which no longer exists or which '.
'you can no longer see was discarded.',
'%s notifications about objects which no longer exist or which '.
'you can no longer see were discarded.',
),
'This draft revision will be sent for review once %s '.
'build(s) pass: %s.' => array(
'This draft revision will be sent for review once this build '.
'passes: %2$s.',
'This draft revision will be sent for review once these builds '.
'pass: %2$s.',
),
'This factor recently issued a challenge to a different login '.
'session. Wait %s second(s) for the code to cycle, then try '.
'again.' => array(
'This factor recently issued a challenge to a different login '.
'session. Wait %s second for the code to cycle, then try '.
'again.',
'This factor recently issued a challenge to a different login '.
'session. Wait %s seconds for the code to cycle, then try '.
'again.',
),
'This factor recently issued a challenge for a different '.
'workflow. Wait %s second(s) for the code to cycle, then try '.
'again.' => array(
'This factor recently issued a challenge for a different '.
'workflow. Wait %s second for the code to cycle, then try '.
'again.',
'This factor recently issued a challenge for a different '.
'workflow. Wait %s seconds for the code to cycle, then try '.
'again.',
),
'This factor recently issued a challenge which has expired. '.
'A new challenge can not be issued yet. Wait %s second(s) for '.
'the code to cycle, then try again.' => array(
'This factor recently issued a challenge which has expired. '.
'A new challenge can not be issued yet. Wait %s second for '.
'the code to cycle, then try again.',
'This factor recently issued a challenge which has expired. '.
'A new challenge can not be issued yet. Wait %s seconds for '.
'the code to cycle, then try again.',
),
'You recently provided a response to this factor. Responses '.
'may not be reused. Wait %s second(s) for the code to cycle, '.
'then try again.' => array(
'You recently provided a response to this factor. Responses '.
'may not be reused. Wait %s second for the code to cycle, '.
'then try again.',
'You recently provided a response to this factor. Responses '.
'may not be reused. Wait %s seconds for the code to cycle, '.
'then try again.',
),
'View All %d Subscriber(s)' => array(
'View Subscriber',
'View All %d Subscribers',
),
'You are currently editing %s inline comment(s) on this '.
'revision.' => array(
'You are currently editing an inline comment on this revision.',
'You are currently editing %s inline comments on this revision.',
),
'These %s inline comment(s) will be saved and published.' => array(
'This inline comment will be saved and published.',
'These inline comments will be saved and published.',
),
'Delayed %s task(s).' => array(
'Delayed 1 task.',
'Delayed %s tasks.',
),
'Freed %s task lease(s).' => array(
'Freed 1 task lease.',
'Freed %s task leases.',
),
'Cancelled %s task(s).' => array(
'Cancelled 1 task.',
'Cancelled %s tasks.',
),
'Queued %s task(s) for retry.' => array(
'Queued 1 task for retry.',
'Queued %s tasks for retry.',
),
'Reprioritized %s task(s).' => array(
'Reprioritized one task.',
'Reprioritized %s tasks.',
),
'Executed %s task(s).' => array(
'Executed 1 task.',
'Executed %s tasks.',
),
+ '%s modified %s attached file(s): %s.' => array(
+ array(
+ '%s modified an attached file: %3$s.',
+ '%s modified attached files: %3$s.',
+ ),
+ ),
+
+ '%s attached %s referenced file(s): %s.' => array(
+ array(
+ '%s attached a referenced file: %3$s.',
+ '%s attached referenced files: %3$s.',
+ ),
+ ),
+
+ '%s removed %s attached file(s): %s.' => array(
+ array(
+ '%s removed an attached file: %3$s.',
+ '%s removed attached files: %3$s.',
+ ),
+ ),
+
);
}
}
diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php
index 3ad5304945..cedd0398e3 100644
--- a/src/infrastructure/markup/PhabricatorMarkupEngine.php
+++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php
@@ -1,715 +1,743 @@
<?php
/**
* Manages markup engine selection, configuration, application, caching and
* pipelining.
*
* @{class:PhabricatorMarkupEngine} can be used to render objects which
* implement @{interface:PhabricatorMarkupInterface} in a batched, cache-aware
* way. For example, if you have a list of comments written in remarkup (and
* the objects implement the correct interface) you can render them by first
* building an engine and adding the fields with @{method:addObject}.
*
* $field = 'field:body'; // Field you want to render. Each object exposes
* // one or more fields of markup.
*
* $engine = new PhabricatorMarkupEngine();
* foreach ($comments as $comment) {
* $engine->addObject($comment, $field);
* }
*
* Now, call @{method:process} to perform the actual cache/rendering
* step. This is a heavyweight call which does batched data access and
* transforms the markup into output.
*
* $engine->process();
*
* Finally, do something with the results:
*
* $results = array();
* foreach ($comments as $comment) {
* $results[] = $engine->getOutput($comment, $field);
* }
*
* If you have a single object to render, you can use the convenience method
* @{method:renderOneObject}.
*
* @task markup Markup Pipeline
* @task engine Engine Construction
*/
final class PhabricatorMarkupEngine extends Phobject {
private $objects = array();
private $viewer;
private $contextObject;
private $version = 21;
private $engineCaches = array();
private $auxiliaryConfig = array();
+ private static $engineStack = array();
+
/* -( Markup Pipeline )---------------------------------------------------- */
/**
* Convenience method for pushing a single object through the markup
* pipeline.
*
* @param PhabricatorMarkupInterface The object to render.
* @param string The field to render.
* @param PhabricatorUser User viewing the markup.
* @param object A context object for policy checks
* @return string Marked up output.
* @task markup
*/
public static function renderOneObject(
PhabricatorMarkupInterface $object,
$field,
PhabricatorUser $viewer,
$context_object = null) {
return id(new PhabricatorMarkupEngine())
->setViewer($viewer)
->setContextObject($context_object)
->addObject($object, $field)
->process()
->getOutput($object, $field);
}
/**
* Queue an object for markup generation when @{method:process} is
* called. You can retrieve the output later with @{method:getOutput}.
*
* @param PhabricatorMarkupInterface The object to render.
* @param string The field to render.
* @return this
* @task markup
*/
public function addObject(PhabricatorMarkupInterface $object, $field) {
$key = $this->getMarkupFieldKey($object, $field);
$this->objects[$key] = array(
'object' => $object,
'field' => $field,
);
return $this;
}
/**
* Process objects queued with @{method:addObject}. You can then retrieve
* the output with @{method:getOutput}.
*
* @return this
* @task markup
*/
public function process() {
+ self::$engineStack[] = $this;
+
+ try {
+ $result = $this->execute();
+ } finally {
+ array_pop(self::$engineStack);
+ }
+
+ return $result;
+ }
+
+ public static function isRenderingEmbeddedContent() {
+ // See T13678. This prevents cycles when rendering embedded content that
+ // itself has remarkup fields.
+ return (count(self::$engineStack) > 1);
+ }
+
+ private function execute() {
$keys = array();
foreach ($this->objects as $key => $info) {
if (!isset($info['markup'])) {
$keys[] = $key;
}
}
if (!$keys) {
return $this;
}
$objects = array_select_keys($this->objects, $keys);
// Build all the markup engines. We need an engine for each field whether
// we have a cache or not, since we still need to postprocess the cache.
$engines = array();
foreach ($objects as $key => $info) {
$engines[$key] = $info['object']->newMarkupEngine($info['field']);
$engines[$key]->setConfig('viewer', $this->viewer);
$engines[$key]->setConfig('contextObject', $this->contextObject);
foreach ($this->auxiliaryConfig as $aux_key => $aux_value) {
$engines[$key]->setConfig($aux_key, $aux_value);
}
}
// Load or build the preprocessor caches.
$blocks = $this->loadPreprocessorCaches($engines, $objects);
$blocks = mpull($blocks, 'getCacheData');
$this->engineCaches = $blocks;
// Finalize the output.
foreach ($objects as $key => $info) {
$engine = $engines[$key];
$field = $info['field'];
$object = $info['object'];
$output = $engine->postprocessText($blocks[$key]);
$output = $object->didMarkupText($field, $output, $engine);
$this->objects[$key]['output'] = $output;
}
return $this;
}
/**
* Get the output of markup processing for a field queued with
* @{method:addObject}. Before you can call this method, you must call
* @{method:process}.
*
* @param PhabricatorMarkupInterface The object to retrieve.
* @param string The field to retrieve.
* @return string Processed output.
* @task markup
*/
public function getOutput(PhabricatorMarkupInterface $object, $field) {
$key = $this->getMarkupFieldKey($object, $field);
$this->requireKeyProcessed($key);
return $this->objects[$key]['output'];
}
/**
* Retrieve engine metadata for a given field.
*
* @param PhabricatorMarkupInterface The object to retrieve.
* @param string The field to retrieve.
* @param string The engine metadata field to retrieve.
* @param wild Optional default value.
* @task markup
*/
public function getEngineMetadata(
PhabricatorMarkupInterface $object,
$field,
$metadata_key,
$default = null) {
$key = $this->getMarkupFieldKey($object, $field);
$this->requireKeyProcessed($key);
return idx($this->engineCaches[$key]['metadata'], $metadata_key, $default);
}
/**
* @task markup
*/
private function requireKeyProcessed($key) {
if (empty($this->objects[$key])) {
throw new Exception(
pht(
"Call %s before using results (key = '%s').",
'addObject()',
$key));
}
if (!isset($this->objects[$key]['output'])) {
throw new PhutilInvalidStateException('process');
}
}
/**
* @task markup
*/
private function getMarkupFieldKey(
PhabricatorMarkupInterface $object,
$field) {
static $custom;
if ($custom === null) {
$custom = array_merge(
self::loadCustomInlineRules(),
self::loadCustomBlockRules());
$custom = mpull($custom, 'getRuleVersion', null);
ksort($custom);
$custom = PhabricatorHash::digestForIndex(serialize($custom));
}
return $object->getMarkupFieldKey($field).'@'.$this->version.'@'.$custom;
}
/**
* @task markup
*/
private function loadPreprocessorCaches(array $engines, array $objects) {
$blocks = array();
$use_cache = array();
foreach ($objects as $key => $info) {
if ($info['object']->shouldUseMarkupCache($info['field'])) {
$use_cache[$key] = true;
}
}
if ($use_cache) {
try {
$blocks = id(new PhabricatorMarkupCache())->loadAllWhere(
'cacheKey IN (%Ls)',
array_keys($use_cache));
$blocks = mpull($blocks, null, 'getCacheKey');
} catch (Exception $ex) {
phlog($ex);
}
}
$is_readonly = PhabricatorEnv::isReadOnly();
foreach ($objects as $key => $info) {
// False check in case MySQL doesn't support unicode characters
// in the string (T1191), resulting in unserialize returning false.
if (isset($blocks[$key]) && $blocks[$key]->getCacheData() !== false) {
// If we already have a preprocessing cache, we don't need to rebuild
// it.
continue;
}
$text = $info['object']->getMarkupText($info['field']);
$data = $engines[$key]->preprocessText($text);
// NOTE: This is just debugging information to help sort out cache issues.
// If one machine is misconfigured and poisoning caches you can use this
// field to hunt it down.
$metadata = array(
'host' => php_uname('n'),
);
$blocks[$key] = id(new PhabricatorMarkupCache())
->setCacheKey($key)
->setCacheData($data)
->setMetadata($metadata);
if (isset($use_cache[$key]) && !$is_readonly) {
// This is just filling a cache and always safe, even on a read pathway.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$blocks[$key]->replace();
unset($unguarded);
}
}
return $blocks;
}
/**
* Set the viewing user. Used to implement object permissions.
*
* @param PhabricatorUser The viewing user.
* @return this
* @task markup
*/
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
/**
* Set the context object. Used to implement object permissions.
*
* @param The object in which context this remarkup is used.
* @return this
* @task markup
*/
public function setContextObject($object) {
$this->contextObject = $object;
return $this;
}
public function setAuxiliaryConfig($key, $value) {
// TODO: This is gross and should be removed. Avoid use.
$this->auxiliaryConfig[$key] = $value;
return $this;
}
/* -( Engine Construction )------------------------------------------------ */
/**
* @task engine
*/
public static function newManiphestMarkupEngine() {
return self::newMarkupEngine(array(
));
}
/**
* @task engine
*/
public static function newPhrictionMarkupEngine() {
return self::newMarkupEngine(array(
'header.generate-toc' => true,
));
}
/**
* @task engine
*/
public static function newPhameMarkupEngine() {
return self::newMarkupEngine(
array(
'macros' => false,
'uri.full' => true,
'uri.same-window' => true,
'uri.base' => PhabricatorEnv::getURI('/'),
));
}
/**
* @task engine
*/
public static function newFeedMarkupEngine() {
return self::newMarkupEngine(
array(
'macros' => false,
'youtube' => false,
));
}
/**
* @task engine
*/
public static function newCalendarMarkupEngine() {
return self::newMarkupEngine(array(
));
}
/**
* @task engine
*/
public static function newDifferentialMarkupEngine(array $options = array()) {
return self::newMarkupEngine(array(
'differential.diff' => idx($options, 'differential.diff'),
));
}
/**
* @task engine
*/
public static function newDiffusionMarkupEngine(array $options = array()) {
return self::newMarkupEngine(array(
'header.generate-toc' => true,
));
}
/**
* @task engine
*/
public static function getEngine($ruleset = 'default') {
static $engines = array();
if (isset($engines[$ruleset])) {
return $engines[$ruleset];
}
$engine = null;
switch ($ruleset) {
case 'default':
$engine = self::newMarkupEngine(array());
break;
case 'feed':
$engine = self::newMarkupEngine(array());
$engine->setConfig('autoplay.disable', true);
break;
case 'nolinebreaks':
$engine = self::newMarkupEngine(array());
$engine->setConfig('preserve-linebreaks', false);
break;
case 'diffusion-readme':
$engine = self::newMarkupEngine(array());
$engine->setConfig('preserve-linebreaks', false);
$engine->setConfig('header.generate-toc', true);
break;
case 'diviner':
$engine = self::newMarkupEngine(array());
$engine->setConfig('preserve-linebreaks', false);
// $engine->setConfig('diviner.renderer', new DivinerDefaultRenderer());
$engine->setConfig('header.generate-toc', true);
break;
case 'extract':
// Engine used for reference/edge extraction. Turn off anything which
// is slow and doesn't change reference extraction.
$engine = self::newMarkupEngine(array());
$engine->setConfig('pygments.enabled', false);
break;
default:
throw new Exception(pht('Unknown engine ruleset: %s!', $ruleset));
}
$engines[$ruleset] = $engine;
return $engine;
}
/**
* @task engine
*/
private static function getMarkupEngineDefaultConfiguration() {
return array(
'pygments' => PhabricatorEnv::getEnvConfig('pygments.enabled'),
'youtube' => PhabricatorEnv::getEnvConfig(
'remarkup.enable-embedded-youtube'),
'differential.diff' => null,
'header.generate-toc' => false,
'macros' => true,
'uri.allowed-protocols' => PhabricatorEnv::getEnvConfig(
'uri.allowed-protocols'),
'uri.full' => false,
'syntax-highlighter.engine' => PhabricatorEnv::getEnvConfig(
'syntax-highlighter.engine'),
'preserve-linebreaks' => true,
);
}
/**
* @task engine
*/
public static function newMarkupEngine(array $options) {
$options += self::getMarkupEngineDefaultConfiguration();
$engine = new PhutilRemarkupEngine();
$engine->setConfig('preserve-linebreaks', $options['preserve-linebreaks']);
$engine->setConfig('pygments.enabled', $options['pygments']);
$engine->setConfig(
'uri.allowed-protocols',
$options['uri.allowed-protocols']);
$engine->setConfig('differential.diff', $options['differential.diff']);
$engine->setConfig('header.generate-toc', $options['header.generate-toc']);
$engine->setConfig(
'syntax-highlighter.engine',
$options['syntax-highlighter.engine']);
$style_map = id(new PhabricatorDefaultSyntaxStyle())
->getRemarkupStyleMap();
$engine->setConfig('phutil.codeblock.style-map', $style_map);
$engine->setConfig('uri.full', $options['uri.full']);
if (isset($options['uri.base'])) {
$engine->setConfig('uri.base', $options['uri.base']);
}
if (isset($options['uri.same-window'])) {
$engine->setConfig('uri.same-window', $options['uri.same-window']);
}
$rules = array();
$rules[] = new PhutilRemarkupEscapeRemarkupRule();
$rules[] = new PhutilRemarkupEvalRule();
$rules[] = new PhutilRemarkupMonospaceRule();
$rules[] = new PhutilRemarkupDocumentLinkRule();
$rules[] = new PhabricatorNavigationRemarkupRule();
$rules[] = new PhabricatorKeyboardRemarkupRule();
$rules[] = new PhabricatorConfigRemarkupRule();
if ($options['youtube']) {
$rules[] = new PhabricatorYoutubeRemarkupRule();
}
$rules[] = new PhabricatorIconRemarkupRule();
$rules[] = new PhabricatorEmojiRemarkupRule();
$rules[] = new PhabricatorHandleRemarkupRule();
$applications = PhabricatorApplication::getAllInstalledApplications();
foreach ($applications as $application) {
foreach ($application->getRemarkupRules() as $rule) {
$rules[] = $rule;
}
}
$rules[] = new PhutilRemarkupHyperlinkRule();
if ($options['macros']) {
$rules[] = new PhabricatorImageMacroRemarkupRule();
$rules[] = new PhabricatorMemeRemarkupRule();
}
$rules[] = new PhutilRemarkupBoldRule();
$rules[] = new PhutilRemarkupItalicRule();
$rules[] = new PhutilRemarkupDelRule();
$rules[] = new PhutilRemarkupUnderlineRule();
$rules[] = new PhutilRemarkupHighlightRule();
$rules[] = new PhutilRemarkupAnchorRule();
foreach (self::loadCustomInlineRules() as $rule) {
$rules[] = clone $rule;
}
$blocks = array();
$blocks[] = new PhutilRemarkupQuotesBlockRule();
$blocks[] = new PhutilRemarkupReplyBlockRule();
$blocks[] = new PhutilRemarkupLiteralBlockRule();
$blocks[] = new PhutilRemarkupHeaderBlockRule();
$blocks[] = new PhutilRemarkupHorizontalRuleBlockRule();
$blocks[] = new PhutilRemarkupListBlockRule();
$blocks[] = new PhutilRemarkupCodeBlockRule();
$blocks[] = new PhutilRemarkupNoteBlockRule();
$blocks[] = new PhutilRemarkupTableBlockRule();
$blocks[] = new PhutilRemarkupSimpleTableBlockRule();
$blocks[] = new PhutilRemarkupInterpreterBlockRule();
$blocks[] = new PhutilRemarkupDefaultBlockRule();
foreach (self::loadCustomBlockRules() as $rule) {
$blocks[] = $rule;
}
foreach ($blocks as $block) {
$block->setMarkupRules($rules);
}
$engine->setBlockRules($blocks);
return $engine;
}
public static function extractPHIDsFromMentions(
PhabricatorUser $viewer,
array $content_blocks) {
$mentions = array();
$engine = self::newDifferentialMarkupEngine();
$engine->setConfig('viewer', $viewer);
foreach ($content_blocks as $content_block) {
+ if ($content_block === null) {
+ continue;
+ }
+
+ if (!strlen($content_block)) {
+ continue;
+ }
+
$engine->markupText($content_block);
$phids = $engine->getTextMetadata(
PhabricatorMentionRemarkupRule::KEY_MENTIONED,
array());
$mentions += $phids;
}
return $mentions;
}
public static function extractFilePHIDsFromEmbeddedFiles(
PhabricatorUser $viewer,
array $content_blocks) {
$files = array();
$engine = self::newDifferentialMarkupEngine();
$engine->setConfig('viewer', $viewer);
foreach ($content_blocks as $content_block) {
$engine->markupText($content_block);
$phids = $engine->getTextMetadata(
- PhabricatorEmbedFileRemarkupRule::KEY_EMBED_FILE_PHIDS,
+ PhabricatorEmbedFileRemarkupRule::KEY_ATTACH_INTENT_FILE_PHIDS,
array());
foreach ($phids as $phid) {
$files[$phid] = $phid;
}
}
return array_values($files);
}
public static function summarizeSentence($corpus) {
$corpus = trim($corpus);
$blocks = preg_split('/\n+/', $corpus, 2);
$block = head($blocks);
$sentences = preg_split(
'/\b([.?!]+)\B/u',
$block,
2,
PREG_SPLIT_DELIM_CAPTURE);
if (count($sentences) > 1) {
$result = $sentences[0].$sentences[1];
} else {
$result = head($sentences);
}
return id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs(128)
->truncateString($result);
}
/**
* Produce a corpus summary, in a way that shortens the underlying text
* without truncating it somewhere awkward.
*
* TODO: We could do a better job of this.
*
* @param string Remarkup corpus to summarize.
* @return string Summarized corpus.
*/
public static function summarize($corpus) {
// Major goals here are:
// - Don't split in the middle of a character (utf-8).
// - Don't split in the middle of, e.g., **bold** text, since
// we end up with hanging '**' in the summary.
// - Try not to pick an image macro, header, embedded file, etc.
// - Hopefully don't return too much text. We don't explicitly limit
// this right now.
$blocks = preg_split("/\n *\n\s*/", $corpus);
$best = null;
foreach ($blocks as $block) {
// This is a test for normal spaces in the block, i.e. a heuristic to
// distinguish standard paragraphs from things like image macros. It may
// not work well for non-latin text. We prefer to summarize with a
// paragraph of normal words over an image macro, if possible.
$has_space = preg_match('/\w\s\w/', $block);
// This is a test to find embedded images and headers. We prefer to
// summarize with a normal paragraph over a header or an embedded object,
// if possible.
$has_embed = preg_match('/^[{=]/', $block);
if ($has_space && !$has_embed) {
// This seems like a good summary, so return it.
return $block;
}
if (!$best) {
// This is the first block we found; if everything is garbage just
// use the first block.
$best = $block;
}
}
return $best;
}
private static function loadCustomInlineRules() {
return id(new PhutilClassMapQuery())
->setAncestorClass('PhabricatorRemarkupCustomInlineRule')
->execute();
}
private static function loadCustomBlockRules() {
return id(new PhutilClassMapQuery())
->setAncestorClass('PhabricatorRemarkupCustomBlockRule')
->execute();
}
public static function digestRemarkupContent($object, $content) {
$parts = array();
$parts[] = get_class($object);
if ($object instanceof PhabricatorLiskDAO) {
$parts[] = $object->getID();
}
$parts[] = $content;
$message = implode("\n", $parts);
return PhabricatorHash::digestWithNamedKey($message, 'remarkup');
}
}
diff --git a/src/infrastructure/markup/blockrule/PhutilRemarkupBlockRule.php b/src/infrastructure/markup/blockrule/PhutilRemarkupBlockRule.php
index feac6cffa9..ac3a308d06 100644
--- a/src/infrastructure/markup/blockrule/PhutilRemarkupBlockRule.php
+++ b/src/infrastructure/markup/blockrule/PhutilRemarkupBlockRule.php
@@ -1,170 +1,178 @@
<?php
abstract class PhutilRemarkupBlockRule extends Phobject {
private $engine;
private $rules = array();
/**
* Determine the order in which blocks execute. Blocks with smaller priority
* numbers execute sooner than blocks with larger priority numbers. The
* default priority for blocks is `500`.
*
* Priorities are used to disambiguate syntax which can match multiple
* patterns. For example, ` - Lorem ipsum...` may be a code block or a
* list.
*
* @return int Priority at which this block should execute.
*/
public function getPriority() {
return 500;
}
final public function getPriorityVector() {
return id(new PhutilSortVector())
->addInt($this->getPriority())
->addString(get_class($this));
}
abstract public function markupText($text, $children);
/**
* This will get an array of unparsed lines and return the number of lines
* from the first array value that it can parse.
*
* @param array $lines
* @param int $cursor
*
* @return int
*/
abstract public function getMatchingLineCount(array $lines, $cursor);
protected function didMarkupText() {
return;
}
+ public function willMarkupChildBlocks() {
+ return;
+ }
+
+ public function didMarkupChildBlocks() {
+ return;
+ }
+
final public function setEngine(PhutilRemarkupEngine $engine) {
$this->engine = $engine;
$this->updateRules();
return $this;
}
final protected function getEngine() {
return $this->engine;
}
public function setMarkupRules(array $rules) {
assert_instances_of($rules, 'PhutilRemarkupRule');
$this->rules = $rules;
$this->updateRules();
return $this;
}
private function updateRules() {
$engine = $this->getEngine();
if ($engine) {
$this->rules = msort($this->rules, 'getPriority');
foreach ($this->rules as $rule) {
$rule->setEngine($engine);
}
}
return $this;
}
final public function getMarkupRules() {
return $this->rules;
}
final public function postprocess() {
$this->didMarkupText();
}
final protected function applyRules($text) {
foreach ($this->getMarkupRules() as $rule) {
$text = $rule->apply($text);
}
return $text;
}
public function supportsChildBlocks() {
return false;
}
public function extractChildText($text) {
throw new PhutilMethodNotImplementedException();
}
protected function renderRemarkupTable(array $out_rows) {
assert_instances_of($out_rows, 'array');
if ($this->getEngine()->isTextMode()) {
$lengths = array();
foreach ($out_rows as $r => $row) {
foreach ($row['content'] as $c => $cell) {
$text = $this->getEngine()->restoreText($cell['content']);
$lengths[$c][$r] = phutil_utf8_strlen($text);
}
}
$max_lengths = array_map('max', $lengths);
$out = array();
foreach ($out_rows as $r => $row) {
$headings = false;
foreach ($row['content'] as $c => $cell) {
$length = $max_lengths[$c] - $lengths[$c][$r];
$out[] = '| '.$cell['content'].str_repeat(' ', $length).' ';
if ($cell['type'] == 'th') {
$headings = true;
}
}
$out[] = "|\n";
if ($headings) {
foreach ($row['content'] as $c => $cell) {
$char = ($cell['type'] == 'th' ? '-' : ' ');
$out[] = '| '.str_repeat($char, $max_lengths[$c]).' ';
}
$out[] = "|\n";
}
}
return rtrim(implode('', $out), "\n");
}
if ($this->getEngine()->isHTMLMailMode()) {
$table_attributes = array(
'style' => 'border-collapse: separate;
border-spacing: 1px;
background: #d3d3d3;
margin: 12px 0;',
);
$cell_attributes = array(
'style' => 'background: #ffffff;
padding: 3px 6px;',
);
} else {
$table_attributes = array(
'class' => 'remarkup-table',
);
$cell_attributes = array();
}
$out = array();
$out[] = "\n";
foreach ($out_rows as $row) {
$cells = array();
foreach ($row['content'] as $cell) {
$cells[] = phutil_tag(
$cell['type'],
$cell_attributes,
$cell['content']);
}
$out[] = phutil_tag($row['type'], array(), $cells);
$out[] = "\n";
}
$table = phutil_tag('table', $table_attributes, $out);
return phutil_tag_div('remarkup-table-wrap', $table);
}
}
diff --git a/src/infrastructure/markup/blockrule/PhutilRemarkupListBlockRule.php b/src/infrastructure/markup/blockrule/PhutilRemarkupListBlockRule.php
index 5bdcab2b8f..b7d82edfef 100644
--- a/src/infrastructure/markup/blockrule/PhutilRemarkupListBlockRule.php
+++ b/src/infrastructure/markup/blockrule/PhutilRemarkupListBlockRule.php
@@ -1,567 +1,567 @@
<?php
final class PhutilRemarkupListBlockRule extends PhutilRemarkupBlockRule {
/**
* This rule must apply before the Code block rule because it needs to
* win blocks which begin ` - Lorem ipsum`.
*/
public function getPriority() {
return 400;
}
public function getMatchingLineCount(array $lines, $cursor) {
$num_lines = 0;
$first_line = $cursor;
$is_one_line = false;
while (isset($lines[$cursor])) {
if (!$num_lines) {
if (preg_match(self::START_BLOCK_PATTERN, $lines[$cursor])) {
$num_lines++;
$cursor++;
$is_one_line = true;
continue;
}
} else {
if (preg_match(self::CONT_BLOCK_PATTERN, $lines[$cursor])) {
$num_lines++;
$cursor++;
$is_one_line = false;
continue;
}
// Allow lists to continue across multiple paragraphs, as long as lines
// are indented or a single empty line separates indented lines.
$this_empty = !strlen(trim($lines[$cursor]));
$this_indented = preg_match('/^ /', $lines[$cursor]);
$next_empty = true;
$next_indented = false;
if (isset($lines[$cursor + 1])) {
$next_empty = !strlen(trim($lines[$cursor + 1]));
$next_indented = preg_match('/^ /', $lines[$cursor + 1]);
}
if ($this_empty || $this_indented) {
if (($this_indented && !$this_empty) ||
($next_indented && !$next_empty)) {
$num_lines++;
$cursor++;
continue;
}
}
if ($this_empty) {
$num_lines++;
}
}
break;
}
// If this list only has one item in it, and the list marker is "#", and
// it's not the last line in the input, parse it as a header instead of a
// list. This produces better behavior for alternate Markdown headers.
if ($is_one_line) {
if (($first_line + $num_lines) < count($lines)) {
if (strncmp($lines[$first_line], '#', 1) === 0) {
return 0;
}
}
}
return $num_lines;
}
/**
* The maximum sub-list depth you can nest to. Avoids silliness and blowing
* the stack.
*/
const MAXIMUM_LIST_NESTING_DEPTH = 12;
const START_BLOCK_PATTERN = '@^\s*(?:[-*#]+|([1-9][0-9]*)[.)]|\[\D?\])\s+@';
const CONT_BLOCK_PATTERN = '@^\s*(?:[-*#]+|[0-9]+[.)]|\[\D?\])\s+@';
const STRIP_BLOCK_PATTERN = '@^\s*(?:[-*#]+|[0-9]+[.)])\s*@';
public function markupText($text, $children) {
$items = array();
$lines = explode("\n", $text);
// We allow users to delimit lists using either differing indentation
// levels:
//
// - a
// - b
//
// ...or differing numbers of item-delimiter characters:
//
// - a
// -- b
//
// If they use the second style but block-indent the whole list, we'll
// get the depth counts wrong for the first item. To prevent this,
// un-indent every item by the minimum indentation level for the whole
// block before we begin parsing.
$regex = self::START_BLOCK_PATTERN;
$min_space = PHP_INT_MAX;
foreach ($lines as $ii => $line) {
$matches = null;
if (preg_match($regex, $line)) {
$regex = self::CONT_BLOCK_PATTERN;
if (preg_match('/^(\s+)/', $line, $matches)) {
$space = strlen($matches[1]);
} else {
$space = 0;
}
$min_space = min($min_space, $space);
}
}
$regex = self::START_BLOCK_PATTERN;
if ($min_space) {
foreach ($lines as $key => $line) {
if (preg_match($regex, $line)) {
$regex = self::CONT_BLOCK_PATTERN;
$lines[$key] = substr($line, $min_space);
}
}
}
// The input text may have linewraps in it, like this:
//
// - derp derp derp derp
// derp derp derp derp
// - blarp blarp blarp blarp
//
// Group text lines together into list items, stored in $items. So the
// result in the above case will be:
//
// array(
// array(
// "- derp derp derp derp",
// " derp derp derp derp",
// ),
// array(
// "- blarp blarp blarp blarp",
// ),
// );
$item = array();
$starts_at = null;
$regex = self::START_BLOCK_PATTERN;
foreach ($lines as $line) {
$match = null;
if (preg_match($regex, $line, $match)) {
if (!$starts_at && !empty($match[1])) {
$starts_at = $match[1];
}
$regex = self::CONT_BLOCK_PATTERN;
if ($item) {
$items[] = $item;
$item = array();
}
}
$item[] = $line;
}
if ($item) {
$items[] = $item;
}
if (!$starts_at) {
$starts_at = 1;
}
// Process each item to normalize the text, remove line wrapping, and
// determine its depth (indentation level) and style (ordered vs unordered).
//
// We preserve consecutive linebreaks and interpret them as paragraph
// breaks.
//
// Given the above example, the processed array will look like:
//
// array(
// array(
// 'text' => 'derp derp derp derp derp derp derp derp',
// 'depth' => 0,
// 'style' => '-',
// ),
// array(
// 'text' => 'blarp blarp blarp blarp',
// 'depth' => 0,
// 'style' => '-',
// ),
// );
$has_marks = false;
foreach ($items as $key => $item) {
// Trim space around newlines, to strip trailing whitespace and formatting
// indentation.
$item = preg_replace('/ *(\n+) */', '\1', implode("\n", $item));
// Replace single newlines with a space. Preserve multiple newlines as
// paragraph breaks.
$item = preg_replace('/(?<!\n)\n(?!\n)/', ' ', $item);
$item = rtrim($item);
if (!strlen($item)) {
unset($items[$key]);
continue;
}
$matches = null;
if (preg_match('/^\s*([-*#]{2,})/', $item, $matches)) {
// Alternate-style indents; use number of list item symbols.
$depth = strlen($matches[1]) - 1;
} else if (preg_match('/^(\s+)/', $item, $matches)) {
// Markdown-style indents; use indent depth.
$depth = strlen($matches[1]);
} else {
$depth = 0;
}
if (preg_match('/^\s*(?:#|[0-9])/', $item)) {
$style = '#';
} else {
$style = '-';
}
// Strip leading indicators off the item.
$text = preg_replace(self::STRIP_BLOCK_PATTERN, '', $item);
// Look for "[]", "[ ]", "[*]", "[x]", etc., which we render as a
// checkbox. We don't render [1], [2], etc., as checkboxes, as these
// are often used as footnotes.
$mark = null;
$matches = null;
if (preg_match('/^\s*\[(\D?)\]\s*/', $text, $matches)) {
if (strlen(trim($matches[1]))) {
$mark = true;
} else {
$mark = false;
}
$has_marks = true;
$text = substr($text, strlen($matches[0]));
}
$items[$key] = array(
'text' => $text,
'depth' => $depth,
'style' => $style,
'mark' => $mark,
);
}
$items = array_values($items);
// Users can create a sub-list by indenting any deeper amount than the
// previous list, so these are both valid:
//
// - a
// - b
//
// - a
// - b
//
// In the former case, we'll have depths (0, 2). In the latter case, depths
// (0, 4). We don't actually care about how many spaces there are, only
// how many list indentation levels (that is, we want to map both of
// those cases to (0, 1), indicating "outermost list" and "first sublist").
//
// This is made more complicated because lists at two different indentation
// levels might be at the same list level:
//
// - a
// - b
// - c
// - d
//
// Here, 'b' and 'd' are at the same list level (2) but different indent
// levels (2, 4).
//
// Users can also create "staircases" like this:
//
// - a
// - b
// # c
//
// While this is silly, we'd like to render it as faithfully as possible.
//
// In order to do this, we convert the list of nodes into a tree,
// normalizing indentation levels and inserting dummy nodes as necessary to
// make the tree well-formed. See additional notes at buildTree().
//
// In the case above, the result is a tree like this:
//
// - <null>
// - <null>
// - a
// - b
// # c
$l = 0;
$r = count($items);
$tree = $this->buildTree($items, $l, $r, $cur_level = 0);
// We may need to open a list on a <null> node, but they do not have
// list style information yet. We need to propagate list style information
// backward through the tree. In the above example, the tree now looks
// like this:
//
// - <null (style=#)>
// - <null (style=-)>
// - a
// - b
// # c
$this->adjustTreeStyleInformation($tree);
// Finally, we have enough information to render the tree.
$out = $this->renderTree($tree, 0, $has_marks, $starts_at);
if ($this->getEngine()->isTextMode()) {
$out = implode('', $out);
$out = rtrim($out, "\n");
$out = preg_replace('/ +$/m', '', $out);
return $out;
}
return phutil_implode_html('', $out);
}
/**
* See additional notes in @{method:markupText}.
*/
private function buildTree(array $items, $l, $r, $cur_level) {
if ($l == $r) {
return array();
}
if ($cur_level > self::MAXIMUM_LIST_NESTING_DEPTH) {
// This algorithm is recursive and we don't need you blowing the stack
// with your oh-so-clever 50,000-item-deep list. Cap indentation levels
// at a reasonable number and just shove everything deeper up to this
// level.
$nodes = array();
for ($ii = $l; $ii < $r; $ii++) {
$nodes[] = array(
'level' => $cur_level,
'items' => array(),
) + $items[$ii];
}
return $nodes;
}
$min = $l;
for ($ii = $r - 1; $ii >= $l; $ii--) {
if ($items[$ii]['depth'] <= $items[$min]['depth']) {
$min = $ii;
}
}
$min_depth = $items[$min]['depth'];
$nodes = array();
if ($min != $l) {
$nodes[] = array(
'text' => null,
'level' => $cur_level,
'style' => null,
'mark' => null,
'items' => $this->buildTree($items, $l, $min, $cur_level + 1),
);
}
$last = $min;
for ($ii = $last + 1; $ii < $r; $ii++) {
if ($items[$ii]['depth'] == $min_depth) {
$nodes[] = array(
'level' => $cur_level,
'items' => $this->buildTree($items, $last + 1, $ii, $cur_level + 1),
) + $items[$last];
$last = $ii;
}
}
$nodes[] = array(
'level' => $cur_level,
'items' => $this->buildTree($items, $last + 1, $r, $cur_level + 1),
) + $items[$last];
return $nodes;
}
/**
* See additional notes in @{method:markupText}.
*/
private function adjustTreeStyleInformation(array &$tree) {
// The effect here is just to walk backward through the nodes at this level
// and apply the first style in the list to any empty nodes we inserted
// before it. As we go, also recurse down the tree.
$style = '-';
for ($ii = count($tree) - 1; $ii >= 0; $ii--) {
if ($tree[$ii]['style'] !== null) {
// This is the earliest node we've seen with style, so set the
// style to its style.
$style = $tree[$ii]['style'];
} else {
// This node has no style, so apply the current style.
$tree[$ii]['style'] = $style;
}
if ($tree[$ii]['items']) {
$this->adjustTreeStyleInformation($tree[$ii]['items']);
}
}
}
/**
* See additional notes in @{method:markupText}.
*/
private function renderTree(
array $tree,
$level,
$has_marks,
$starts_at = 1) {
$style = idx(head($tree), 'style');
$out = array();
if (!$this->getEngine()->isTextMode()) {
switch ($style) {
case '#':
$tag = 'ol';
break;
case '-':
$tag = 'ul';
break;
}
$start_attr = null;
- if (ctype_digit($starts_at) && $starts_at > 1) {
+ if (ctype_digit(phutil_string_cast($starts_at)) && $starts_at > 1) {
$start_attr = hsprintf(' start="%d"', $starts_at);
}
if ($has_marks) {
$out[] = hsprintf(
'<%s class="remarkup-list remarkup-list-with-checkmarks"%s>',
$tag,
$start_attr);
} else {
$out[] = hsprintf(
'<%s class="remarkup-list"%s>',
$tag,
$start_attr);
}
$out[] = "\n";
}
$number = $starts_at;
foreach ($tree as $item) {
if ($this->getEngine()->isTextMode()) {
if ($item['text'] === null) {
// Don't render anything.
} else {
$indent = str_repeat(' ', 2 * $level);
$out[] = $indent;
if ($item['mark'] !== null) {
if ($item['mark']) {
$out[] = '[X] ';
} else {
$out[] = '[ ] ';
}
} else {
switch ($style) {
case '#':
$out[] = $number.'. ';
$number++;
break;
case '-':
$out[] = '- ';
break;
}
}
$parts = preg_split('/\n{2,}/', $item['text']);
foreach ($parts as $key => $part) {
if ($key != 0) {
$out[] = "\n\n ".$indent;
}
$out[] = $this->applyRules($part);
}
$out[] = "\n";
}
} else {
if ($item['text'] === null) {
$out[] = hsprintf('<li class="remarkup-list-item phantom-item">');
} else {
if ($item['mark'] !== null) {
if ($item['mark'] == true) {
$out[] = hsprintf(
'<li class="remarkup-list-item remarkup-checked-item">');
} else {
$out[] = hsprintf(
'<li class="remarkup-list-item remarkup-unchecked-item">');
}
$out[] = phutil_tag(
'input',
array(
'type' => 'checkbox',
'checked' => ($item['mark'] ? 'checked' : null),
'disabled' => 'disabled',
));
$out[] = ' ';
} else {
$out[] = hsprintf('<li class="remarkup-list-item">');
}
$parts = preg_split('/\n{2,}/', $item['text']);
foreach ($parts as $key => $part) {
if ($key != 0) {
$out[] = array(
"\n",
phutil_tag('br'),
phutil_tag('br'),
"\n",
);
}
$out[] = $this->applyRules($part);
}
}
}
if ($item['items']) {
$subitems = $this->renderTree($item['items'], $level + 1, $has_marks);
foreach ($subitems as $i) {
$out[] = $i;
}
}
if (!$this->getEngine()->isTextMode()) {
$out[] = hsprintf("</li>\n");
}
}
if (!$this->getEngine()->isTextMode()) {
switch ($style) {
case '#':
$out[] = hsprintf('</ol>');
break;
case '-':
$out[] = hsprintf('</ul>');
break;
}
}
return $out;
}
}
diff --git a/src/infrastructure/markup/blockrule/PhutilRemarkupQuotedBlockRule.php b/src/infrastructure/markup/blockrule/PhutilRemarkupQuotedBlockRule.php
index 9f2bd7a297..90c9a2c33a 100644
--- a/src/infrastructure/markup/blockrule/PhutilRemarkupQuotedBlockRule.php
+++ b/src/infrastructure/markup/blockrule/PhutilRemarkupQuotedBlockRule.php
@@ -1,110 +1,126 @@
<?php
abstract class PhutilRemarkupQuotedBlockRule
extends PhutilRemarkupBlockRule {
final public function supportsChildBlocks() {
return true;
}
+ public function willMarkupChildBlocks() {
+ $engine = $this->getEngine();
+
+ $depth = $engine->getQuoteDepth();
+ $depth = $depth + 1;
+ $engine->setQuoteDepth($depth);
+ }
+
+ public function didMarkupChildBlocks() {
+ $engine = $this->getEngine();
+
+ $depth = $engine->getQuoteDepth();
+ $depth = $depth - 1;
+ $engine->setQuoteDepth($depth);
+ }
+
final protected function normalizeQuotedBody($text) {
$text = phutil_split_lines($text, true);
foreach ($text as $key => $line) {
$text[$key] = substr($line, 1);
}
// If every line in the block is empty or begins with at least one leading
// space, strip the initial space off each line. When we quote text, we
// normally add "> " (with a space) to the beginning of each line, which
// can disrupt some other rules. If the block appears to have this space
// in front of each line, remove it.
$strip_space = true;
foreach ($text as $key => $line) {
$len = strlen($line);
if (!$len) {
// We'll still strip spaces if there are some completely empty
// lines, they may have just had trailing whitespace trimmed.
continue;
}
// If this line is part of a nested quote block, just ignore it when
// realigning this quote block. It's either an author attribution
// line with ">>!", or we'll deal with it in a subrule when processing
// the nested quote block.
if ($line[0] == '>') {
continue;
}
if ($line[0] == ' ' || $line[0] == "\n") {
continue;
}
// The first character of this line is something other than a space, so
// we can't strip spaces.
$strip_space = false;
break;
}
if ($strip_space) {
foreach ($text as $key => $line) {
$len = strlen($line);
if (!$len) {
continue;
}
if ($line[0] !== ' ') {
continue;
}
$text[$key] = substr($line, 1);
}
}
// Strip leading empty lines.
foreach ($text as $key => $line) {
if (!strlen(trim($line))) {
unset($text[$key]);
} else {
break;
}
}
return implode('', $text);
}
final protected function getQuotedText($text) {
$text = rtrim($text, "\n");
$no_whitespace = array(
// For readability, we render nested quotes as ">> quack",
// not "> > quack".
'>' => true,
// If the line is empty except for a newline, do not add an
// unnecessary dangling space.
"\n" => true,
);
$text = phutil_split_lines($text, true);
foreach ($text as $key => $line) {
$c = null;
if (isset($line[0])) {
$c = $line[0];
} else {
$c = null;
}
if (isset($no_whitespace[$c])) {
$text[$key] = '>'.$line;
} else {
$text[$key] = '> '.$line;
}
}
$text = implode('', $text);
return $text;
}
}
diff --git a/src/infrastructure/markup/markuprule/PhutilRemarkupDocumentLinkRule.php b/src/infrastructure/markup/markuprule/PhutilRemarkupDocumentLinkRule.php
index 2170d9ae5e..ded57d4c77 100644
--- a/src/infrastructure/markup/markuprule/PhutilRemarkupDocumentLinkRule.php
+++ b/src/infrastructure/markup/markuprule/PhutilRemarkupDocumentLinkRule.php
@@ -1,183 +1,183 @@
<?php
final class PhutilRemarkupDocumentLinkRule extends PhutilRemarkupRule {
public function getPriority() {
return 150.0;
}
public function apply($text) {
// Handle mediawiki-style links: [[ href | name ]]
$text = preg_replace_callback(
'@\B\\[\\[([^|\\]]+)(?:\\|([^\\]]+))?\\]\\]\B@U',
array($this, 'markupDocumentLink'),
$text);
// Handle markdown-style links: [name](href)
$text = preg_replace_callback(
'@'.
'\B'.
'\\[([^\\]]+)\\]'.
'\\('.
'(\s*'.
// See T12343. This is making some kind of effort to implement
// parenthesis balancing rules. It won't get nested parentheses
// right, but should do OK for Wikipedia pages, which seem to be
// the most important use case.
// Match zero or more non-parenthesis, non-space characters.
'[^\s()]*'.
// Match zero or more sequences of "(...)", where two balanced
// parentheses enclose zero or more normal characters. If we
// match some, optionally match more stuff at the end.
'(?:(?:\\([^ ()]*\\))+[^\s()]*)?'.
'\s*)'.
'\\)'.
'\B'.
'@U',
array($this, 'markupAlternateLink'),
$text);
return $text;
}
protected function renderHyperlink($link, $name) {
$engine = $this->getEngine();
$is_anchor = false;
if (strncmp($link, '/', 1) == 0) {
- $base = $engine->getConfig('uri.base');
+ $base = phutil_string_cast($engine->getConfig('uri.base'));
$base = rtrim($base, '/');
$link = $base.$link;
} else if (strncmp($link, '#', 1) == 0) {
$here = $engine->getConfig('uri.here');
$link = $here.$link;
$is_anchor = true;
}
if ($engine->isTextMode()) {
// If present, strip off "mailto:" or "tel:".
$link = preg_replace('/^(?:mailto|tel):/', '', $link);
if (!strlen($name)) {
return $link;
}
return $name.' <'.$link.'>';
}
if (!strlen($name)) {
$name = $link;
$name = preg_replace('/^(?:mailto|tel):/', '', $name);
}
if ($engine->getState('toc')) {
return $name;
}
$same_window = $engine->getConfig('uri.same-window', false);
if ($same_window) {
$target = null;
} else {
$target = '_blank';
}
// For anchors on the same page, always stay here.
if ($is_anchor) {
$target = null;
}
return phutil_tag(
'a',
array(
'href' => $link,
'class' => 'remarkup-link',
'target' => $target,
'rel' => 'noreferrer',
),
$name);
}
public function markupAlternateLink(array $matches) {
$uri = trim($matches[2]);
if (!strlen($uri)) {
return $matches[0];
}
// NOTE: We apply some special rules to avoid false positives here. The
// major concern is that we do not want to convert `x[0][1](y)` in a
// discussion about C source code into a link. To this end, we:
//
// - Don't match at word boundaries;
// - require the URI to contain a "/" character or "@" character; and
// - reject URIs which being with a quote character.
if ($uri[0] == '"' || $uri[0] == "'" || $uri[0] == '`') {
return $matches[0];
}
if (strpos($uri, '/') === false &&
strpos($uri, '@') === false &&
strncmp($uri, 'tel:', 4)) {
return $matches[0];
}
return $this->markupDocumentLink(
array(
$matches[0],
$matches[2],
$matches[1],
));
}
public function markupDocumentLink(array $matches) {
$uri = trim($matches[1]);
- $name = trim(idx($matches, 2));
+ $name = trim(idx($matches, 2, ''));
if (!$this->isFlatText($uri)) {
return $matches[0];
}
if (!$this->isFlatText($name)) {
return $matches[0];
}
// If whatever is being linked to begins with "/" or "#", or has "://",
// or is "mailto:" or "tel:", treat it as a URI instead of a wiki page.
$is_uri = preg_match('@(^/)|(://)|(^#)|(^(?:mailto|tel):)@', $uri);
if ($is_uri && strncmp('/', $uri, 1) && strncmp('#', $uri, 1)) {
$protocols = $this->getEngine()->getConfig(
'uri.allowed-protocols',
array());
try {
$protocol = id(new PhutilURI($uri))->getProtocol();
if (!idx($protocols, $protocol)) {
// Don't treat this as a URI if it's not an allowed protocol.
$is_uri = false;
}
} catch (Exception $ex) {
// We can end up here if we try to parse an ambiguous URI, see
// T12796.
$is_uri = false;
}
}
// As a special case, skip "[[ / ]]" so that Phriction picks it up as a
// link to the Phriction root. It is more useful to be able to use this
// syntax to link to the root document than the home page of the install.
if ($uri == '/') {
$is_uri = false;
}
if (!$is_uri) {
return $matches[0];
}
return $this->getEngine()->storeText($this->renderHyperlink($uri, $name));
}
}
diff --git a/src/infrastructure/markup/markuprule/PhutilRemarkupEvalRule.php b/src/infrastructure/markup/markuprule/PhutilRemarkupEvalRule.php
index ea20dfedef..cb67041c62 100644
--- a/src/infrastructure/markup/markuprule/PhutilRemarkupEvalRule.php
+++ b/src/infrastructure/markup/markuprule/PhutilRemarkupEvalRule.php
@@ -1,100 +1,100 @@
<?php
final class PhutilRemarkupEvalRule extends PhutilRemarkupRule {
const KEY_EVAL = 'eval';
public function getPriority() {
return 50;
}
public function apply($text) {
return preg_replace_callback(
'/\${{{(.+?)}}}/',
array($this, 'newExpressionToken'),
$text);
}
public function newExpressionToken(array $matches) {
$expression = $matches[1];
if (!$this->isFlatText($expression)) {
return $matches[0];
}
$engine = $this->getEngine();
$token = $engine->storeText($expression);
$list_key = self::KEY_EVAL;
$expression_list = $engine->getTextMetadata($list_key, array());
$expression_list[] = array(
'token' => $token,
'expression' => $expression,
'original' => $matches[0],
);
$engine->setTextMetadata($list_key, $expression_list);
return $token;
}
public function didMarkupText() {
$engine = $this->getEngine();
$list_key = self::KEY_EVAL;
$expression_list = $engine->getTextMetadata($list_key, array());
foreach ($expression_list as $expression_item) {
$token = $expression_item['token'];
$expression = $expression_item['expression'];
$result = $this->evaluateExpression($expression);
if ($result === null) {
$result = $expression_item['original'];
}
$engine->overwriteStoredText($token, $result);
}
}
private function evaluateExpression($expression) {
static $string_map;
if ($string_map === null) {
$string_map = array(
'strings' => array(
'platform' => array(
'server' => array(
- 'name' => pht('Phabricator'),
+ 'name' => PlatformSymbols::getPlatformServerName(),
'path' => pht('phabricator/'),
),
'client' => array(
- 'name' => pht('Arcanist'),
+ 'name' => PlatformSymbols::getPlatformClientName(),
'path' => pht('arcanist/'),
),
),
),
);
}
$parts = explode('.', $expression);
$cursor = $string_map;
foreach ($parts as $part) {
if (isset($cursor[$part])) {
$cursor = $cursor[$part];
} else {
break;
}
}
if (is_string($cursor)) {
return $cursor;
}
return null;
}
}
diff --git a/src/infrastructure/markup/remarkup/PhutilRemarkupEngine.php b/src/infrastructure/markup/remarkup/PhutilRemarkupEngine.php
index a0a379aaf9..bedbf0bab7 100644
--- a/src/infrastructure/markup/remarkup/PhutilRemarkupEngine.php
+++ b/src/infrastructure/markup/remarkup/PhutilRemarkupEngine.php
@@ -1,357 +1,371 @@
<?php
final class PhutilRemarkupEngine extends PhutilMarkupEngine {
const MODE_DEFAULT = 0;
const MODE_TEXT = 1;
const MODE_HTML_MAIL = 2;
const MAX_CHILD_DEPTH = 32;
private $blockRules = array();
private $config = array();
private $mode;
private $metadata = array();
private $states = array();
private $postprocessRules = array();
private $storage;
public function setConfig($key, $value) {
$this->config[$key] = $value;
return $this;
}
public function getConfig($key, $default = null) {
return idx($this->config, $key, $default);
}
public function setMode($mode) {
$this->mode = $mode;
return $this;
}
public function isTextMode() {
return $this->mode & self::MODE_TEXT;
}
public function isAnchorMode() {
return $this->getState('toc');
}
public function isHTMLMailMode() {
return $this->mode & self::MODE_HTML_MAIL;
}
+ public function getQuoteDepth() {
+ return $this->getConfig('runtime.quote.depth', 0);
+ }
+
+ public function setQuoteDepth($depth) {
+ return $this->setConfig('runtime.quote.depth', $depth);
+ }
+
public function setBlockRules(array $rules) {
assert_instances_of($rules, 'PhutilRemarkupBlockRule');
$rules = msortv($rules, 'getPriorityVector');
$this->blockRules = $rules;
foreach ($this->blockRules as $rule) {
$rule->setEngine($this);
}
$post_rules = array();
foreach ($this->blockRules as $block_rule) {
foreach ($block_rule->getMarkupRules() as $rule) {
$key = $rule->getPostprocessKey();
if ($key !== null) {
$post_rules[$key] = $rule;
}
}
}
$this->postprocessRules = $post_rules;
return $this;
}
public function getTextMetadata($key, $default = null) {
if (isset($this->metadata[$key])) {
return $this->metadata[$key];
}
return idx($this->metadata, $key, $default);
}
public function setTextMetadata($key, $value) {
$this->metadata[$key] = $value;
return $this;
}
public function storeText($text) {
if ($this->isTextMode()) {
$text = phutil_safe_html($text);
}
return $this->storage->store($text);
}
public function overwriteStoredText($token, $new_text) {
if ($this->isTextMode()) {
$new_text = phutil_safe_html($new_text);
}
$this->storage->overwrite($token, $new_text);
return $this;
}
public function markupText($text) {
return $this->postprocessText($this->preprocessText($text));
}
public function pushState($state) {
if (empty($this->states[$state])) {
$this->states[$state] = 0;
}
$this->states[$state]++;
return $this;
}
public function popState($state) {
if (empty($this->states[$state])) {
throw new Exception(pht("State '%s' pushed more than popped!", $state));
}
$this->states[$state]--;
if (!$this->states[$state]) {
unset($this->states[$state]);
}
return $this;
}
public function getState($state) {
return !empty($this->states[$state]);
}
public function preprocessText($text) {
$this->metadata = array();
$this->storage = new PhutilRemarkupBlockStorage();
$blocks = $this->splitTextIntoBlocks($text);
$output = array();
foreach ($blocks as $block) {
$output[] = $this->markupBlock($block);
}
$output = $this->flattenOutput($output);
$map = $this->storage->getMap();
$this->storage = null;
$metadata = $this->metadata;
return array(
'output' => $output,
'storage' => $map,
'metadata' => $metadata,
);
}
private function splitTextIntoBlocks($text, $depth = 0) {
// Apply basic block and paragraph normalization to the text. NOTE: We don't
// strip trailing whitespace because it is semantic in some contexts,
// notably inlined diffs that the author intends to show as a code block.
$text = phutil_split_lines($text, true);
$block_rules = $this->blockRules;
$blocks = array();
$cursor = 0;
$can_merge = array();
foreach ($block_rules as $key => $block_rule) {
if ($block_rule instanceof PhutilRemarkupDefaultBlockRule) {
$can_merge[$key] = true;
}
}
$last_block = null;
$last_block_key = -1;
// See T13487. For very large inputs, block separation can dominate
// runtime. This is written somewhat clumsily to attempt to handle
// very large inputs as gracefully as is practical.
while (isset($text[$cursor])) {
$starting_cursor = $cursor;
foreach ($block_rules as $block_key => $block_rule) {
$num_lines = $block_rule->getMatchingLineCount($text, $cursor);
if ($num_lines) {
$current_block = array(
'start' => $cursor,
'num_lines' => $num_lines,
'rule' => $block_rule,
'empty' => self::isEmptyBlock($text, $cursor, $num_lines),
'children' => array(),
'merge' => isset($can_merge[$block_key]),
);
$should_merge = self::shouldMergeParagraphBlocks(
$text,
$last_block,
$current_block);
if ($should_merge) {
$last_block['num_lines'] =
($last_block['num_lines'] + $current_block['num_lines']);
$last_block['empty'] =
($last_block['empty'] && $current_block['empty']);
$blocks[$last_block_key] = $last_block;
} else {
$blocks[] = $current_block;
$last_block = $current_block;
$last_block_key++;
}
$cursor += $num_lines;
break;
}
}
if ($starting_cursor === $cursor) {
throw new Exception(pht('Block in text did not match any block rule.'));
}
}
// See T13487. It's common for blocks to be small, and this loop seems to
// measure as faster if we manually concatenate blocks than if we
// "array_slice()" and "implode()" blocks. This is a bit muddy.
foreach ($blocks as $key => $block) {
$min = $block['start'];
$max = $min + $block['num_lines'];
$lines = '';
for ($ii = $min; $ii < $max; $ii++) {
$lines .= $text[$ii];
}
$blocks[$key]['text'] = $lines;
}
// Stop splitting child blocks apart if we get too deep. This arrests
// any blocks which have looping child rules, and stops the stack from
// exploding if someone writes a hilarious comment with 5,000 levels of
// quoted text.
if ($depth < self::MAX_CHILD_DEPTH) {
foreach ($blocks as $key => $block) {
$rule = $block['rule'];
if (!$rule->supportsChildBlocks()) {
continue;
}
list($parent_text, $child_text) = $rule->extractChildText(
$block['text']);
$blocks[$key]['text'] = $parent_text;
$blocks[$key]['children'] = $this->splitTextIntoBlocks(
$child_text,
$depth + 1);
}
}
return $blocks;
}
private function markupBlock(array $block) {
+ $rule = $block['rule'];
+
+ $rule->willMarkupChildBlocks();
+
$children = array();
foreach ($block['children'] as $child) {
$children[] = $this->markupBlock($child);
}
+ $rule->didMarkupChildBlocks();
+
if ($children) {
$children = $this->flattenOutput($children);
} else {
$children = null;
}
- return $block['rule']->markupText($block['text'], $children);
+ return $rule->markupText($block['text'], $children);
}
private function flattenOutput(array $output) {
if ($this->isTextMode()) {
$output = implode("\n\n", $output)."\n";
} else {
$output = phutil_implode_html("\n\n", $output);
}
return $output;
}
private static function shouldMergeParagraphBlocks(
$text,
$last_block,
$current_block) {
// If we're at the beginning of the input, we can't merge.
if ($last_block === null) {
return false;
}
// If the previous block wasn't a default block, we can't merge.
if (!$last_block['merge']) {
return false;
}
// If the current block isn't a default block, we can't merge.
if (!$current_block['merge']) {
return false;
}
// If the last block was empty, we definitely want to merge.
if ($last_block['empty']) {
return true;
}
// If this block is empty, we definitely want to merge.
if ($current_block['empty']) {
return true;
}
// Check if the last line of the previous block or the first line of this
// block have any non-whitespace text. If they both do, we're going to
// merge.
// If either of them are a blank line or a line with only whitespace, we
// do not merge: this means we've found a paragraph break.
$tail = $text[$current_block['start'] - 1];
$head = $text[$current_block['start']];
if (strlen(trim($tail)) && strlen(trim($head))) {
return true;
}
return false;
}
private static function isEmptyBlock($text, $start, $num_lines) {
for ($cursor = $start; $cursor < $start + $num_lines; $cursor++) {
if (strlen(trim($text[$cursor]))) {
return false;
}
}
return true;
}
public function postprocessText(array $dict) {
$this->metadata = idx($dict, 'metadata', array());
$this->storage = new PhutilRemarkupBlockStorage();
$this->storage->setMap(idx($dict, 'storage', array()));
foreach ($this->blockRules as $block_rule) {
$block_rule->postprocess();
}
foreach ($this->postprocessRules as $rule) {
$rule->didMarkupText();
}
return $this->restoreText(idx($dict, 'output'));
}
public function restoreText($text) {
return $this->storage->restore($text, $this->isTextMode());
}
}
diff --git a/src/infrastructure/markup/render.php b/src/infrastructure/markup/render.php
index 7294fc0ecf..84c3616fe8 100644
--- a/src/infrastructure/markup/render.php
+++ b/src/infrastructure/markup/render.php
@@ -1,183 +1,187 @@
<?php
/**
* Render an HTML tag in a way that treats user content as unsafe by default.
*
* Tag rendering has some special logic which implements security features:
*
* - When rendering `<a>` tags, if the `rel` attribute is not specified, it
* is interpreted as `rel="noreferrer"`.
* - When rendering `<a>` tags, the `href` attribute may not begin with
* `javascript:`.
*
* These special cases can not be disabled.
*
* IMPORTANT: The `$tag` attribute and the keys of the `$attributes` array are
* trusted blindly, and not escaped. You should not pass user data in these
* parameters.
*
* @param string The name of the tag, like `a` or `div`.
* @param map<string, string> A map of tag attributes.
* @param wild Content to put in the tag.
* @return PhutilSafeHTML Tag object.
*/
function phutil_tag($tag, array $attributes = array(), $content = null) {
// If the `href` attribute is present, make sure it is not a "javascript:"
// URI. We never permit these.
if (!empty($attributes['href'])) {
// This might be a URI object, so cast it to a string.
$href = (string)$attributes['href'];
if (isset($href[0])) {
// Block 'javascript:' hrefs at the tag level: no well-designed
// application should ever use them, and they are a potent attack vector.
// This function is deep in the core and performance sensitive, so we're
// doing a cheap version of this test first to avoid calling preg_match()
// on URIs which begin with '/' or `#`. These cover essentially all URIs
// in Phabricator.
if (($href[0] !== '/') && ($href[0] !== '#')) {
// Chrome 33 and IE 11 both interpret "javascript\n:" as a Javascript
// URI, and all browsers interpret " javascript:" as a Javascript URI,
// so be aggressive about looking for "javascript:" in the initial
// section of the string.
$normalized_href = preg_replace('([^a-z0-9/:]+)i', '', $href);
if (preg_match('/^javascript:/i', $normalized_href)) {
throw new Exception(
pht(
"Attempting to render a tag with an '%s' attribute that begins ".
"with '%s'. This is either a serious security concern or a ".
"serious architecture concern. Seek urgent remedy.",
'href',
'javascript:'));
}
}
}
}
// For tags which can't self-close, treat null as the empty string -- for
// example, always render `<div></div>`, never `<div />`.
static $self_closing_tags = array(
'area' => true,
'base' => true,
'br' => true,
'col' => true,
'command' => true,
'embed' => true,
'frame' => true,
'hr' => true,
'img' => true,
'input' => true,
'keygen' => true,
'link' => true,
'meta' => true,
'param' => true,
'source' => true,
'track' => true,
'wbr' => true,
);
$attr_string = '';
foreach ($attributes as $k => $v) {
if ($v === null) {
continue;
}
$v = phutil_escape_html($v);
$attr_string .= ' '.$k.'="'.$v.'"';
}
if ($content === null) {
if (isset($self_closing_tags[$tag])) {
return new PhutilSafeHTML('<'.$tag.$attr_string.' />');
} else {
$content = '';
}
} else {
$content = phutil_escape_html($content);
}
return new PhutilSafeHTML('<'.$tag.$attr_string.'>'.$content.'</'.$tag.'>');
}
function phutil_tag_div($class, $content = null) {
return phutil_tag('div', array('class' => $class), $content);
}
function phutil_escape_html($string) {
+ if ($string === null) {
+ return '';
+ }
+
if ($string instanceof PhutilSafeHTML) {
return $string;
} else if ($string instanceof PhutilSafeHTMLProducerInterface) {
$result = $string->producePhutilSafeHTML();
if ($result instanceof PhutilSafeHTML) {
return phutil_escape_html($result);
} else if (is_array($result)) {
return phutil_escape_html($result);
} else if ($result instanceof PhutilSafeHTMLProducerInterface) {
return phutil_escape_html($result);
} else {
try {
assert_stringlike($result);
return phutil_escape_html((string)$result);
} catch (Exception $ex) {
throw new Exception(
pht(
"Object (of class '%s') implements %s but did not return anything ".
"renderable from %s.",
get_class($string),
'PhutilSafeHTMLProducerInterface',
'producePhutilSafeHTML()'));
}
}
} else if (is_array($string)) {
$result = '';
foreach ($string as $item) {
$result .= phutil_escape_html($item);
}
return $result;
}
return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
}
function phutil_escape_html_newlines($string) {
return PhutilSafeHTML::applyFunction('nl2br', $string);
}
/**
* Mark string as safe for use in HTML.
*/
function phutil_safe_html($string) {
if ($string == '') {
return $string;
} else if ($string instanceof PhutilSafeHTML) {
return $string;
} else {
return new PhutilSafeHTML($string);
}
}
/**
* HTML safe version of `implode()`.
*/
function phutil_implode_html($glue, array $pieces) {
$glue = phutil_escape_html($glue);
foreach ($pieces as $k => $piece) {
$pieces[$k] = phutil_escape_html($piece);
}
return phutil_safe_html(implode($glue, $pieces));
}
/**
* Format a HTML code. This function behaves like `sprintf()`, except that all
* the normal conversions (like %s) will be properly escaped.
*/
function hsprintf($html /* , ... */) {
$args = func_get_args();
array_shift($args);
return new PhutilSafeHTML(
vsprintf($html, array_map('phutil_escape_html', $args)));
}
diff --git a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php
index 7fb997b2c5..b0399527b0 100644
--- a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php
+++ b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php
@@ -1,418 +1,432 @@
<?php
abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule {
private $referencePattern;
private $embedPattern;
const KEY_RULE_OBJECT = 'rule.object';
const KEY_MENTIONED_OBJECTS = 'rule.object.mentioned';
abstract protected function getObjectNamePrefix();
abstract protected function loadObjects(array $ids);
public function getPriority() {
return 450.0;
}
protected function getObjectNamePrefixBeginsWithWordCharacter() {
$prefix = $this->getObjectNamePrefix();
return preg_match('/^\w/', $prefix);
}
protected function getObjectIDPattern() {
return '[1-9]\d*';
}
protected function shouldMarkupObject(array $params) {
return true;
}
protected function getObjectNameText(
$object,
PhabricatorObjectHandle $handle,
$id) {
return $this->getObjectNamePrefix().$id;
}
protected function loadHandles(array $objects) {
$phids = mpull($objects, 'getPHID');
$viewer = $this->getEngine()->getConfig('viewer');
$handles = $viewer->loadHandles($phids);
$handles = iterator_to_array($handles);
$result = array();
foreach ($objects as $id => $object) {
$result[$id] = $handles[$object->getPHID()];
}
return $result;
}
protected function getObjectHref(
$object,
PhabricatorObjectHandle $handle,
$id) {
$uri = $handle->getURI();
if ($this->getEngine()->getConfig('uri.full')) {
$uri = PhabricatorEnv::getURI($uri);
}
return $uri;
}
protected function renderObjectRefForAnyMedia(
$object,
PhabricatorObjectHandle $handle,
$anchor,
$id) {
$href = $this->getObjectHref($object, $handle, $id);
$text = $this->getObjectNameText($object, $handle, $id);
if ($anchor) {
$href = $href.'#'.$anchor;
$text = $text.'#'.$anchor;
}
if ($this->getEngine()->isTextMode()) {
return $text.' <'.PhabricatorEnv::getProductionURI($href).'>';
} else if ($this->getEngine()->isHTMLMailMode()) {
$href = PhabricatorEnv::getProductionURI($href);
return $this->renderObjectTagForMail($text, $href, $handle);
}
return $this->renderObjectRef($object, $handle, $anchor, $id);
}
protected function renderObjectRef(
$object,
PhabricatorObjectHandle $handle,
$anchor,
$id) {
$href = $this->getObjectHref($object, $handle, $id);
$text = $this->getObjectNameText($object, $handle, $id);
$status_closed = PhabricatorObjectHandle::STATUS_CLOSED;
if ($anchor) {
$href = $href.'#'.$anchor;
$text = $text.'#'.$anchor;
}
$attr = array(
'phid' => $handle->getPHID(),
'closed' => ($handle->getStatus() == $status_closed),
);
return $this->renderHovertag($text, $href, $attr);
}
protected function renderObjectEmbedForAnyMedia(
$object,
PhabricatorObjectHandle $handle,
$options) {
$name = $handle->getFullName();
$href = $handle->getURI();
if ($this->getEngine()->isTextMode()) {
return $name.' <'.PhabricatorEnv::getProductionURI($href).'>';
} else if ($this->getEngine()->isHTMLMailMode()) {
$href = PhabricatorEnv::getProductionURI($href);
return $this->renderObjectTagForMail($name, $href, $handle);
}
+ // See T13678. If we're already rendering embedded content, render a
+ // default reference instead to avoid cycles.
+ if (PhabricatorMarkupEngine::isRenderingEmbeddedContent()) {
+ return $this->renderDefaultObjectEmbed($object, $handle);
+ }
+
return $this->renderObjectEmbed($object, $handle, $options);
}
protected function renderObjectEmbed(
$object,
PhabricatorObjectHandle $handle,
$options) {
+ return $this->renderDefaultObjectEmbed($object, $handle);
+ }
+
+ final protected function renderDefaultObjectEmbed(
+ $object,
+ PhabricatorObjectHandle $handle) {
$name = $handle->getFullName();
$href = $handle->getURI();
$status_closed = PhabricatorObjectHandle::STATUS_CLOSED;
$attr = array(
'phid' => $handle->getPHID(),
'closed' => ($handle->getStatus() == $status_closed),
);
return $this->renderHovertag($name, $href, $attr);
}
protected function renderObjectTagForMail(
$text,
$href,
PhabricatorObjectHandle $handle) {
$status_closed = PhabricatorObjectHandle::STATUS_CLOSED;
$strikethrough = $handle->getStatus() == $status_closed ?
'text-decoration: line-through;' :
'text-decoration: none;';
return phutil_tag(
'a',
array(
'href' => $href,
'style' => 'background-color: #e7e7e7;
border-color: #e7e7e7;
border-radius: 3px;
padding: 0 4px;
font-weight: bold;
color: black;'
.$strikethrough,
),
$text);
}
protected function renderHovertag($name, $href, array $attr = array()) {
return id(new PHUITagView())
->setName($name)
->setHref($href)
->setType(PHUITagView::TYPE_OBJECT)
->setPHID(idx($attr, 'phid'))
->setClosed(idx($attr, 'closed'))
->render();
}
public function apply($text) {
$text = preg_replace_callback(
$this->getObjectEmbedPattern(),
array($this, 'markupObjectEmbed'),
$text);
$text = preg_replace_callback(
$this->getObjectReferencePattern(),
array($this, 'markupObjectReference'),
$text);
return $text;
}
private function getObjectEmbedPattern() {
if ($this->embedPattern === null) {
$prefix = $this->getObjectNamePrefix();
$prefix = preg_quote($prefix);
$id = $this->getObjectIDPattern();
$this->embedPattern =
'(\B{'.$prefix.'('.$id.')([,\s](?:[^}\\\\]|\\\\.)*)?}\B)u';
}
return $this->embedPattern;
}
private function getObjectReferencePattern() {
if ($this->referencePattern === null) {
$prefix = $this->getObjectNamePrefix();
$prefix = preg_quote($prefix);
$id = $this->getObjectIDPattern();
// If the prefix starts with a word character (like "D"), we want to
// require a word boundary so that we don't match "XD1" as "D1". If the
// prefix does not start with a word character, we want to require no word
// boundary for the same reasons. Test if the prefix starts with a word
// character.
if ($this->getObjectNamePrefixBeginsWithWordCharacter()) {
$boundary = '\\b';
} else {
$boundary = '\\B';
}
// The "(?<![#@-])" prevents us from linking "#abcdef" or similar, and
// "ABC-T1" (see T5714), and from matching "@T1" as a task (it is a user)
// (see T9479).
// The "\b" allows us to link "(abcdef)" or similar without linking things
// in the middle of words.
$this->referencePattern =
'((?<![#@-])'.$boundary.$prefix.'('.$id.')(?:#([-\w\d]+))?(?!\w))u';
}
return $this->referencePattern;
}
/**
* Extract matched object references from a block of text.
*
* This is intended to make it easy to write unit tests for object remarkup
* rules. Production code is not normally expected to call this method.
*
* @param string Text to match rules against.
* @return wild Matches, suitable for writing unit tests against.
*/
public function extractReferences($text) {
$embed_matches = null;
preg_match_all(
$this->getObjectEmbedPattern(),
$text,
$embed_matches,
PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
$ref_matches = null;
preg_match_all(
$this->getObjectReferencePattern(),
$text,
$ref_matches,
PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
$results = array();
$sets = array(
'embed' => $embed_matches,
'ref' => $ref_matches,
);
foreach ($sets as $type => $matches) {
$formatted = array();
foreach ($matches as $match) {
$format = array(
'offset' => $match[1][1],
'id' => $match[1][0],
);
if (isset($match[2][0])) {
$format['tail'] = $match[2][0];
}
$formatted[] = $format;
}
$results[$type] = $formatted;
}
return $results;
}
public function markupObjectEmbed(array $matches) {
if (!$this->isFlatText($matches[0])) {
return $matches[0];
}
// If we're rendering a table of contents, just render the raw input.
// This could perhaps be handled more gracefully but it seems unusual to
// put something like "{P123}" in a header and it's not obvious what users
// expect? See T8845.
$engine = $this->getEngine();
if ($engine->getState('toc')) {
return $matches[0];
}
return $this->markupObject(array(
'type' => 'embed',
'id' => $matches[1],
'options' => idx($matches, 2),
'original' => $matches[0],
+ 'quote.depth' => $engine->getQuoteDepth(),
));
}
public function markupObjectReference(array $matches) {
if (!$this->isFlatText($matches[0])) {
return $matches[0];
}
// If we're rendering a table of contents, just render the monogram.
$engine = $this->getEngine();
if ($engine->getState('toc')) {
return $matches[0];
}
return $this->markupObject(array(
'type' => 'ref',
'id' => $matches[1],
'anchor' => idx($matches, 2),
'original' => $matches[0],
+ 'quote.depth' => $engine->getQuoteDepth(),
));
}
private function markupObject(array $params) {
if (!$this->shouldMarkupObject($params)) {
return $params['original'];
}
$regex = trim(
PhabricatorEnv::getEnvConfig('remarkup.ignored-object-names'));
if ($regex && preg_match($regex, $params['original'])) {
return $params['original'];
}
$engine = $this->getEngine();
$token = $engine->storeText('x');
$metadata_key = self::KEY_RULE_OBJECT.'.'.$this->getObjectNamePrefix();
$metadata = $engine->getTextMetadata($metadata_key, array());
$metadata[] = array(
'token' => $token,
) + $params;
$engine->setTextMetadata($metadata_key, $metadata);
return $token;
}
public function didMarkupText() {
$engine = $this->getEngine();
$metadata_key = self::KEY_RULE_OBJECT.'.'.$this->getObjectNamePrefix();
$metadata = $engine->getTextMetadata($metadata_key, array());
if (!$metadata) {
return;
}
$ids = ipull($metadata, 'id');
$objects = $this->loadObjects($ids);
// For objects that are invalid or which the user can't see, just render
// the original text.
// TODO: We should probably distinguish between these cases and render a
// "you can't see this" state for nonvisible objects.
foreach ($metadata as $key => $spec) {
if (empty($objects[$spec['id']])) {
$engine->overwriteStoredText(
$spec['token'],
$spec['original']);
unset($metadata[$key]);
}
}
$phids = $engine->getTextMetadata(self::KEY_MENTIONED_OBJECTS, array());
foreach ($objects as $object) {
$phids[$object->getPHID()] = $object->getPHID();
}
$engine->setTextMetadata(self::KEY_MENTIONED_OBJECTS, $phids);
$handles = $this->loadHandles($objects);
foreach ($metadata as $key => $spec) {
$handle = $handles[$spec['id']];
$object = $objects[$spec['id']];
switch ($spec['type']) {
case 'ref':
$view = $this->renderObjectRefForAnyMedia(
$object,
$handle,
$spec['anchor'],
$spec['id']);
break;
case 'embed':
$spec['options'] = $this->assertFlatText($spec['options']);
$view = $this->renderObjectEmbedForAnyMedia(
$object,
$handle,
$spec['options']);
break;
}
$engine->overwriteStoredText($spec['token'], $view);
}
$engine->setTextMetadata($metadata_key, array());
}
}
diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php
index 34a8e1813c..42ccad3316 100644
--- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php
+++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php
@@ -1,3262 +1,3283 @@
<?php
/**
* A query class which uses cursor-based paging. This paging is much more
* performant than offset-based paging in the presence of policy filtering.
*
* @task cursors Query Cursors
* @task clauses Building Query Clauses
* @task appsearch Integration with ApplicationSearch
* @task customfield Integration with CustomField
* @task paging Paging
* @task order Result Ordering
* @task edgelogic Working with Edge Logic
* @task spaces Working with Spaces
*/
abstract class PhabricatorCursorPagedPolicyAwareQuery
extends PhabricatorPolicyAwareQuery {
private $externalCursorString;
private $internalCursorObject;
private $isQueryOrderReversed = false;
private $rawCursorRow;
private $applicationSearchConstraints = array();
private $internalPaging;
private $orderVector;
private $groupVector;
private $builtinOrder;
private $edgeLogicConstraints = array();
private $edgeLogicConstraintsAreValid = false;
private $spacePHIDs;
private $spaceIsArchived;
private $ngrams = array();
private $ferretEngine;
private $ferretTokens = array();
private $ferretTables = array();
private $ferretQuery;
private $ferretMetadata = array();
private $ngramEngine;
const FULLTEXT_RANK = '_ft_rank';
const FULLTEXT_MODIFIED = '_ft_epochModified';
const FULLTEXT_CREATED = '_ft_epochCreated';
/* -( Cursors )------------------------------------------------------------ */
protected function newExternalCursorStringForResult($object) {
if (!($object instanceof LiskDAO)) {
throw new Exception(
pht(
'Expected to be passed a result object of class "LiskDAO" in '.
'"newExternalCursorStringForResult()", actually passed "%s". '.
'Return storage objects from "loadPage()" or override '.
'"newExternalCursorStringForResult()".',
phutil_describe_type($object)));
}
return (string)$object->getID();
}
protected function newInternalCursorFromExternalCursor($cursor) {
$viewer = $this->getViewer();
$query = newv(get_class($this), array());
$query
->setParentQuery($this)
->setViewer($viewer);
// We're copying our order vector to the subquery so that the subquery
// knows it should generate any supplemental information required by the
// ordering.
// For example, Phriction documents may be ordered by title, but the title
// isn't a column in the "document" table: the query must JOIN the
// "content" table to perform the ordering. Passing the ordering to the
// subquery tells it that we need it to do that JOIN and attach relevant
// paging information to the internal cursor object.
// We only expect to load a single result, so the actual result order does
// not matter. We only want the internal cursor for that result to look
// like a cursor this parent query would generate.
$query->setOrderVector($this->getOrderVector());
$this->applyExternalCursorConstraintsToQuery($query, $cursor);
// If we have a Ferret fulltext query, copy it to the subquery so that we
// generate ranking columns appropriately, and compute the correct object
// ranking score for the current query.
if ($this->ferretEngine) {
$query->withFerretConstraint($this->ferretEngine, $this->ferretTokens);
}
// We're executing the subquery normally to make sure the viewer can
// actually see the object, and that it's a completely valid object which
// passes all filtering and policy checks. You aren't allowed to use an
// object you can't see as a cursor, since this can leak information.
$result = $query->executeOne();
if (!$result) {
$this->throwCursorException(
pht(
'Cursor "%s" does not identify a valid object in query "%s".',
$cursor,
get_class($this)));
}
// Now that we made sure the viewer can actually see the object the
// external cursor identifies, return the internal cursor the query
// generated as a side effect while loading the object.
return $query->getInternalCursorObject();
}
final protected function throwCursorException($message) {
throw new PhabricatorInvalidQueryCursorException($message);
}
protected function applyExternalCursorConstraintsToQuery(
PhabricatorCursorPagedPolicyAwareQuery $subquery,
$cursor) {
$subquery->withIDs(array($cursor));
}
protected function newPagingMapFromCursorObject(
PhabricatorQueryCursor $cursor,
array $keys) {
$object = $cursor->getObject();
return $this->newPagingMapFromPartialObject($object);
}
protected function newPagingMapFromPartialObject($object) {
return array(
'id' => (int)$object->getID(),
);
}
private function getExternalCursorStringForResult($object) {
$cursor = $this->newExternalCursorStringForResult($object);
if (!is_string($cursor)) {
throw new Exception(
pht(
'Expected "newExternalCursorStringForResult()" in class "%s" to '.
'return a string, but got "%s".',
get_class($this),
phutil_describe_type($cursor)));
}
return $cursor;
}
final protected function getExternalCursorString() {
return $this->externalCursorString;
}
private function setExternalCursorString($external_cursor) {
$this->externalCursorString = $external_cursor;
return $this;
}
final protected function getIsQueryOrderReversed() {
return $this->isQueryOrderReversed;
}
final protected function setIsQueryOrderReversed($is_reversed) {
$this->isQueryOrderReversed = $is_reversed;
return $this;
}
private function getInternalCursorObject() {
return $this->internalCursorObject;
}
private function setInternalCursorObject(
PhabricatorQueryCursor $cursor) {
$this->internalCursorObject = $cursor;
return $this;
}
private function getInternalCursorFromExternalCursor(
$cursor_string) {
$cursor_object = $this->newInternalCursorFromExternalCursor($cursor_string);
if (!($cursor_object instanceof PhabricatorQueryCursor)) {
throw new Exception(
pht(
'Expected "newInternalCursorFromExternalCursor()" to return an '.
'object of class "PhabricatorQueryCursor", but got "%s" (in '.
'class "%s").',
phutil_describe_type($cursor_object),
get_class($this)));
}
return $cursor_object;
}
private function getPagingMapFromCursorObject(
PhabricatorQueryCursor $cursor,
array $keys) {
$map = $this->newPagingMapFromCursorObject($cursor, $keys);
if (!is_array($map)) {
throw new Exception(
pht(
'Expected "newPagingMapFromCursorObject()" to return a map of '.
'paging values, but got "%s" (in class "%s").',
phutil_describe_type($map),
get_class($this)));
}
if ($this->supportsFerretEngine()) {
if ($this->hasFerretOrder()) {
$map += array(
'rank' =>
$cursor->getRawRowProperty(self::FULLTEXT_RANK),
'fulltext-modified' =>
$cursor->getRawRowProperty(self::FULLTEXT_MODIFIED),
'fulltext-created' =>
$cursor->getRawRowProperty(self::FULLTEXT_CREATED),
);
}
}
foreach ($keys as $key) {
if (!array_key_exists($key, $map)) {
throw new Exception(
pht(
'Map returned by "newPagingMapFromCursorObject()" in class "%s" '.
'omits required key "%s".',
get_class($this),
$key));
}
}
return $map;
}
final protected function nextPage(array $page) {
if (!$page) {
return;
}
$cursor = id(new PhabricatorQueryCursor())
->setObject(last($page));
if ($this->rawCursorRow) {
$cursor->setRawRow($this->rawCursorRow);
}
$this->setInternalCursorObject($cursor);
}
final public function getFerretMetadata() {
if (!$this->supportsFerretEngine()) {
throw new Exception(
pht(
'Unable to retrieve Ferret engine metadata, this class ("%s") does '.
'not support the Ferret engine.',
get_class($this)));
}
return $this->ferretMetadata;
}
+ protected function loadPage() {
+ $object = $this->newResultObject();
+
+ if (!$object instanceof PhabricatorLiskDAO) {
+ throw new Exception(
+ pht(
+ 'Query class ("%s") did not return the correct type of object '.
+ 'from "newResultObject()" (expected a subclass of '.
+ '"PhabricatorLiskDAO", found "%s"). Return an object of the '.
+ 'expected type (this is common), or implement a custom '.
+ '"loadPage()" method (this is unusual in modern code).',
+ get_class($this),
+ phutil_describe_type($object)));
+ }
+
+ return $this->loadStandardPage($object);
+ }
+
protected function loadStandardPage(PhabricatorLiskDAO $table) {
$rows = $this->loadStandardPageRows($table);
return $table->loadAllFromArray($rows);
}
protected function loadStandardPageRows(PhabricatorLiskDAO $table) {
$conn = $table->establishConnection('r');
return $this->loadStandardPageRowsWithConnection(
$conn,
$table->getTableName());
}
protected function loadStandardPageRowsWithConnection(
AphrontDatabaseConnection $conn,
$table_name) {
$query = $this->buildStandardPageQuery($conn, $table_name);
$rows = queryfx_all($conn, '%Q', $query);
$rows = $this->didLoadRawRows($rows);
return $rows;
}
protected function buildStandardPageQuery(
AphrontDatabaseConnection $conn,
$table_name) {
$table_alias = $this->getPrimaryTableAlias();
if ($table_alias === null) {
$table_alias = qsprintf($conn, '');
} else {
$table_alias = qsprintf($conn, '%T', $table_alias);
}
return qsprintf(
$conn,
'%Q FROM %T %Q %Q %Q %Q %Q %Q %Q',
$this->buildSelectClause($conn),
$table_name,
$table_alias,
$this->buildJoinClause($conn),
$this->buildWhereClause($conn),
$this->buildGroupClause($conn),
$this->buildHavingClause($conn),
$this->buildOrderClause($conn),
$this->buildLimitClause($conn));
}
protected function didLoadRawRows(array $rows) {
$this->rawCursorRow = last($rows);
if ($this->ferretEngine) {
foreach ($rows as $row) {
$phid = $row['phid'];
$metadata = id(new PhabricatorFerretMetadata())
->setPHID($phid)
->setEngine($this->ferretEngine)
->setRelevance(idx($row, self::FULLTEXT_RANK));
$this->ferretMetadata[$phid] = $metadata;
unset($row[self::FULLTEXT_RANK]);
unset($row[self::FULLTEXT_MODIFIED]);
unset($row[self::FULLTEXT_CREATED]);
}
}
return $rows;
}
final protected function buildLimitClause(AphrontDatabaseConnection $conn) {
if ($this->shouldLimitResults()) {
$limit = $this->getRawResultLimit();
if ($limit) {
return qsprintf($conn, 'LIMIT %d', $limit);
}
}
return qsprintf($conn, '');
}
protected function shouldLimitResults() {
return true;
}
final protected function didLoadResults(array $results) {
if ($this->getIsQueryOrderReversed()) {
$results = array_reverse($results, $preserve_keys = true);
}
return $results;
}
final public function newIterator() {
return new PhabricatorQueryIterator($this);
}
final public function executeWithCursorPager(AphrontCursorPagerView $pager) {
$limit = $pager->getPageSize();
$this->setLimit($limit + 1);
- if (strlen($pager->getAfterID())) {
- $this->setExternalCursorString($pager->getAfterID());
- } else if ($pager->getBeforeID()) {
- $this->setExternalCursorString($pager->getBeforeID());
+ $after_id = phutil_string_cast($pager->getAfterID());
+ $before_id = phutil_string_cast($pager->getBeforeID());
+
+ if (phutil_nonempty_string($after_id)) {
+ $this->setExternalCursorString($after_id);
+ } else if (phutil_nonempty_string($before_id)) {
+ $this->setExternalCursorString($before_id);
$this->setIsQueryOrderReversed(true);
}
$results = $this->execute();
$count = count($results);
$sliced_results = $pager->sliceResults($results);
if ($sliced_results) {
// If we have results, generate external-facing cursors from the visible
// results. This stops us from leaking any internal details about objects
// which we loaded but which were not visible to the viewer.
if ($pager->getBeforeID() || ($count > $limit)) {
$last_object = last($sliced_results);
$cursor = $this->getExternalCursorStringForResult($last_object);
$pager->setNextPageID($cursor);
}
if ($pager->getAfterID() ||
($pager->getBeforeID() && ($count > $limit))) {
$head_object = head($sliced_results);
$cursor = $this->getExternalCursorStringForResult($head_object);
$pager->setPrevPageID($cursor);
}
}
return $sliced_results;
}
/**
* Return the alias this query uses to identify the primary table.
*
* Some automatic query constructions may need to be qualified with a table
* alias if the query performs joins which make column names ambiguous. If
* this is the case, return the alias for the primary table the query
* uses; generally the object table which has `id` and `phid` columns.
*
* @return string Alias for the primary table.
*/
protected function getPrimaryTableAlias() {
return null;
}
public function newResultObject() {
return null;
}
/* -( Building Query Clauses )--------------------------------------------- */
/**
* @task clauses
*/
protected function buildSelectClause(AphrontDatabaseConnection $conn) {
$parts = $this->buildSelectClauseParts($conn);
return $this->formatSelectClause($conn, $parts);
}
/**
* @task clauses
*/
protected function buildSelectClauseParts(AphrontDatabaseConnection $conn) {
$select = array();
$alias = $this->getPrimaryTableAlias();
if ($alias) {
$select[] = qsprintf($conn, '%T.*', $alias);
} else {
$select[] = qsprintf($conn, '*');
}
$select[] = $this->buildEdgeLogicSelectClause($conn);
$select[] = $this->buildFerretSelectClause($conn);
return $select;
}
/**
* @task clauses
*/
protected function buildJoinClause(AphrontDatabaseConnection $conn) {
$joins = $this->buildJoinClauseParts($conn);
return $this->formatJoinClause($conn, $joins);
}
/**
* @task clauses
*/
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = array();
$joins[] = $this->buildEdgeLogicJoinClause($conn);
$joins[] = $this->buildApplicationSearchJoinClause($conn);
$joins[] = $this->buildNgramsJoinClause($conn);
$joins[] = $this->buildFerretJoinClause($conn);
return $joins;
}
/**
* @task clauses
*/
protected function buildWhereClause(AphrontDatabaseConnection $conn) {
$where = $this->buildWhereClauseParts($conn);
return $this->formatWhereClause($conn, $where);
}
/**
* @task clauses
*/
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = array();
$where[] = $this->buildPagingWhereClause($conn);
$where[] = $this->buildEdgeLogicWhereClause($conn);
$where[] = $this->buildSpacesWhereClause($conn);
$where[] = $this->buildNgramsWhereClause($conn);
$where[] = $this->buildFerretWhereClause($conn);
$where[] = $this->buildApplicationSearchWhereClause($conn);
return $where;
}
/**
* @task clauses
*/
protected function buildHavingClause(AphrontDatabaseConnection $conn) {
$having = $this->buildHavingClauseParts($conn);
$having[] = $this->buildPagingHavingClause($conn);
return $this->formatHavingClause($conn, $having);
}
/**
* @task clauses
*/
protected function buildHavingClauseParts(AphrontDatabaseConnection $conn) {
$having = array();
$having[] = $this->buildEdgeLogicHavingClause($conn);
return $having;
}
/**
* @task clauses
*/
protected function buildGroupClause(AphrontDatabaseConnection $conn) {
if (!$this->shouldGroupQueryResultRows()) {
return qsprintf($conn, '');
}
return qsprintf(
$conn,
'GROUP BY %Q',
$this->getApplicationSearchObjectPHIDColumn($conn));
}
/**
* @task clauses
*/
protected function shouldGroupQueryResultRows() {
if ($this->shouldGroupEdgeLogicResultRows()) {
return true;
}
if ($this->getApplicationSearchMayJoinMultipleRows()) {
return true;
}
if ($this->shouldGroupNgramResultRows()) {
return true;
}
if ($this->shouldGroupFerretResultRows()) {
return true;
}
return false;
}
/* -( Paging )------------------------------------------------------------- */
private function buildPagingWhereClause(AphrontDatabaseConnection $conn) {
if ($this->shouldPageWithHavingClause()) {
return null;
}
return $this->buildPagingClause($conn);
}
private function buildPagingHavingClause(AphrontDatabaseConnection $conn) {
if (!$this->shouldPageWithHavingClause()) {
return null;
}
return $this->buildPagingClause($conn);
}
private function shouldPageWithHavingClause() {
// If any of the paging conditions reference dynamic columns, we need to
// put the paging conditions in a "HAVING" clause instead of a "WHERE"
// clause.
// For example, this happens when paging on the Ferret "rank" column,
// since the "rank" value is computed dynamically in the SELECT statement.
$orderable = $this->getOrderableColumns();
$vector = $this->getOrderVector();
foreach ($vector as $order) {
$key = $order->getOrderKey();
$column = $orderable[$key];
if (!empty($column['having'])) {
return true;
}
}
return false;
}
/**
* @task paging
*/
protected function buildPagingClause(AphrontDatabaseConnection $conn) {
$orderable = $this->getOrderableColumns();
$vector = $this->getQueryableOrderVector();
// If we don't have a cursor object yet, it means we're trying to load
// the first result page. We may need to build a cursor object from the
// external string, or we may not need a paging clause yet.
$cursor_object = $this->getInternalCursorObject();
if (!$cursor_object) {
$external_cursor = $this->getExternalCursorString();
if ($external_cursor !== null) {
$cursor_object = $this->getInternalCursorFromExternalCursor(
$external_cursor);
}
}
// If we still don't have a cursor object, this is the first result page
// and we aren't paging it. We don't need to build a paging clause.
if (!$cursor_object) {
return qsprintf($conn, '');
}
$reversed = $this->getIsQueryOrderReversed();
$keys = array();
foreach ($vector as $order) {
$keys[] = $order->getOrderKey();
}
$keys = array_fuse($keys);
$value_map = $this->getPagingMapFromCursorObject(
$cursor_object,
$keys);
$columns = array();
foreach ($vector as $order) {
$key = $order->getOrderKey();
$column = $orderable[$key];
$column['value'] = $value_map[$key];
// If the vector component is reversed, we need to reverse whatever the
// order of the column is.
if ($order->getIsReversed()) {
$column['reverse'] = !idx($column, 'reverse', false);
}
$columns[] = $column;
}
return $this->buildPagingClauseFromMultipleColumns(
$conn,
$columns,
array(
'reversed' => $reversed,
));
}
/**
* Simplifies the task of constructing a paging clause across multiple
* columns. In the general case, this looks like:
*
* A > a OR (A = a AND B > b) OR (A = a AND B = b AND C > c)
*
* To build a clause, specify the name, type, and value of each column
* to include:
*
* $this->buildPagingClauseFromMultipleColumns(
* $conn_r,
* array(
* array(
* 'table' => 't',
* 'column' => 'title',
* 'type' => 'string',
* 'value' => $cursor->getTitle(),
* 'reverse' => true,
* ),
* array(
* 'table' => 't',
* 'column' => 'id',
* 'type' => 'int',
* 'value' => $cursor->getID(),
* ),
* ),
* array(
* 'reversed' => $is_reversed,
* ));
*
* This method will then return a composable clause for inclusion in WHERE.
*
* @param AphrontDatabaseConnection Connection query will execute on.
* @param list<map> Column description dictionaries.
* @param map Additional construction options.
* @return string Query clause.
* @task paging
*/
final protected function buildPagingClauseFromMultipleColumns(
AphrontDatabaseConnection $conn,
array $columns,
array $options) {
foreach ($columns as $column) {
PhutilTypeSpec::checkMap(
$column,
array(
'table' => 'optional string|null',
'column' => 'string',
'value' => 'wild',
'type' => 'string',
'reverse' => 'optional bool',
'unique' => 'optional bool',
'null' => 'optional string|null',
'requires-ferret' => 'optional bool',
'having' => 'optional bool',
));
}
PhutilTypeSpec::checkMap(
$options,
array(
'reversed' => 'optional bool',
));
$is_query_reversed = idx($options, 'reversed', false);
$clauses = array();
$accumulated = array();
$last_key = last_key($columns);
foreach ($columns as $key => $column) {
$type = $column['type'];
$null = idx($column, 'null');
if ($column['value'] === null) {
if ($null) {
$value = null;
} else {
throw new Exception(
pht(
'Column "%s" has null value, but does not specify a null '.
'behavior.',
$key));
}
} else {
switch ($type) {
case 'int':
$value = qsprintf($conn, '%d', $column['value']);
break;
case 'float':
$value = qsprintf($conn, '%f', $column['value']);
break;
case 'string':
$value = qsprintf($conn, '%s', $column['value']);
break;
default:
throw new Exception(
pht(
'Column "%s" has unknown column type "%s".',
$column['column'],
$type));
}
}
$is_column_reversed = idx($column, 'reverse', false);
$reverse = ($is_query_reversed xor $is_column_reversed);
$clause = $accumulated;
$table_name = idx($column, 'table');
$column_name = $column['column'];
if ($table_name !== null) {
$field = qsprintf($conn, '%T.%T', $table_name, $column_name);
} else {
$field = qsprintf($conn, '%T', $column_name);
}
$parts = array();
if ($null) {
$can_page_if_null = ($null === 'head');
$can_page_if_nonnull = ($null === 'tail');
if ($reverse) {
$can_page_if_null = !$can_page_if_null;
$can_page_if_nonnull = !$can_page_if_nonnull;
}
$subclause = null;
if ($can_page_if_null && $value === null) {
$parts[] = qsprintf(
$conn,
'(%Q IS NOT NULL)',
$field);
} else if ($can_page_if_nonnull && $value !== null) {
$parts[] = qsprintf(
$conn,
'(%Q IS NULL)',
$field);
}
}
if ($value !== null) {
$parts[] = qsprintf(
$conn,
'%Q %Q %Q',
$field,
$reverse ? qsprintf($conn, '>') : qsprintf($conn, '<'),
$value);
}
if ($parts) {
$clause[] = qsprintf($conn, '%LO', $parts);
}
if ($clause) {
$clauses[] = qsprintf($conn, '%LA', $clause);
}
if ($value === null) {
$accumulated[] = qsprintf(
$conn,
'%Q IS NULL',
$field);
} else {
$accumulated[] = qsprintf(
$conn,
'%Q = %Q',
$field,
$value);
}
}
if ($clauses) {
return qsprintf($conn, '%LO', $clauses);
}
return qsprintf($conn, '');
}
/* -( Result Ordering )---------------------------------------------------- */
/**
* Select a result ordering.
*
* This is a high-level method which selects an ordering from a predefined
* list of builtin orders, as provided by @{method:getBuiltinOrders}. These
* options are user-facing and not exhaustive, but are generally convenient
* and meaningful.
*
* You can also use @{method:setOrderVector} to specify a low-level ordering
* across individual orderable columns. This offers greater control but is
* also more involved.
*
* @param string Key of a builtin order supported by this query.
* @return this
* @task order
*/
public function setOrder($order) {
$aliases = $this->getBuiltinOrderAliasMap();
if (empty($aliases[$order])) {
throw new Exception(
pht(
'Query "%s" does not support a builtin order "%s". Supported orders '.
'are: %s.',
get_class($this),
$order,
implode(', ', array_keys($aliases))));
}
$this->builtinOrder = $aliases[$order];
$this->orderVector = null;
return $this;
}
/**
* Set a grouping order to apply before primary result ordering.
*
* This allows you to preface the query order vector with additional orders,
* so you can effect "group by" queries while still respecting "order by".
*
* This is a high-level method which works alongside @{method:setOrder}. For
* lower-level control over order vectors, use @{method:setOrderVector}.
*
* @param PhabricatorQueryOrderVector|list<string> List of order keys.
* @return this
* @task order
*/
public function setGroupVector($vector) {
$this->groupVector = $vector;
$this->orderVector = null;
return $this;
}
/**
* Get builtin orders for this class.
*
* In application UIs, we want to be able to present users with a small
* selection of meaningful order options (like "Order by Title") rather than
* an exhaustive set of column ordering options.
*
* Meaningful user-facing orders are often really orders across multiple
* columns: for example, a "title" ordering is usually implemented as a
* "title, id" ordering under the hood.
*
* Builtin orders provide a mapping from convenient, understandable
* user-facing orders to implementations.
*
* A builtin order should provide these keys:
*
* - `vector` (`list<string>`): The actual order vector to use.
* - `name` (`string`): Human-readable order name.
*
* @return map<string, wild> Map from builtin order keys to specification.
* @task order
*/
public function getBuiltinOrders() {
$orders = array(
'newest' => array(
'vector' => array('id'),
'name' => pht('Creation (Newest First)'),
'aliases' => array('created'),
),
'oldest' => array(
'vector' => array('-id'),
'name' => pht('Creation (Oldest First)'),
),
);
$object = $this->newResultObject();
if ($object instanceof PhabricatorCustomFieldInterface) {
$list = PhabricatorCustomField::getObjectFields(
$object,
PhabricatorCustomField::ROLE_APPLICATIONSEARCH);
foreach ($list->getFields() as $field) {
$index = $field->buildOrderIndex();
if (!$index) {
continue;
}
$legacy_key = 'custom:'.$field->getFieldKey();
$modern_key = $field->getModernFieldKey();
$orders[$modern_key] = array(
'vector' => array($modern_key, 'id'),
'name' => $field->getFieldName(),
'aliases' => array($legacy_key),
);
$orders['-'.$modern_key] = array(
'vector' => array('-'.$modern_key, '-id'),
'name' => pht('%s (Reversed)', $field->getFieldName()),
);
}
}
if ($this->supportsFerretEngine()) {
$orders['relevance'] = array(
'vector' => array('rank', 'fulltext-modified', 'id'),
'name' => pht('Relevance'),
);
}
return $orders;
}
public function getBuiltinOrderAliasMap() {
$orders = $this->getBuiltinOrders();
$map = array();
foreach ($orders as $key => $order) {
$keys = array();
$keys[] = $key;
foreach (idx($order, 'aliases', array()) as $alias) {
$keys[] = $alias;
}
foreach ($keys as $alias) {
if (isset($map[$alias])) {
throw new Exception(
pht(
'Two builtin orders ("%s" and "%s") define the same key or '.
'alias ("%s"). Each order alias and key must be unique and '.
'identify a single order.',
$key,
$map[$alias],
$alias));
}
$map[$alias] = $key;
}
}
return $map;
}
/**
* Set a low-level column ordering.
*
* This is a low-level method which offers granular control over column
* ordering. In most cases, applications can more easily use
* @{method:setOrder} to choose a high-level builtin order.
*
* To set an order vector, specify a list of order keys as provided by
* @{method:getOrderableColumns}.
*
* @param PhabricatorQueryOrderVector|list<string> List of order keys.
* @return this
* @task order
*/
public function setOrderVector($vector) {
$vector = PhabricatorQueryOrderVector::newFromVector($vector);
$orderable = $this->getOrderableColumns();
// Make sure that all the components identify valid columns.
$unique = array();
foreach ($vector as $order) {
$key = $order->getOrderKey();
if (empty($orderable[$key])) {
$valid = implode(', ', array_keys($orderable));
throw new Exception(
pht(
'This query ("%s") does not support sorting by order key "%s". '.
'Supported orders are: %s.',
get_class($this),
$key,
$valid));
}
$unique[$key] = idx($orderable[$key], 'unique', false);
}
// Make sure that the last column is unique so that this is a strong
// ordering which can be used for paging.
$last = last($unique);
if ($last !== true) {
throw new Exception(
pht(
'Order vector "%s" is invalid: the last column in an order must '.
'be a column with unique values, but "%s" is not unique.',
$vector->getAsString(),
last_key($unique)));
}
// Make sure that other columns are not unique; an ordering like "id, name"
// does not make sense because only "id" can ever have an effect.
array_pop($unique);
foreach ($unique as $key => $is_unique) {
if ($is_unique) {
throw new Exception(
pht(
'Order vector "%s" is invalid: only the last column in an order '.
'may be unique, but "%s" is a unique column and not the last '.
'column in the order.',
$vector->getAsString(),
$key));
}
}
$this->orderVector = $vector;
return $this;
}
/**
* Get the effective order vector.
*
* @return PhabricatorQueryOrderVector Effective vector.
* @task order
*/
protected function getOrderVector() {
if (!$this->orderVector) {
if ($this->builtinOrder !== null) {
$builtin_order = idx($this->getBuiltinOrders(), $this->builtinOrder);
$vector = $builtin_order['vector'];
} else {
$vector = $this->getDefaultOrderVector();
}
if ($this->groupVector) {
$group = PhabricatorQueryOrderVector::newFromVector($this->groupVector);
$group->appendVector($vector);
$vector = $group;
}
$vector = PhabricatorQueryOrderVector::newFromVector($vector);
// We call setOrderVector() here to apply checks to the default vector.
// This catches any errors in the implementation.
$this->setOrderVector($vector);
}
return $this->orderVector;
}
/**
* @task order
*/
protected function getDefaultOrderVector() {
return array('id');
}
/**
* @task order
*/
public function getOrderableColumns() {
$cache = PhabricatorCaches::getRequestCache();
$class = get_class($this);
$cache_key = 'query.orderablecolumns.'.$class;
$columns = $cache->getKey($cache_key);
if ($columns !== null) {
return $columns;
}
$columns = array(
'id' => array(
'table' => $this->getPrimaryTableAlias(),
'column' => 'id',
'reverse' => false,
'type' => 'int',
'unique' => true,
),
);
$object = $this->newResultObject();
if ($object instanceof PhabricatorCustomFieldInterface) {
$list = PhabricatorCustomField::getObjectFields(
$object,
PhabricatorCustomField::ROLE_APPLICATIONSEARCH);
foreach ($list->getFields() as $field) {
$index = $field->buildOrderIndex();
if (!$index) {
continue;
}
$digest = $field->getFieldIndex();
$key = $field->getModernFieldKey();
$columns[$key] = array(
'table' => 'appsearch_order_'.$digest,
'column' => 'indexValue',
'type' => $index->getIndexValueType(),
'null' => 'tail',
'customfield' => true,
'customfield.index.table' => $index->getTableName(),
'customfield.index.key' => $digest,
);
}
}
if ($this->supportsFerretEngine()) {
$columns['rank'] = array(
'table' => null,
'column' => self::FULLTEXT_RANK,
'type' => 'int',
'requires-ferret' => true,
'having' => true,
);
$columns['fulltext-created'] = array(
'table' => null,
'column' => self::FULLTEXT_CREATED,
'type' => 'int',
'requires-ferret' => true,
);
$columns['fulltext-modified'] = array(
'table' => null,
'column' => self::FULLTEXT_MODIFIED,
'type' => 'int',
'requires-ferret' => true,
);
}
$cache->setKey($cache_key, $columns);
return $columns;
}
/**
* @task order
*/
final protected function buildOrderClause(
AphrontDatabaseConnection $conn,
$for_union = false) {
$orderable = $this->getOrderableColumns();
$vector = $this->getQueryableOrderVector();
$parts = array();
foreach ($vector as $order) {
$part = $orderable[$order->getOrderKey()];
if ($order->getIsReversed()) {
$part['reverse'] = !idx($part, 'reverse', false);
}
$parts[] = $part;
}
return $this->formatOrderClause($conn, $parts, $for_union);
}
/**
* @task order
*/
private function getQueryableOrderVector() {
$vector = $this->getOrderVector();
$orderable = $this->getOrderableColumns();
$keep = array();
foreach ($vector as $order) {
$column = $orderable[$order->getOrderKey()];
// If this is a Ferret fulltext column but the query doesn't actually
// have a fulltext query, we'll skip most of the Ferret stuff and won't
// actually have the columns in the result set. Just skip them.
if (!empty($column['requires-ferret'])) {
if (!$this->getFerretTokens()) {
continue;
}
}
$keep[] = $order->getAsScalar();
}
return PhabricatorQueryOrderVector::newFromVector($keep);
}
/**
* @task order
*/
protected function formatOrderClause(
AphrontDatabaseConnection $conn,
array $parts,
$for_union = false) {
$is_query_reversed = $this->getIsQueryOrderReversed();
$sql = array();
foreach ($parts as $key => $part) {
$is_column_reversed = !empty($part['reverse']);
$descending = true;
if ($is_query_reversed) {
$descending = !$descending;
}
if ($is_column_reversed) {
$descending = !$descending;
}
$table = idx($part, 'table');
// When we're building an ORDER BY clause for a sequence of UNION
// statements, we can't refer to tables from the subqueries.
if ($for_union) {
$table = null;
}
$column = $part['column'];
if ($table !== null) {
$field = qsprintf($conn, '%T.%T', $table, $column);
} else {
$field = qsprintf($conn, '%T', $column);
}
$null = idx($part, 'null');
if ($null) {
switch ($null) {
case 'head':
$null_field = qsprintf($conn, '(%Q IS NULL)', $field);
break;
case 'tail':
$null_field = qsprintf($conn, '(%Q IS NOT NULL)', $field);
break;
default:
throw new Exception(
pht(
'NULL value "%s" is invalid. Valid values are "head" and '.
'"tail".',
$null));
}
if ($descending) {
$sql[] = qsprintf($conn, '%Q DESC', $null_field);
} else {
$sql[] = qsprintf($conn, '%Q ASC', $null_field);
}
}
if ($descending) {
$sql[] = qsprintf($conn, '%Q DESC', $field);
} else {
$sql[] = qsprintf($conn, '%Q ASC', $field);
}
}
return qsprintf($conn, 'ORDER BY %LQ', $sql);
}
/* -( Application Search )------------------------------------------------- */
/**
* Constrain the query with an ApplicationSearch index, requiring field values
* contain at least one of the values in a set.
*
* This constraint can build the most common types of queries, like:
*
* - Find users with shirt sizes "X" or "XL".
* - Find shoes with size "13".
*
* @param PhabricatorCustomFieldIndexStorage Table where the index is stored.
* @param string|list<string> One or more values to filter by.
* @return this
* @task appsearch
*/
public function withApplicationSearchContainsConstraint(
PhabricatorCustomFieldIndexStorage $index,
$value) {
$values = (array)$value;
$data_values = array();
$constraint_values = array();
foreach ($values as $value) {
if ($value instanceof PhabricatorQueryConstraint) {
$constraint_values[] = $value;
} else {
$data_values[] = $value;
}
}
$alias = 'appsearch_'.count($this->applicationSearchConstraints);
$this->applicationSearchConstraints[] = array(
'type' => $index->getIndexValueType(),
'cond' => '=',
'table' => $index->getTableName(),
'index' => $index->getIndexKey(),
'alias' => $alias,
'value' => $values,
'data' => $data_values,
'constraints' => $constraint_values,
);
return $this;
}
/**
* Constrain the query with an ApplicationSearch index, requiring values
* exist in a given range.
*
* This constraint is useful for expressing date ranges:
*
* - Find events between July 1st and July 7th.
*
* The ends of the range are inclusive, so a `$min` of `3` and a `$max` of
* `5` will match fields with values `3`, `4`, or `5`. Providing `null` for
* either end of the range will leave that end of the constraint open.
*
* @param PhabricatorCustomFieldIndexStorage Table where the index is stored.
* @param int|null Minimum permissible value, inclusive.
* @param int|null Maximum permissible value, inclusive.
* @return this
* @task appsearch
*/
public function withApplicationSearchRangeConstraint(
PhabricatorCustomFieldIndexStorage $index,
$min,
$max) {
$index_type = $index->getIndexValueType();
if ($index_type != 'int') {
throw new Exception(
pht(
'Attempting to apply a range constraint to a field with index type '.
'"%s", expected type "%s".',
$index_type,
'int'));
}
$alias = 'appsearch_'.count($this->applicationSearchConstraints);
$this->applicationSearchConstraints[] = array(
'type' => $index->getIndexValueType(),
'cond' => 'range',
'table' => $index->getTableName(),
'index' => $index->getIndexKey(),
'alias' => $alias,
'value' => array($min, $max),
'data' => null,
'constraints' => null,
);
return $this;
}
/**
* Get the name of the query's primary object PHID column, for constructing
* JOIN clauses. Normally (and by default) this is just `"phid"`, but it may
* be something more exotic.
*
* See @{method:getPrimaryTableAlias} if the column needs to be qualified with
* a table alias.
*
* @param AphrontDatabaseConnection Connection executing queries.
* @return PhutilQueryString Column name.
* @task appsearch
*/
protected function getApplicationSearchObjectPHIDColumn(
AphrontDatabaseConnection $conn) {
if ($this->getPrimaryTableAlias()) {
return qsprintf($conn, '%T.phid', $this->getPrimaryTableAlias());
} else {
return qsprintf($conn, 'phid');
}
}
/**
* Determine if the JOINs built by ApplicationSearch might cause each primary
* object to return multiple result rows. Generally, this means the query
* needs an extra GROUP BY clause.
*
* @return bool True if the query may return multiple rows for each object.
* @task appsearch
*/
protected function getApplicationSearchMayJoinMultipleRows() {
foreach ($this->applicationSearchConstraints as $constraint) {
$type = $constraint['type'];
$value = $constraint['value'];
$cond = $constraint['cond'];
switch ($cond) {
case '=':
switch ($type) {
case 'string':
case 'int':
if (count($value) > 1) {
return true;
}
break;
default:
throw new Exception(pht('Unknown index type "%s"!', $type));
}
break;
case 'range':
// NOTE: It's possible to write a custom field where multiple rows
// match a range constraint, but we don't currently ship any in the
// upstream and I can't immediately come up with cases where this
// would make sense.
break;
default:
throw new Exception(pht('Unknown constraint condition "%s"!', $cond));
}
}
return false;
}
/**
* Construct a GROUP BY clause appropriate for ApplicationSearch constraints.
*
* @param AphrontDatabaseConnection Connection executing the query.
* @return string Group clause.
* @task appsearch
*/
protected function buildApplicationSearchGroupClause(
AphrontDatabaseConnection $conn) {
if ($this->getApplicationSearchMayJoinMultipleRows()) {
return qsprintf(
$conn,
'GROUP BY %Q',
$this->getApplicationSearchObjectPHIDColumn($conn));
} else {
return qsprintf($conn, '');
}
}
/**
* Construct a JOIN clause appropriate for applying ApplicationSearch
* constraints.
*
* @param AphrontDatabaseConnection Connection executing the query.
* @return string Join clause.
* @task appsearch
*/
protected function buildApplicationSearchJoinClause(
AphrontDatabaseConnection $conn) {
$joins = array();
foreach ($this->applicationSearchConstraints as $key => $constraint) {
$table = $constraint['table'];
$alias = $constraint['alias'];
$index = $constraint['index'];
$cond = $constraint['cond'];
$phid_column = $this->getApplicationSearchObjectPHIDColumn($conn);
switch ($cond) {
case '=':
// Figure out whether we need to do a LEFT JOIN or not. We need to
// LEFT JOIN if we're going to select "IS NULL" rows.
$join_type = qsprintf($conn, 'JOIN');
foreach ($constraint['constraints'] as $query_constraint) {
$op = $query_constraint->getOperator();
if ($op === PhabricatorQueryConstraint::OPERATOR_NULL) {
$join_type = qsprintf($conn, 'LEFT JOIN');
break;
}
}
$joins[] = qsprintf(
$conn,
'%Q %T %T ON %T.objectPHID = %Q
AND %T.indexKey = %s',
$join_type,
$table,
$alias,
$alias,
$phid_column,
$alias,
$index);
break;
case 'range':
list($min, $max) = $constraint['value'];
if (($min === null) && ($max === null)) {
// If there's no actual range constraint, just move on.
break;
}
if ($min === null) {
$constraint_clause = qsprintf(
$conn,
'%T.indexValue <= %d',
$alias,
$max);
} else if ($max === null) {
$constraint_clause = qsprintf(
$conn,
'%T.indexValue >= %d',
$alias,
$min);
} else {
$constraint_clause = qsprintf(
$conn,
'%T.indexValue BETWEEN %d AND %d',
$alias,
$min,
$max);
}
$joins[] = qsprintf(
$conn,
'JOIN %T %T ON %T.objectPHID = %Q
AND %T.indexKey = %s
AND (%Q)',
$table,
$alias,
$alias,
$phid_column,
$alias,
$index,
$constraint_clause);
break;
default:
throw new Exception(pht('Unknown constraint condition "%s"!', $cond));
}
}
$phid_column = $this->getApplicationSearchObjectPHIDColumn($conn);
$orderable = $this->getOrderableColumns();
$vector = $this->getOrderVector();
foreach ($vector as $order) {
$spec = $orderable[$order->getOrderKey()];
if (empty($spec['customfield'])) {
continue;
}
$table = $spec['customfield.index.table'];
$alias = $spec['table'];
$key = $spec['customfield.index.key'];
$joins[] = qsprintf(
$conn,
'LEFT JOIN %T %T ON %T.objectPHID = %Q
AND %T.indexKey = %s',
$table,
$alias,
$alias,
$phid_column,
$alias,
$key);
}
if ($joins) {
return qsprintf($conn, '%LJ', $joins);
} else {
return qsprintf($conn, '');
}
}
/**
* Construct a WHERE clause appropriate for applying ApplicationSearch
* constraints.
*
* @param AphrontDatabaseConnection Connection executing the query.
* @return list<string> Where clause parts.
* @task appsearch
*/
protected function buildApplicationSearchWhereClause(
AphrontDatabaseConnection $conn) {
$where = array();
foreach ($this->applicationSearchConstraints as $key => $constraint) {
$alias = $constraint['alias'];
$cond = $constraint['cond'];
$type = $constraint['type'];
$data_values = $constraint['data'];
$constraint_values = $constraint['constraints'];
$constraint_parts = array();
switch ($cond) {
case '=':
if ($data_values) {
switch ($type) {
case 'string':
$constraint_parts[] = qsprintf(
$conn,
'%T.indexValue IN (%Ls)',
$alias,
$data_values);
break;
case 'int':
$constraint_parts[] = qsprintf(
$conn,
'%T.indexValue IN (%Ld)',
$alias,
$data_values);
break;
default:
throw new Exception(pht('Unknown index type "%s"!', $type));
}
}
if ($constraint_values) {
foreach ($constraint_values as $value) {
$op = $value->getOperator();
switch ($op) {
case PhabricatorQueryConstraint::OPERATOR_NULL:
$constraint_parts[] = qsprintf(
$conn,
'%T.indexValue IS NULL',
$alias);
break;
case PhabricatorQueryConstraint::OPERATOR_ANY:
$constraint_parts[] = qsprintf(
$conn,
'%T.indexValue IS NOT NULL',
$alias);
break;
default:
throw new Exception(
pht(
'No support for applying operator "%s" against '.
'index of type "%s".',
$op,
$type));
}
}
}
if ($constraint_parts) {
$where[] = qsprintf($conn, '%LO', $constraint_parts);
}
break;
}
}
return $where;
}
/* -( Integration with CustomField )--------------------------------------- */
/**
* @task customfield
*/
protected function getPagingValueMapForCustomFields(
PhabricatorCustomFieldInterface $object) {
// We have to get the current field values on the cursor object.
$fields = PhabricatorCustomField::getObjectFields(
$object,
PhabricatorCustomField::ROLE_APPLICATIONSEARCH);
$fields->setViewer($this->getViewer());
$fields->readFieldsFromStorage($object);
$map = array();
foreach ($fields->getFields() as $field) {
$map['custom:'.$field->getFieldKey()] = $field->getValueForStorage();
}
return $map;
}
/**
* @task customfield
*/
protected function isCustomFieldOrderKey($key) {
$prefix = 'custom:';
return !strncmp($key, $prefix, strlen($prefix));
}
/* -( Ferret )------------------------------------------------------------- */
public function supportsFerretEngine() {
$object = $this->newResultObject();
return ($object instanceof PhabricatorFerretInterface);
}
public function withFerretQuery(
PhabricatorFerretEngine $engine,
PhabricatorSavedQuery $query) {
if (!$this->supportsFerretEngine()) {
throw new Exception(
pht(
'Query ("%s") does not support the Ferret fulltext engine.',
get_class($this)));
}
$this->ferretEngine = $engine;
$this->ferretQuery = $query;
return $this;
}
public function getFerretTokens() {
if (!$this->supportsFerretEngine()) {
throw new Exception(
pht(
'Query ("%s") does not support the Ferret fulltext engine.',
get_class($this)));
}
return $this->ferretTokens;
}
public function withFerretConstraint(
PhabricatorFerretEngine $engine,
array $fulltext_tokens) {
if (!$this->supportsFerretEngine()) {
throw new Exception(
pht(
'Query ("%s") does not support the Ferret fulltext engine.',
get_class($this)));
}
if ($this->ferretEngine) {
throw new Exception(
pht(
'Query may not have multiple fulltext constraints.'));
}
if (!$fulltext_tokens) {
return $this;
}
$this->ferretEngine = $engine;
$this->ferretTokens = $fulltext_tokens;
$op_absent = PhutilSearchQueryCompiler::OPERATOR_ABSENT;
$default_function = $engine->getDefaultFunctionKey();
$table_map = array();
$idx = 1;
foreach ($this->ferretTokens as $fulltext_token) {
$raw_token = $fulltext_token->getToken();
$function = $raw_token->getFunction();
if ($function === null) {
$function = $default_function;
}
$function_def = $engine->getFunctionForName($function);
// NOTE: The query compiler guarantees that a query can not make a
// field both "present" and "absent", so it's safe to just use the
// first operator we encounter to determine whether the table is
// optional or not.
$operator = $raw_token->getOperator();
$is_optional = ($operator === $op_absent);
if (!isset($table_map[$function])) {
$alias = 'ftfield_'.$idx++;
$table_map[$function] = array(
'alias' => $alias,
'function' => $function_def,
'optional' => $is_optional,
);
}
}
// Join the title field separately so we can rank results.
$table_map['rank'] = array(
'alias' => 'ft_rank',
'function' => $engine->getFunctionForName('title'),
// See T13345. Not every document has a title, so we want to LEFT JOIN
// this table to avoid excluding documents with no title that match
// the query in other fields.
'optional' => true,
);
$this->ferretTables = $table_map;
return $this;
}
protected function buildFerretSelectClause(AphrontDatabaseConnection $conn) {
$select = array();
if (!$this->supportsFerretEngine()) {
return $select;
}
if (!$this->hasFerretOrder()) {
// We only need to SELECT the virtual rank/relevance columns if we're
// actually sorting the results by rank.
return $select;
}
if (!$this->ferretEngine) {
$select[] = qsprintf($conn, '0 AS %T', self::FULLTEXT_RANK);
$select[] = qsprintf($conn, '0 AS %T', self::FULLTEXT_CREATED);
$select[] = qsprintf($conn, '0 AS %T', self::FULLTEXT_MODIFIED);
return $select;
}
$engine = $this->ferretEngine;
$stemmer = $engine->newStemmer();
$op_sub = PhutilSearchQueryCompiler::OPERATOR_SUBSTRING;
$op_not = PhutilSearchQueryCompiler::OPERATOR_NOT;
$table_alias = 'ft_rank';
$parts = array();
foreach ($this->ferretTokens as $fulltext_token) {
$raw_token = $fulltext_token->getToken();
$value = $raw_token->getValue();
if ($raw_token->getOperator() == $op_not) {
// Ignore "not" terms when ranking, since they aren't useful.
continue;
}
if ($raw_token->getOperator() == $op_sub) {
$is_substring = true;
} else {
$is_substring = false;
}
if ($is_substring) {
$parts[] = qsprintf(
$conn,
'IF(%T.rawCorpus LIKE %~, 2, 0)',
$table_alias,
$value);
continue;
}
if ($raw_token->isQuoted()) {
$is_quoted = true;
$is_stemmed = false;
} else {
$is_quoted = false;
$is_stemmed = true;
}
$term_constraints = array();
$term_value = $engine->newTermsCorpus($value);
$parts[] = qsprintf(
$conn,
'IF(%T.termCorpus LIKE %~, 2, 0)',
$table_alias,
$term_value);
if ($is_stemmed) {
$stem_value = $stemmer->stemToken($value);
$stem_value = $engine->newTermsCorpus($stem_value);
$parts[] = qsprintf(
$conn,
'IF(%T.normalCorpus LIKE %~, 1, 0)',
$table_alias,
$stem_value);
}
}
$parts[] = qsprintf($conn, '%d', 0);
$sum = array_shift($parts);
foreach ($parts as $part) {
$sum = qsprintf(
$conn,
'%Q + %Q',
$sum,
$part);
}
$select[] = qsprintf(
$conn,
'%Q AS %T',
$sum,
self::FULLTEXT_RANK);
// See D20297. We select these as real columns in the result set so that
// constructions like this will work:
//
// ((SELECT ...) UNION (SELECT ...)) ORDER BY ...
//
// If the columns aren't part of the result set, the final "ORDER BY" can
// not act on them.
$select[] = qsprintf(
$conn,
'ft_doc.epochCreated AS %T',
self::FULLTEXT_CREATED);
$select[] = qsprintf(
$conn,
'ft_doc.epochModified AS %T',
self::FULLTEXT_MODIFIED);
return $select;
}
protected function buildFerretJoinClause(AphrontDatabaseConnection $conn) {
if (!$this->ferretEngine) {
return array();
}
$op_sub = PhutilSearchQueryCompiler::OPERATOR_SUBSTRING;
$op_not = PhutilSearchQueryCompiler::OPERATOR_NOT;
$op_absent = PhutilSearchQueryCompiler::OPERATOR_ABSENT;
$op_present = PhutilSearchQueryCompiler::OPERATOR_PRESENT;
$engine = $this->ferretEngine;
$stemmer = $engine->newStemmer();
$ngram_table = $engine->getNgramsTableName();
$ngram_engine = $this->getNgramEngine();
$flat = array();
foreach ($this->ferretTokens as $fulltext_token) {
$raw_token = $fulltext_token->getToken();
$operator = $raw_token->getOperator();
// If this is a negated term like "-pomegranate", don't join the ngram
// table since we aren't looking for documents with this term. (We could
// LEFT JOIN the table and require a NULL row, but this is probably more
// trouble than it's worth.)
if ($operator === $op_not) {
continue;
}
// Neither the "present" or "absent" operators benefit from joining
// the ngram table.
if ($operator === $op_absent || $operator === $op_present) {
continue;
}
$value = $raw_token->getValue();
$length = count(phutil_utf8v($value));
if ($raw_token->getOperator() == $op_sub) {
$is_substring = true;
} else {
$is_substring = false;
}
// If the user specified a substring query for a substring which is
// shorter than the ngram length, we can't use the ngram index, so
// don't do a join. We'll fall back to just doing LIKE on the full
// corpus.
if ($is_substring) {
if ($length < 3) {
continue;
}
}
if ($raw_token->isQuoted()) {
$is_stemmed = false;
} else {
$is_stemmed = true;
}
if ($is_substring) {
$ngrams = $ngram_engine->getSubstringNgramsFromString($value);
} else {
$terms_value = $engine->newTermsCorpus($value);
$ngrams = $ngram_engine->getTermNgramsFromString($terms_value);
// If this is a stemmed term, only look for ngrams present in both the
// unstemmed and stemmed variations.
if ($is_stemmed) {
// Trim the boundary space characters so the stemmer recognizes this
// is (or, at least, may be) a normal word and activates.
$terms_value = trim($terms_value, ' ');
$stem_value = $stemmer->stemToken($terms_value);
$stem_ngrams = $ngram_engine->getTermNgramsFromString($stem_value);
$ngrams = array_intersect($ngrams, $stem_ngrams);
}
}
foreach ($ngrams as $ngram) {
$flat[] = array(
'table' => $ngram_table,
'ngram' => $ngram,
);
}
}
// Remove common ngrams, like "the", which occur too frequently in
// documents to be useful in constraining the query. The best ngrams
// are obscure sequences which occur in very few documents.
if ($flat) {
$common_ngrams = queryfx_all(
$conn,
'SELECT ngram FROM %T WHERE ngram IN (%Ls)',
$engine->getCommonNgramsTableName(),
ipull($flat, 'ngram'));
$common_ngrams = ipull($common_ngrams, 'ngram', 'ngram');
foreach ($flat as $key => $spec) {
$ngram = $spec['ngram'];
if (isset($common_ngrams[$ngram])) {
unset($flat[$key]);
continue;
}
// NOTE: MySQL discards trailing whitespace in CHAR(X) columns.
$trim_ngram = rtrim($ngram, ' ');
if (isset($common_ngrams[$trim_ngram])) {
unset($flat[$key]);
continue;
}
}
}
// MySQL only allows us to join a maximum of 61 tables per query. Each
// ngram is going to cost us a join toward that limit, so if the user
// specified a very long query string, just pick 16 of the ngrams
// at random.
if (count($flat) > 16) {
shuffle($flat);
$flat = array_slice($flat, 0, 16);
}
$alias = $this->getPrimaryTableAlias();
if ($alias) {
$phid_column = qsprintf($conn, '%T.%T', $alias, 'phid');
} else {
$phid_column = qsprintf($conn, '%T', 'phid');
}
$document_table = $engine->getDocumentTableName();
$field_table = $engine->getFieldTableName();
$joins = array();
$joins[] = qsprintf(
$conn,
'JOIN %T ft_doc ON ft_doc.objectPHID = %Q',
$document_table,
$phid_column);
$idx = 1;
foreach ($flat as $spec) {
$table = $spec['table'];
$ngram = $spec['ngram'];
$alias = 'ftngram_'.$idx++;
$joins[] = qsprintf(
$conn,
'JOIN %T %T ON %T.documentID = ft_doc.id AND %T.ngram = %s',
$table,
$alias,
$alias,
$alias,
$ngram);
}
$object = $this->newResultObject();
if (!$object) {
throw new Exception(
pht(
'Query class ("%s") must define "newResultObject()" to use '.
'Ferret constraints.',
get_class($this)));
}
// See T13511. If we have a fulltext query which uses valid field
// functions, but at least one of the functions applies to a field which
// the object can never have, the query can never match anything. Detect
// this and return an empty result set.
// (Even if the query is "field is absent" or "field does not contain
// such-and-such", the interpretation is that these constraints are
// not meaningful when applied to an object which can never have the
// field.)
$functions = ipull($this->ferretTables, 'function');
$functions = mpull($functions, null, 'getFerretFunctionName');
foreach ($functions as $function) {
if (!$function->supportsObject($object)) {
throw new PhabricatorEmptyQueryException(
pht(
'This query uses a fulltext function which this document '.
'type does not support.'));
}
}
foreach ($this->ferretTables as $table) {
$alias = $table['alias'];
if (empty($table['optional'])) {
$join_type = qsprintf($conn, 'JOIN');
} else {
$join_type = qsprintf($conn, 'LEFT JOIN');
}
$joins[] = qsprintf(
$conn,
'%Q %T %T ON ft_doc.id = %T.documentID
AND %T.fieldKey = %s',
$join_type,
$field_table,
$alias,
$alias,
$alias,
$table['function']->getFerretFieldKey());
}
return $joins;
}
protected function buildFerretWhereClause(AphrontDatabaseConnection $conn) {
if (!$this->ferretEngine) {
return array();
}
$engine = $this->ferretEngine;
$stemmer = $engine->newStemmer();
$table_map = $this->ferretTables;
$op_sub = PhutilSearchQueryCompiler::OPERATOR_SUBSTRING;
$op_not = PhutilSearchQueryCompiler::OPERATOR_NOT;
$op_exact = PhutilSearchQueryCompiler::OPERATOR_EXACT;
$op_absent = PhutilSearchQueryCompiler::OPERATOR_ABSENT;
$op_present = PhutilSearchQueryCompiler::OPERATOR_PRESENT;
$where = array();
$default_function = $engine->getDefaultFunctionKey();
foreach ($this->ferretTokens as $fulltext_token) {
$raw_token = $fulltext_token->getToken();
$value = $raw_token->getValue();
$function = $raw_token->getFunction();
if ($function === null) {
$function = $default_function;
}
$operator = $raw_token->getOperator();
$table_alias = $table_map[$function]['alias'];
// If this is a "field is present" operator, we've already implicitly
// guaranteed this by JOINing the table. We don't need to do any
// more work.
$is_present = ($operator === $op_present);
if ($is_present) {
continue;
}
// If this is a "field is absent" operator, we just want documents
// which failed to match to a row when we LEFT JOINed the table. This
// means there's no index for the field.
$is_absent = ($operator === $op_absent);
if ($is_absent) {
$where[] = qsprintf(
$conn,
'(%T.rawCorpus IS NULL)',
$table_alias);
continue;
}
$is_not = ($operator === $op_not);
if ($operator == $op_sub) {
$is_substring = true;
} else {
$is_substring = false;
}
// If we're doing exact search, just test the raw corpus.
$is_exact = ($operator === $op_exact);
if ($is_exact) {
if ($is_not) {
$where[] = qsprintf(
$conn,
'(%T.rawCorpus != %s)',
$table_alias,
$value);
} else {
$where[] = qsprintf(
$conn,
'(%T.rawCorpus = %s)',
$table_alias,
$value);
}
continue;
}
// If we're doing substring search, we just match against the raw corpus
// and we're done.
if ($is_substring) {
if ($is_not) {
$where[] = qsprintf(
$conn,
'(%T.rawCorpus NOT LIKE %~)',
$table_alias,
$value);
} else {
$where[] = qsprintf(
$conn,
'(%T.rawCorpus LIKE %~)',
$table_alias,
$value);
}
continue;
}
// Otherwise, we need to match against the term corpus and the normal
// corpus, so that searching for "raw" does not find "strawberry".
if ($raw_token->isQuoted()) {
$is_quoted = true;
$is_stemmed = false;
} else {
$is_quoted = false;
$is_stemmed = true;
}
// Never stem negated queries, since this can exclude results users
// did not mean to exclude and generally confuse things.
if ($is_not) {
$is_stemmed = false;
}
$term_constraints = array();
$term_value = $engine->newTermsCorpus($value);
if ($is_not) {
$term_constraints[] = qsprintf(
$conn,
'(%T.termCorpus NOT LIKE %~)',
$table_alias,
$term_value);
} else {
$term_constraints[] = qsprintf(
$conn,
'(%T.termCorpus LIKE %~)',
$table_alias,
$term_value);
}
if ($is_stemmed) {
$stem_value = $stemmer->stemToken($value);
$stem_value = $engine->newTermsCorpus($stem_value);
$term_constraints[] = qsprintf(
$conn,
'(%T.normalCorpus LIKE %~)',
$table_alias,
$stem_value);
}
if ($is_not) {
$where[] = qsprintf(
$conn,
'%LA',
$term_constraints);
} else if ($is_quoted) {
$where[] = qsprintf(
$conn,
'(%T.rawCorpus LIKE %~ AND %LO)',
$table_alias,
$value,
$term_constraints);
} else {
$where[] = qsprintf(
$conn,
'%LO',
$term_constraints);
}
}
if ($this->ferretQuery) {
$query = $this->ferretQuery;
$author_phids = $query->getParameter('authorPHIDs');
if ($author_phids) {
$where[] = qsprintf(
$conn,
'ft_doc.authorPHID IN (%Ls)',
$author_phids);
}
$with_unowned = $query->getParameter('withUnowned');
$with_any = $query->getParameter('withAnyOwner');
if ($with_any && $with_unowned) {
throw new PhabricatorEmptyQueryException(
pht(
'This query matches only unowned documents owned by anyone, '.
'which is impossible.'));
}
$owner_phids = $query->getParameter('ownerPHIDs');
if ($owner_phids && !$with_any) {
if ($with_unowned) {
$where[] = qsprintf(
$conn,
'ft_doc.ownerPHID IN (%Ls) OR ft_doc.ownerPHID IS NULL',
$owner_phids);
} else {
$where[] = qsprintf(
$conn,
'ft_doc.ownerPHID IN (%Ls)',
$owner_phids);
}
} else if ($with_unowned) {
$where[] = qsprintf(
$conn,
'ft_doc.ownerPHID IS NULL');
}
if ($with_any) {
$where[] = qsprintf(
$conn,
'ft_doc.ownerPHID IS NOT NULL');
}
$rel_open = PhabricatorSearchRelationship::RELATIONSHIP_OPEN;
$statuses = $query->getParameter('statuses');
$is_closed = null;
if ($statuses) {
$statuses = array_fuse($statuses);
if (count($statuses) == 1) {
if (isset($statuses[$rel_open])) {
$is_closed = 0;
} else {
$is_closed = 1;
}
}
}
if ($is_closed !== null) {
$where[] = qsprintf(
$conn,
'ft_doc.isClosed = %d',
$is_closed);
}
}
return $where;
}
protected function shouldGroupFerretResultRows() {
return (bool)$this->ferretTokens;
}
/* -( Ngrams )------------------------------------------------------------- */
protected function withNgramsConstraint(
PhabricatorSearchNgrams $index,
$value) {
if (strlen($value)) {
$this->ngrams[] = array(
'index' => $index,
'value' => $value,
'length' => count(phutil_utf8v($value)),
);
}
return $this;
}
protected function buildNgramsJoinClause(AphrontDatabaseConnection $conn) {
$ngram_engine = $this->getNgramEngine();
$flat = array();
foreach ($this->ngrams as $spec) {
$length = $spec['length'];
if ($length < 3) {
continue;
}
$index = $spec['index'];
$value = $spec['value'];
$ngrams = $ngram_engine->getSubstringNgramsFromString($value);
foreach ($ngrams as $ngram) {
$flat[] = array(
'table' => $index->getTableName(),
'ngram' => $ngram,
);
}
}
if (!$flat) {
return array();
}
// MySQL only allows us to join a maximum of 61 tables per query. Each
// ngram is going to cost us a join toward that limit, so if the user
// specified a very long query string, just pick 16 of the ngrams
// at random.
if (count($flat) > 16) {
shuffle($flat);
$flat = array_slice($flat, 0, 16);
}
$alias = $this->getPrimaryTableAlias();
if ($alias) {
$id_column = qsprintf($conn, '%T.%T', $alias, 'id');
} else {
$id_column = qsprintf($conn, '%T', 'id');
}
$idx = 1;
$joins = array();
foreach ($flat as $spec) {
$table = $spec['table'];
$ngram = $spec['ngram'];
$alias = 'ngm'.$idx++;
$joins[] = qsprintf(
$conn,
'JOIN %T %T ON %T.objectID = %Q AND %T.ngram = %s',
$table,
$alias,
$alias,
$id_column,
$alias,
$ngram);
}
return $joins;
}
protected function buildNgramsWhereClause(AphrontDatabaseConnection $conn) {
$where = array();
$ngram_engine = $this->getNgramEngine();
foreach ($this->ngrams as $ngram) {
$index = $ngram['index'];
$value = $ngram['value'];
$column = $index->getColumnName();
$alias = $this->getPrimaryTableAlias();
if ($alias) {
$column = qsprintf($conn, '%T.%T', $alias, $column);
} else {
$column = qsprintf($conn, '%T', $column);
}
$tokens = $ngram_engine->tokenizeNgramString($value);
foreach ($tokens as $token) {
$where[] = qsprintf(
$conn,
'%Q LIKE %~',
$column,
$token);
}
}
return $where;
}
protected function shouldGroupNgramResultRows() {
return (bool)$this->ngrams;
}
private function getNgramEngine() {
if (!$this->ngramEngine) {
$this->ngramEngine = new PhabricatorSearchNgramEngine();
}
return $this->ngramEngine;
}
/* -( Edge Logic )--------------------------------------------------------- */
/**
* Convenience method for specifying edge logic constraints with a list of
* PHIDs.
*
* @param const Edge constant.
* @param const Constraint operator.
* @param list<phid> List of PHIDs.
* @return this
* @task edgelogic
*/
public function withEdgeLogicPHIDs($edge_type, $operator, array $phids) {
$constraints = array();
foreach ($phids as $phid) {
$constraints[] = new PhabricatorQueryConstraint($operator, $phid);
}
return $this->withEdgeLogicConstraints($edge_type, $constraints);
}
/**
* @return this
* @task edgelogic
*/
public function withEdgeLogicConstraints($edge_type, array $constraints) {
assert_instances_of($constraints, 'PhabricatorQueryConstraint');
$constraints = mgroup($constraints, 'getOperator');
foreach ($constraints as $operator => $list) {
foreach ($list as $item) {
$this->edgeLogicConstraints[$edge_type][$operator][] = $item;
}
}
$this->edgeLogicConstraintsAreValid = false;
return $this;
}
/**
* @task edgelogic
*/
public function buildEdgeLogicSelectClause(AphrontDatabaseConnection $conn) {
$select = array();
$this->validateEdgeLogicConstraints();
foreach ($this->edgeLogicConstraints as $type => $constraints) {
foreach ($constraints as $operator => $list) {
$alias = $this->getEdgeLogicTableAlias($operator, $type);
switch ($operator) {
case PhabricatorQueryConstraint::OPERATOR_AND:
if (count($list) > 1) {
$select[] = qsprintf(
$conn,
'COUNT(DISTINCT(%T.dst)) %T',
$alias,
$this->buildEdgeLogicTableAliasCount($alias));
}
break;
case PhabricatorQueryConstraint::OPERATOR_ANCESTOR:
// This is tricky. We have a query which specifies multiple
// projects, each of which may have an arbitrarily large number
// of descendants.
// Suppose the projects are "Engineering" and "Operations", and
// "Engineering" has subprojects X, Y and Z.
// We first use `FIELD(dst, X, Y, Z)` to produce a 0 if a row
// is not part of Engineering at all, or some number other than
// 0 if it is.
// Then we use `IF(..., idx, NULL)` to convert the 0 to a NULL and
// any other value to an index (say, 1) for the ancestor.
// We build these up for every ancestor, then use `COALESCE(...)`
// to select the non-null one, giving us an ancestor which this
// row is a member of.
// From there, we use `COUNT(DISTINCT(...))` to make sure that
// each result row is a member of all ancestors.
if (count($list) > 1) {
$idx = 1;
$parts = array();
foreach ($list as $constraint) {
$parts[] = qsprintf(
$conn,
'IF(FIELD(%T.dst, %Ls) != 0, %d, NULL)',
$alias,
(array)$constraint->getValue(),
$idx++);
}
$parts = qsprintf($conn, '%LQ', $parts);
$select[] = qsprintf(
$conn,
'COUNT(DISTINCT(COALESCE(%Q))) %T',
$parts,
$this->buildEdgeLogicTableAliasAncestor($alias));
}
break;
default:
break;
}
}
}
return $select;
}
/**
* @task edgelogic
*/
public function buildEdgeLogicJoinClause(AphrontDatabaseConnection $conn) {
$edge_table = PhabricatorEdgeConfig::TABLE_NAME_EDGE;
$phid_column = $this->getApplicationSearchObjectPHIDColumn($conn);
$joins = array();
foreach ($this->edgeLogicConstraints as $type => $constraints) {
$op_null = PhabricatorQueryConstraint::OPERATOR_NULL;
$has_null = isset($constraints[$op_null]);
// If we're going to process an only() operator, build a list of the
// acceptable set of PHIDs first. We'll only match results which have
// no edges to any other PHIDs.
$all_phids = array();
if (isset($constraints[PhabricatorQueryConstraint::OPERATOR_ONLY])) {
foreach ($constraints as $operator => $list) {
switch ($operator) {
case PhabricatorQueryConstraint::OPERATOR_ANCESTOR:
case PhabricatorQueryConstraint::OPERATOR_AND:
case PhabricatorQueryConstraint::OPERATOR_OR:
foreach ($list as $constraint) {
$value = (array)$constraint->getValue();
foreach ($value as $v) {
$all_phids[$v] = $v;
}
}
break;
}
}
}
foreach ($constraints as $operator => $list) {
$alias = $this->getEdgeLogicTableAlias($operator, $type);
$phids = array();
foreach ($list as $constraint) {
$value = (array)$constraint->getValue();
foreach ($value as $v) {
$phids[$v] = $v;
}
}
$phids = array_keys($phids);
switch ($operator) {
case PhabricatorQueryConstraint::OPERATOR_NOT:
$joins[] = qsprintf(
$conn,
'LEFT JOIN %T %T ON %Q = %T.src AND %T.type = %d
AND %T.dst IN (%Ls)',
$edge_table,
$alias,
$phid_column,
$alias,
$alias,
$type,
$alias,
$phids);
break;
case PhabricatorQueryConstraint::OPERATOR_ANCESTOR:
case PhabricatorQueryConstraint::OPERATOR_AND:
case PhabricatorQueryConstraint::OPERATOR_OR:
// If we're including results with no matches, we have to degrade
// this to a LEFT join. We'll use WHERE to select matching rows
// later.
if ($has_null) {
$join_type = qsprintf($conn, 'LEFT');
} else {
$join_type = qsprintf($conn, '');
}
$joins[] = qsprintf(
$conn,
'%Q JOIN %T %T ON %Q = %T.src AND %T.type = %d
AND %T.dst IN (%Ls)',
$join_type,
$edge_table,
$alias,
$phid_column,
$alias,
$alias,
$type,
$alias,
$phids);
break;
case PhabricatorQueryConstraint::OPERATOR_NULL:
$joins[] = qsprintf(
$conn,
'LEFT JOIN %T %T ON %Q = %T.src AND %T.type = %d',
$edge_table,
$alias,
$phid_column,
$alias,
$alias,
$type);
break;
case PhabricatorQueryConstraint::OPERATOR_ONLY:
$joins[] = qsprintf(
$conn,
'LEFT JOIN %T %T ON %Q = %T.src AND %T.type = %d
AND %T.dst NOT IN (%Ls)',
$edge_table,
$alias,
$phid_column,
$alias,
$alias,
$type,
$alias,
$all_phids);
break;
}
}
}
return $joins;
}
/**
* @task edgelogic
*/
public function buildEdgeLogicWhereClause(AphrontDatabaseConnection $conn) {
$where = array();
foreach ($this->edgeLogicConstraints as $type => $constraints) {
$full = array();
$null = array();
$op_null = PhabricatorQueryConstraint::OPERATOR_NULL;
$has_null = isset($constraints[$op_null]);
foreach ($constraints as $operator => $list) {
$alias = $this->getEdgeLogicTableAlias($operator, $type);
switch ($operator) {
case PhabricatorQueryConstraint::OPERATOR_NOT:
case PhabricatorQueryConstraint::OPERATOR_ONLY:
$full[] = qsprintf(
$conn,
'%T.dst IS NULL',
$alias);
break;
case PhabricatorQueryConstraint::OPERATOR_AND:
case PhabricatorQueryConstraint::OPERATOR_OR:
if ($has_null) {
$full[] = qsprintf(
$conn,
'%T.dst IS NOT NULL',
$alias);
}
break;
case PhabricatorQueryConstraint::OPERATOR_NULL:
$null[] = qsprintf(
$conn,
'%T.dst IS NULL',
$alias);
break;
}
}
if ($full && $null) {
$where[] = qsprintf($conn, '(%LA OR %LA)', $full, $null);
} else if ($full) {
foreach ($full as $condition) {
$where[] = $condition;
}
} else if ($null) {
foreach ($null as $condition) {
$where[] = $condition;
}
}
}
return $where;
}
/**
* @task edgelogic
*/
public function buildEdgeLogicHavingClause(AphrontDatabaseConnection $conn) {
$having = array();
foreach ($this->edgeLogicConstraints as $type => $constraints) {
foreach ($constraints as $operator => $list) {
$alias = $this->getEdgeLogicTableAlias($operator, $type);
switch ($operator) {
case PhabricatorQueryConstraint::OPERATOR_AND:
if (count($list) > 1) {
$having[] = qsprintf(
$conn,
'%T = %d',
$this->buildEdgeLogicTableAliasCount($alias),
count($list));
}
break;
case PhabricatorQueryConstraint::OPERATOR_ANCESTOR:
if (count($list) > 1) {
$having[] = qsprintf(
$conn,
'%T = %d',
$this->buildEdgeLogicTableAliasAncestor($alias),
count($list));
}
break;
}
}
}
return $having;
}
/**
* @task edgelogic
*/
public function shouldGroupEdgeLogicResultRows() {
foreach ($this->edgeLogicConstraints as $type => $constraints) {
foreach ($constraints as $operator => $list) {
switch ($operator) {
case PhabricatorQueryConstraint::OPERATOR_NOT:
case PhabricatorQueryConstraint::OPERATOR_AND:
case PhabricatorQueryConstraint::OPERATOR_OR:
if (count($list) > 1) {
return true;
}
break;
case PhabricatorQueryConstraint::OPERATOR_ANCESTOR:
// NOTE: We must always group query results rows when using an
// "ANCESTOR" operator because a single task may be related to
// two different descendants of a particular ancestor. For
// discussion, see T12753.
return true;
case PhabricatorQueryConstraint::OPERATOR_NULL:
case PhabricatorQueryConstraint::OPERATOR_ONLY:
return true;
}
}
}
return false;
}
/**
* @task edgelogic
*/
private function getEdgeLogicTableAlias($operator, $type) {
return 'edgelogic_'.$operator.'_'.$type;
}
/**
* @task edgelogic
*/
private function buildEdgeLogicTableAliasCount($alias) {
return $alias.'_count';
}
/**
* @task edgelogic
*/
private function buildEdgeLogicTableAliasAncestor($alias) {
return $alias.'_ancestor';
}
/**
* Select certain edge logic constraint values.
*
* @task edgelogic
*/
protected function getEdgeLogicValues(
array $edge_types,
array $operators) {
$values = array();
$constraint_lists = $this->edgeLogicConstraints;
if ($edge_types) {
$constraint_lists = array_select_keys($constraint_lists, $edge_types);
}
foreach ($constraint_lists as $type => $constraints) {
if ($operators) {
$constraints = array_select_keys($constraints, $operators);
}
foreach ($constraints as $operator => $list) {
foreach ($list as $constraint) {
$value = (array)$constraint->getValue();
foreach ($value as $v) {
$values[] = $v;
}
}
}
}
return $values;
}
/**
* Validate edge logic constraints for the query.
*
* @return this
* @task edgelogic
*/
private function validateEdgeLogicConstraints() {
if ($this->edgeLogicConstraintsAreValid) {
return $this;
}
foreach ($this->edgeLogicConstraints as $type => $constraints) {
foreach ($constraints as $operator => $list) {
switch ($operator) {
case PhabricatorQueryConstraint::OPERATOR_EMPTY:
throw new PhabricatorEmptyQueryException(
pht('This query specifies an empty constraint.'));
}
}
}
// This should probably be more modular, eventually, but we only do
// project-based edge logic today.
$project_phids = $this->getEdgeLogicValues(
array(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
),
array(
PhabricatorQueryConstraint::OPERATOR_AND,
PhabricatorQueryConstraint::OPERATOR_OR,
PhabricatorQueryConstraint::OPERATOR_NOT,
PhabricatorQueryConstraint::OPERATOR_ANCESTOR,
));
if ($project_phids) {
$projects = id(new PhabricatorProjectQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($project_phids)
->execute();
$projects = mpull($projects, null, 'getPHID');
foreach ($project_phids as $phid) {
if (empty($projects[$phid])) {
throw new PhabricatorEmptyQueryException(
pht(
'This query is constrained by a project you do not have '.
'permission to see.'));
}
}
}
$op_and = PhabricatorQueryConstraint::OPERATOR_AND;
$op_or = PhabricatorQueryConstraint::OPERATOR_OR;
$op_ancestor = PhabricatorQueryConstraint::OPERATOR_ANCESTOR;
foreach ($this->edgeLogicConstraints as $type => $constraints) {
foreach ($constraints as $operator => $list) {
switch ($operator) {
case PhabricatorQueryConstraint::OPERATOR_ONLY:
if (count($list) > 1) {
throw new PhabricatorEmptyQueryException(
pht(
'This query specifies only() more than once.'));
}
$have_and = idx($constraints, $op_and);
$have_or = idx($constraints, $op_or);
$have_ancestor = idx($constraints, $op_ancestor);
if (!$have_and && !$have_or && !$have_ancestor) {
throw new PhabricatorEmptyQueryException(
pht(
'This query specifies only(), but no other constraints '.
'which it can apply to.'));
}
break;
}
}
}
$this->edgeLogicConstraintsAreValid = true;
return $this;
}
/* -( Spaces )------------------------------------------------------------- */
/**
* Constrain the query to return results from only specific Spaces.
*
* Pass a list of Space PHIDs, or `null` to represent the default space. Only
* results in those Spaces will be returned.
*
* Queries are always constrained to include only results from spaces the
* viewer has access to.
*
* @param list<phid|null>
* @task spaces
*/
public function withSpacePHIDs(array $space_phids) {
$object = $this->newResultObject();
if (!$object) {
throw new Exception(
pht(
'This query (of class "%s") does not implement newResultObject(), '.
'but must implement this method to enable support for Spaces.',
get_class($this)));
}
if (!($object instanceof PhabricatorSpacesInterface)) {
throw new Exception(
pht(
'This query (of class "%s") returned an object of class "%s" from '.
'getNewResultObject(), but it does not implement the required '.
'interface ("%s"). Objects must implement this interface to enable '.
'Spaces support.',
get_class($this),
get_class($object),
'PhabricatorSpacesInterface'));
}
$this->spacePHIDs = $space_phids;
return $this;
}
public function withSpaceIsArchived($archived) {
$this->spaceIsArchived = $archived;
return $this;
}
/**
* Constrain the query to include only results in valid Spaces.
*
* This method builds part of a WHERE clause which considers the spaces the
* viewer has access to see with any explicit constraint on spaces added by
* @{method:withSpacePHIDs}.
*
* @param AphrontDatabaseConnection Database connection.
* @return string Part of a WHERE clause.
* @task spaces
*/
private function buildSpacesWhereClause(AphrontDatabaseConnection $conn) {
$object = $this->newResultObject();
if (!$object) {
return null;
}
if (!($object instanceof PhabricatorSpacesInterface)) {
return null;
}
$viewer = $this->getViewer();
// If we have an omnipotent viewer and no formal space constraints, don't
// emit a clause. This primarily enables older migrations to run cleanly,
// without fataling because they try to match a `spacePHID` column which
// does not exist yet. See T8743, T8746.
if ($viewer->isOmnipotent()) {
if ($this->spaceIsArchived === null && $this->spacePHIDs === null) {
return null;
}
}
// See T13240. If this query raises policy exceptions, don't filter objects
// in the MySQL layer. We want them to reach the application layer so we
// can reject them and raise an exception.
if ($this->shouldRaisePolicyExceptions()) {
return null;
}
$space_phids = array();
$include_null = false;
$all = PhabricatorSpacesNamespaceQuery::getAllSpaces();
if (!$all) {
// If there are no spaces at all, implicitly give the viewer access to
// the default space.
$include_null = true;
} else {
// Otherwise, give them access to the spaces they have permission to
// see.
$viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces(
$viewer);
foreach ($viewer_spaces as $viewer_space) {
if ($this->spaceIsArchived !== null) {
if ($viewer_space->getIsArchived() != $this->spaceIsArchived) {
continue;
}
}
$phid = $viewer_space->getPHID();
$space_phids[$phid] = $phid;
if ($viewer_space->getIsDefaultNamespace()) {
$include_null = true;
}
}
}
// If we have additional explicit constraints, evaluate them now.
if ($this->spacePHIDs !== null) {
$explicit = array();
$explicit_null = false;
foreach ($this->spacePHIDs as $phid) {
if ($phid === null) {
$space = PhabricatorSpacesNamespaceQuery::getDefaultSpace();
} else {
$space = idx($all, $phid);
}
if ($space) {
$phid = $space->getPHID();
$explicit[$phid] = $phid;
if ($space->getIsDefaultNamespace()) {
$explicit_null = true;
}
}
}
// If the viewer can see the default space but it isn't on the explicit
// list of spaces to query, don't match it.
if ($include_null && !$explicit_null) {
$include_null = false;
}
// Include only the spaces common to the viewer and the constraints.
$space_phids = array_intersect_key($space_phids, $explicit);
}
if (!$space_phids && !$include_null) {
if ($this->spacePHIDs === null) {
throw new PhabricatorEmptyQueryException(
pht('You do not have access to any spaces.'));
} else {
throw new PhabricatorEmptyQueryException(
pht(
'You do not have access to any of the spaces this query '.
'is constrained to.'));
}
}
$alias = $this->getPrimaryTableAlias();
if ($alias) {
$col = qsprintf($conn, '%T.spacePHID', $alias);
} else {
$col = qsprintf($conn, 'spacePHID');
}
if ($space_phids && $include_null) {
return qsprintf(
$conn,
'(%Q IN (%Ls) OR %Q IS NULL)',
$col,
$space_phids,
$col);
} else if ($space_phids) {
return qsprintf(
$conn,
'%Q IN (%Ls)',
$col,
$space_phids);
} else {
return qsprintf(
$conn,
'%Q IS NULL',
$col);
}
}
private function hasFerretOrder() {
$vector = $this->getOrderVector();
if ($vector->containsKey('rank')) {
return true;
}
if ($vector->containsKey('fulltext-created')) {
return true;
}
if ($vector->containsKey('fulltext-modified')) {
return true;
}
return false;
}
}
diff --git a/src/infrastructure/storage/connection/mysql/AphrontBaseMySQLDatabaseConnection.php b/src/infrastructure/storage/connection/mysql/AphrontBaseMySQLDatabaseConnection.php
index db52eebe8e..6ff9b3ba90 100644
--- a/src/infrastructure/storage/connection/mysql/AphrontBaseMySQLDatabaseConnection.php
+++ b/src/infrastructure/storage/connection/mysql/AphrontBaseMySQLDatabaseConnection.php
@@ -1,424 +1,425 @@
<?php
abstract class AphrontBaseMySQLDatabaseConnection
extends AphrontDatabaseConnection {
private $configuration;
private $connection;
private $connectionPool = array();
private $lastResult;
private $nextError;
const CALLERROR_QUERY = 777777;
const CALLERROR_CONNECT = 777778;
abstract protected function connect();
abstract protected function rawQuery($raw_query);
abstract protected function rawQueries(array $raw_queries);
abstract protected function fetchAssoc($result);
abstract protected function getErrorCode($connection);
abstract protected function getErrorDescription($connection);
abstract protected function closeConnection();
abstract protected function freeResult($result);
public function __construct(array $configuration) {
$this->configuration = $configuration;
}
public function __clone() {
$this->establishConnection();
}
public function openConnection() {
$this->requireConnection();
}
public function close() {
if ($this->lastResult) {
$this->lastResult = null;
}
if ($this->connection) {
$this->closeConnection();
$this->connection = null;
}
}
public function escapeColumnName($name) {
return '`'.str_replace('`', '``', $name).'`';
}
public function escapeMultilineComment($comment) {
// These can either terminate a comment, confuse the hell out of the parser,
// make MySQL execute the comment as a query, or, in the case of semicolon,
// are quasi-dangerous because the semicolon could turn a broken query into
// a working query plus an ignored query.
static $map = array(
'--' => '(DOUBLEDASH)',
'*/' => '(STARSLASH)',
'//' => '(SLASHSLASH)',
'#' => '(HASH)',
'!' => '(BANG)',
';' => '(SEMICOLON)',
);
$comment = str_replace(
array_keys($map),
array_values($map),
$comment);
// For good measure, kill anything else that isn't a nice printable
// character.
$comment = preg_replace('/[^\x20-\x7F]+/', ' ', $comment);
return '/* '.$comment.' */';
}
public function escapeStringForLikeClause($value) {
+ $value = phutil_string_cast($value);
$value = addcslashes($value, '\%_');
$value = $this->escapeUTF8String($value);
return $value;
}
protected function getConfiguration($key, $default = null) {
return idx($this->configuration, $key, $default);
}
private function establishConnection() {
$host = $this->getConfiguration('host');
$database = $this->getConfiguration('database');
$profiler = PhutilServiceProfiler::getInstance();
$call_id = $profiler->beginServiceCall(
array(
'type' => 'connect',
'host' => $host,
'database' => $database,
));
// If we receive these errors, we'll retry the connection up to the
// retry limit. For other errors, we'll fail immediately.
$retry_codes = array(
// "Connection Timeout"
2002 => true,
// "Unable to Connect"
2003 => true,
);
$max_retries = max(1, $this->getConfiguration('retries', 3));
for ($attempt = 1; $attempt <= $max_retries; $attempt++) {
try {
$conn = $this->connect();
$profiler->endServiceCall($call_id, array());
break;
} catch (AphrontQueryException $ex) {
$code = $ex->getCode();
if (($attempt < $max_retries) && isset($retry_codes[$code])) {
$message = pht(
'Retrying database connection to "%s" after connection '.
'failure (attempt %d; "%s"; error #%d): %s',
$host,
$attempt,
get_class($ex),
$code,
$ex->getMessage());
// See T13403. If we're silenced with the "@" operator, don't log
// this connection attempt. This keeps things quiet if we're
// running a setup workflow like "bin/config" and expect that the
// database credentials will often be incorrect.
if (error_reporting()) {
phlog($message);
}
} else {
$profiler->endServiceCall($call_id, array());
throw $ex;
}
}
}
$this->connection = $conn;
}
protected function requireConnection() {
if (!$this->connection) {
if ($this->connectionPool) {
$this->connection = array_pop($this->connectionPool);
} else {
$this->establishConnection();
}
}
return $this->connection;
}
protected function beginAsyncConnection() {
$connection = $this->requireConnection();
$this->connection = null;
return $connection;
}
protected function endAsyncConnection($connection) {
if ($this->connection) {
$this->connectionPool[] = $this->connection;
}
$this->connection = $connection;
}
public function selectAllResults() {
$result = array();
$res = $this->lastResult;
if ($res == null) {
throw new Exception(pht('No query result to fetch from!'));
}
while (($row = $this->fetchAssoc($res))) {
$result[] = $row;
}
return $result;
}
public function executeQuery(PhutilQueryString $query) {
$display_query = $query->getMaskedString();
$raw_query = $query->getUnmaskedString();
$this->lastResult = null;
$retries = max(1, $this->getConfiguration('retries', 3));
while ($retries--) {
try {
$this->requireConnection();
$is_write = $this->checkWrite($raw_query);
$profiler = PhutilServiceProfiler::getInstance();
$call_id = $profiler->beginServiceCall(
array(
'type' => 'query',
'config' => $this->configuration,
'query' => $display_query,
'write' => $is_write,
));
$result = $this->rawQuery($raw_query);
$profiler->endServiceCall($call_id, array());
if ($this->nextError) {
$result = null;
}
if ($result) {
$this->lastResult = $result;
break;
}
$this->throwQueryException($this->connection);
} catch (AphrontConnectionLostQueryException $ex) {
$can_retry = ($retries > 0);
if ($this->isInsideTransaction()) {
// Zero out the transaction state to prevent a second exception
// ("program exited with open transaction") from being thrown, since
// we're about to throw a more relevant/useful one instead.
$state = $this->getTransactionState();
while ($state->getDepth()) {
$state->decreaseDepth();
}
$can_retry = false;
}
if ($this->isHoldingAnyLock()) {
$this->forgetAllLocks();
$can_retry = false;
}
$this->close();
if (!$can_retry) {
throw $ex;
}
}
}
}
public function executeRawQueries(array $raw_queries) {
if (!$raw_queries) {
return array();
}
$is_write = false;
foreach ($raw_queries as $key => $raw_query) {
$is_write = $is_write || $this->checkWrite($raw_query);
$raw_queries[$key] = rtrim($raw_query, "\r\n\t ;");
}
$profiler = PhutilServiceProfiler::getInstance();
$call_id = $profiler->beginServiceCall(
array(
'type' => 'multi-query',
'config' => $this->configuration,
'queries' => $raw_queries,
'write' => $is_write,
));
$results = $this->rawQueries($raw_queries);
$profiler->endServiceCall($call_id, array());
return $results;
}
protected function processResult($result) {
if (!$result) {
try {
$this->throwQueryException($this->requireConnection());
} catch (Exception $ex) {
return $ex;
}
} else if (is_bool($result)) {
return $this->getAffectedRows();
}
$rows = array();
while (($row = $this->fetchAssoc($result))) {
$rows[] = $row;
}
$this->freeResult($result);
return $rows;
}
protected function checkWrite($raw_query) {
// NOTE: The opening "(" allows queries in the form of:
//
// (SELECT ...) UNION (SELECT ...)
$is_write = !preg_match('/^[(]*(SELECT|SHOW|EXPLAIN)\s/', $raw_query);
if ($is_write) {
if ($this->getReadOnly()) {
throw new Exception(
pht(
'Attempting to issue a write query on a read-only '.
'connection (to database "%s")!',
$this->getConfiguration('database')));
}
AphrontWriteGuard::willWrite();
return true;
}
return false;
}
protected function throwQueryException($connection) {
if ($this->nextError) {
$errno = $this->nextError;
$error = pht('Simulated error.');
$this->nextError = null;
} else {
$errno = $this->getErrorCode($connection);
$error = $this->getErrorDescription($connection);
}
$this->throwQueryCodeException($errno, $error);
}
private function throwCommonException($errno, $error) {
$message = pht('#%d: %s', $errno, $error);
switch ($errno) {
case 2013: // Connection Dropped
throw new AphrontConnectionLostQueryException($message);
case 2006: // Gone Away
$more = pht(
'This error may occur if your configured MySQL "wait_timeout" or '.
'"max_allowed_packet" values are too small. This may also indicate '.
'that something used the MySQL "KILL <process>" command to kill '.
'the connection running the query.');
throw new AphrontConnectionLostQueryException("{$message}\n\n{$more}");
case 1213: // Deadlock
throw new AphrontDeadlockQueryException($message);
case 1205: // Lock wait timeout exceeded
throw new AphrontLockTimeoutQueryException($message);
case 1062: // Duplicate Key
// NOTE: In some versions of MySQL we get a key name back here, but
// older versions just give us a key index ("key 2") so it's not
// portable to parse the key out of the error and attach it to the
// exception.
throw new AphrontDuplicateKeyQueryException($message);
case 1044: // Access denied to database
case 1142: // Access denied to table
case 1143: // Access denied to column
case 1227: // Access denied (e.g., no SUPER for SHOW SLAVE STATUS).
// See T13622. Try to help users figure out that this is a GRANT
// problem.
$more = pht(
'This error usually indicates that you need to "GRANT" the '.
'MySQL user additional permissions. See "GRANT" in the MySQL '.
'manual for help.');
throw new AphrontAccessDeniedQueryException("{$message}\n\n{$more}");
case 1045: // Access denied (auth)
throw new AphrontInvalidCredentialsQueryException($message);
case 1146: // No such table
case 1049: // No such database
case 1054: // Unknown column "..." in field list
throw new AphrontSchemaQueryException($message);
}
// TODO: 1064 is syntax error, and quite terrible in production.
return null;
}
protected function throwConnectionException($errno, $error, $user, $host) {
$this->throwCommonException($errno, $error);
$message = pht(
'Attempt to connect to %s@%s failed with error #%d: %s.',
$user,
$host,
$errno,
$error);
throw new AphrontConnectionQueryException($message, $errno);
}
protected function throwQueryCodeException($errno, $error) {
$this->throwCommonException($errno, $error);
$message = pht(
'#%d: %s',
$errno,
$error);
throw new AphrontQueryException($message, $errno);
}
/**
* Force the next query to fail with a simulated error. This should be used
* ONLY for unit tests.
*/
public function simulateErrorOnNextQuery($error) {
$this->nextError = $error;
return $this;
}
/**
* Check inserts for characters outside of the BMP. Even with the strictest
* settings, MySQL will silently truncate data when it encounters these, which
* can lead to data loss and security problems.
*/
protected function validateUTF8String($string) {
if (phutil_is_utf8($string)) {
return;
}
throw new AphrontCharacterSetQueryException(
pht(
'Attempting to construct a query using a non-utf8 string when '.
'utf8 is expected. Use the `%%B` conversion to escape binary '.
'strings data.'));
}
}
diff --git a/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php b/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
index 85adb4fc29..93f623338f 100644
--- a/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
+++ b/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
@@ -1,353 +1,353 @@
<?php
/**
* @task config Configuring Storage
*/
abstract class PhabricatorLiskDAO extends LiskDAO {
private static $namespaceStack = array();
private $forcedNamespace;
const ATTACHABLE = '<attachable>';
const CONFIG_APPLICATION_SERIALIZERS = 'phabricator/serializers';
/* -( Configuring Storage )------------------------------------------------ */
/**
* @task config
*/
public static function pushStorageNamespace($namespace) {
self::$namespaceStack[] = $namespace;
}
/**
* @task config
*/
public static function popStorageNamespace() {
array_pop(self::$namespaceStack);
}
/**
* @task config
*/
public static function getDefaultStorageNamespace() {
return PhabricatorEnv::getEnvConfig('storage.default-namespace');
}
/**
* @task config
*/
public static function getStorageNamespace() {
$namespace = end(self::$namespaceStack);
if (!strlen($namespace)) {
$namespace = self::getDefaultStorageNamespace();
}
if (!strlen($namespace)) {
throw new Exception(pht('No storage namespace configured!'));
}
return $namespace;
}
public function setForcedStorageNamespace($namespace) {
$this->forcedNamespace = $namespace;
return $this;
}
/**
* @task config
*/
protected function establishLiveConnection($mode) {
$namespace = self::getStorageNamespace();
$database = $namespace.'_'.$this->getApplicationName();
$is_readonly = PhabricatorEnv::isReadOnly();
if ($is_readonly && ($mode != 'r')) {
$this->raiseImproperWrite($database);
}
$connection = $this->newClusterConnection(
$this->getApplicationName(),
$database,
$mode);
// TODO: This should be testing if the mode is "r", but that would probably
// break a lot of things. Perform a more narrow test for readonly mode
// until we have greater certainty that this works correctly most of the
// time.
if ($is_readonly) {
$connection->setReadOnly(true);
}
return $connection;
}
private function newClusterConnection($application, $database, $mode) {
$master = PhabricatorDatabaseRef::getMasterDatabaseRefForApplication(
$application);
$master_exception = null;
if ($master && !$master->isSevered()) {
$connection = $master->newApplicationConnection($database);
if ($master->isReachable($connection)) {
return $connection;
} else {
if ($mode == 'w') {
$this->raiseImpossibleWrite($database);
}
PhabricatorEnv::setReadOnly(
true,
PhabricatorEnv::READONLY_UNREACHABLE);
$master_exception = $master->getConnectionException();
}
}
$replica = PhabricatorDatabaseRef::getReplicaDatabaseRefForApplication(
$application);
if ($replica) {
$connection = $replica->newApplicationConnection($database);
$connection->setReadOnly(true);
if ($replica->isReachable($connection)) {
if ($master_exception) {
// If we ended up here as the result of a failover, log the
// exception. This is seriously bad news even if we are able
// to recover from it.
$proxy_exception = new PhutilProxyException(
pht(
'Failed to connect to master database ("%s"), failing over '.
'into read-only mode.',
$database),
$master_exception);
phlog($proxy_exception);
}
return $connection;
}
}
if (!$master && !$replica) {
$this->raiseUnconfigured($database);
}
$this->raiseUnreachable($database, $master_exception);
}
private function raiseImproperWrite($database) {
throw new PhabricatorClusterImproperWriteException(
pht(
'Unable to establish a write-mode connection (to application '.
- 'database "%s") because Phabricator is in read-only mode. Whatever '.
+ 'database "%s") because this server is in read-only mode. Whatever '.
'you are trying to do does not function correctly in read-only mode.',
$database));
}
private function raiseImpossibleWrite($database) {
throw new PhabricatorClusterImpossibleWriteException(
pht(
'Unable to connect to master database ("%s"). This is a severe '.
'failure; your request did not complete.',
$database));
}
private function raiseUnconfigured($database) {
throw new Exception(
pht(
'Unable to establish a connection to any database host '.
'(while trying "%s"). No masters or replicas are configured.',
$database));
}
private function raiseUnreachable($database, Exception $proxy = null) {
$message = pht(
'Unable to establish a connection to any database host '.
'(while trying "%s"). All masters and replicas are completely '.
'unreachable.',
$database);
if ($proxy) {
$proxy_message = pht(
'%s: %s',
get_class($proxy),
$proxy->getMessage());
$message = $message."\n\n".$proxy_message;
}
throw new PhabricatorClusterStrandedException($message);
}
/**
* @task config
*/
public function getTableName() {
$str = 'phabricator';
$len = strlen($str);
$class = strtolower(get_class($this));
if (!strncmp($class, $str, $len)) {
$class = substr($class, $len);
}
$app = $this->getApplicationName();
if (!strncmp($class, $app, strlen($app))) {
$class = substr($class, strlen($app));
}
if (strlen($class)) {
return $app.'_'.$class;
} else {
return $app;
}
}
/**
* @task config
*/
abstract public function getApplicationName();
protected function getDatabaseName() {
if ($this->forcedNamespace) {
$namespace = $this->forcedNamespace;
} else {
$namespace = self::getStorageNamespace();
}
return $namespace.'_'.$this->getApplicationName();
}
/**
* Break a list of escaped SQL statement fragments (e.g., VALUES lists for
* INSERT, previously built with @{function:qsprintf}) into chunks which will
* fit under the MySQL 'max_allowed_packet' limit.
*
* If a statement is too large to fit within the limit, it is broken into
* its own chunk (but might fail when the query executes).
*/
public static function chunkSQL(
array $fragments,
$limit = null) {
if ($limit === null) {
// NOTE: Hard-code this at 1MB for now, minus a 10% safety buffer.
// Eventually we could query MySQL or let the user configure it.
$limit = (int)((1024 * 1024) * 0.90);
}
$result = array();
$chunk = array();
$len = 0;
$glue_len = strlen(', ');
foreach ($fragments as $fragment) {
if ($fragment instanceof PhutilQueryString) {
$this_len = strlen($fragment->getUnmaskedString());
} else {
$this_len = strlen($fragment);
}
if ($chunk) {
// Chunks after the first also imply glue.
$this_len += $glue_len;
}
if ($len + $this_len <= $limit) {
$len += $this_len;
$chunk[] = $fragment;
} else {
if ($chunk) {
$result[] = $chunk;
}
$len = ($this_len - $glue_len);
$chunk = array($fragment);
}
}
if ($chunk) {
$result[] = $chunk;
}
return $result;
}
protected function assertAttached($property) {
if ($property === self::ATTACHABLE) {
throw new PhabricatorDataNotAttachedException($this);
}
return $property;
}
protected function assertAttachedKey($value, $key) {
$this->assertAttached($value);
if (!array_key_exists($key, $value)) {
throw new PhabricatorDataNotAttachedException($this);
}
return $value[$key];
}
protected function detectEncodingForStorage($string) {
return phutil_is_utf8($string) ? 'utf8' : null;
}
protected function getUTF8StringFromStorage($string, $encoding) {
if ($encoding == 'utf8') {
return $string;
}
if (function_exists('mb_detect_encoding')) {
if (strlen($encoding)) {
$try_encodings = array(
$encoding,
);
} else {
// TODO: This is pretty much a guess, and probably needs to be
// configurable in the long run.
$try_encodings = array(
'JIS',
'EUC-JP',
'SJIS',
'ISO-8859-1',
);
}
$guess = mb_detect_encoding($string, $try_encodings);
if ($guess) {
return mb_convert_encoding($string, 'UTF-8', $guess);
}
}
return phutil_utf8ize($string);
}
protected function willReadData(array &$data) {
parent::willReadData($data);
static $custom;
if ($custom === null) {
$custom = $this->getConfigOption(self::CONFIG_APPLICATION_SERIALIZERS);
}
if ($custom) {
foreach ($custom as $key => $serializer) {
$data[$key] = $serializer->willReadValue($data[$key]);
}
}
}
protected function willWriteData(array &$data) {
static $custom;
if ($custom === null) {
$custom = $this->getConfigOption(self::CONFIG_APPLICATION_SERIALIZERS);
}
if ($custom) {
foreach ($custom as $key => $serializer) {
$data[$key] = $serializer->willWriteValue($data[$key]);
}
}
parent::willWriteData($data);
}
}
diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php
index ef6735ec8f..00689ab5fe 100644
--- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php
+++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php
@@ -1,27 +1,27 @@
<?php
final class PhabricatorStorageManagementDatabasesWorkflow
extends PhabricatorStorageManagementWorkflow {
protected function didConstruct() {
$this
->setName('databases')
->setExamples('**databases** [__options__]')
- ->setSynopsis(pht('List Phabricator databases.'));
+ ->setSynopsis(pht('List databases.'));
}
protected function isReadOnlyWorkflow() {
return true;
}
public function didExecute(PhutilArgumentParser $args) {
$api = $this->getAnyAPI();
$patches = $this->getPatches();
$databases = $api->getDatabaseList($patches, true);
echo implode("\n", $databases)."\n";
return 0;
}
}
diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php
index 9b718e231d..d9ad8b8375 100644
--- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php
+++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php
@@ -1,133 +1,134 @@
<?php
final class PhabricatorStorageManagementDestroyWorkflow
extends PhabricatorStorageManagementWorkflow {
protected function didConstruct() {
$this
->setName('destroy')
->setExamples('**destroy** [__options__]')
->setSynopsis(pht('Permanently destroy all storage and data.'))
->setArguments(
array(
array(
'name' => 'unittest-fixtures',
'help' => pht(
'Restrict **destroy** operations to databases created '.
'by %s test fixtures.',
'PhabricatorTestCase'),
),
));
}
public function didExecute(PhutilArgumentParser $args) {
$api = $this->getSingleAPI();
$host_display = $api->getDisplayName();
if (!$this->isDryRun() && !$this->isForce()) {
if ($args->getArg('unittest-fixtures')) {
$warning = pht(
'Are you completely sure you really want to destroy all unit '.
'test fixure data on host "%s"? This operation can not be undone.',
$host_display);
echo tsprintf(
'%B',
id(new PhutilConsoleBlock())
->addParagraph($warning)
->drawConsoleString());
if (!phutil_console_confirm(pht('Destroy all unit test data?'))) {
$this->logFail(
pht('CANCELLED'),
pht('User cancelled operation.'));
exit(1);
}
} else {
$warning = pht(
'Are you completely sure you really want to permanently destroy '.
- 'all storage for Phabricator data on host "%s"? This operation '.
+ 'all storage for %s data on host "%s"? This operation '.
'can not be undone and your data will not be recoverable if '.
'you proceed.',
+ PlatformSymbols::getPlatformServerName(),
$host_display);
echo tsprintf(
'%B',
id(new PhutilConsoleBlock())
->addParagraph($warning)
->drawConsoleString());
if (!phutil_console_confirm(pht('Permanently destroy all data?'))) {
$this->logFail(
pht('CANCELLED'),
pht('User cancelled operation.'));
exit(1);
}
if (!phutil_console_confirm(pht('Really destroy all data forever?'))) {
$this->logFail(
pht('CANCELLED'),
pht('User cancelled operation.'));
exit(1);
}
}
}
$patches = $this->getPatches();
if ($args->getArg('unittest-fixtures')) {
$conn = $api->getConn(null);
$databases = queryfx_all(
$conn,
'SELECT DISTINCT(TABLE_SCHEMA) AS db '.
'FROM INFORMATION_SCHEMA.TABLES '.
'WHERE TABLE_SCHEMA LIKE %>',
PhabricatorTestCase::NAMESPACE_PREFIX);
$databases = ipull($databases, 'db');
} else {
$databases = $api->getDatabaseList($patches);
$databases[] = $api->getDatabaseName('meta_data');
// These are legacy databases that were dropped long ago. See T2237.
$databases[] = $api->getDatabaseName('phid');
$databases[] = $api->getDatabaseName('directory');
}
asort($databases);
foreach ($databases as $database) {
if ($this->isDryRun()) {
$this->logInfo(
pht('DRY RUN'),
pht(
'Would drop database "%s" on host "%s".',
$database,
$host_display));
} else {
$this->logWarn(
pht('DESTROY'),
pht(
'Dropping database "%s" on host "%s"...',
$database,
$host_display));
queryfx(
$api->getConn(null),
'DROP DATABASE IF EXISTS %T',
$database);
}
}
if (!$this->isDryRun()) {
$this->logOkay(
pht('DONE'),
pht(
'Storage on "%s" was destroyed.',
$host_display));
}
return 0;
}
}
diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php
index aab4701bbf..8cc5b1ae63 100644
--- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php
+++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php
@@ -1,440 +1,440 @@
<?php
final class PhabricatorStorageManagementDumpWorkflow
extends PhabricatorStorageManagementWorkflow {
protected function didConstruct() {
$this
->setName('dump')
->setExamples('**dump** [__options__]')
->setSynopsis(pht('Dump all data in storage to stdout.'))
->setArguments(
array(
array(
'name' => 'for-replica',
'help' => pht(
'Add __--master-data__ to the __mysqldump__ command, '.
'generating a CHANGE MASTER statement in the output. This '.
'option also dumps all data, including caches.'),
),
array(
'name' => 'output',
'param' => 'file',
'help' => pht(
'Write output directly to disk. This handles errors better '.
'than using pipes. Use with __--compress__ to gzip the '.
'output.'),
),
array(
'name' => 'compress',
'help' => pht(
'With __--output__, write a compressed file to disk instead '.
'of a plaintext file.'),
),
array(
'name' => 'no-indexes',
'help' => pht(
'Do not dump data in rebuildable index tables. This means '.
'backups are smaller and faster, but you will need to manually '.
'rebuild indexes after performing a restore.'),
),
array(
'name' => 'overwrite',
'help' => pht(
'With __--output__, overwrite the output file if it already '.
'exists.'),
),
array(
'name' => 'database',
'param' => 'database-name',
'help' => pht(
'Dump only tables in the named database (or databases, if '.
'the flag is repeated). Specify database names without the '.
'namespace prefix (that is: use "differential", not '.
'"phabricator_differential").'),
'repeat' => true,
),
));
}
protected function isReadOnlyWorkflow() {
return true;
}
public function didExecute(PhutilArgumentParser $args) {
$output_file = $args->getArg('output');
$is_compress = $args->getArg('compress');
$is_overwrite = $args->getArg('overwrite');
$is_noindex = $args->getArg('no-indexes');
$is_replica = $args->getArg('for-replica');
$database_filter = $args->getArg('database');
if ($is_compress) {
if ($output_file === null) {
throw new PhutilArgumentUsageException(
pht(
'The "--compress" flag can only be used alongside "--output".'));
}
if (!function_exists('gzopen')) {
throw new PhutilArgumentUsageException(
pht(
'The "--compress" flag requires the PHP "zlib" extension, but '.
'that extension is not available. Install the extension or '.
'omit the "--compress" option.'));
}
}
if ($is_overwrite) {
if ($output_file === null) {
throw new PhutilArgumentUsageException(
pht(
'The "--overwrite" flag can only be used alongside "--output".'));
}
}
if ($is_replica && $is_noindex) {
throw new PhutilArgumentUsageException(
pht(
'The "--for-replica" flag can not be used with the '.
'"--no-indexes" flag. Replication dumps must contain a complete '.
'representation of database state.'));
}
if ($output_file !== null) {
if (Filesystem::pathExists($output_file)) {
if (!$is_overwrite) {
throw new PhutilArgumentUsageException(
pht(
'Output file "%s" already exists. Use "--overwrite" '.
'to overwrite.',
$output_file));
}
}
}
$api = $this->getSingleAPI();
$patches = $this->getPatches();
$applied = $api->getAppliedPatches();
if ($applied === null) {
throw new PhutilArgumentUsageException(
pht(
'There is no database storage initialized in the current storage '.
'namespace ("%s"). Use "bin/storage upgrade" to initialize '.
'storage or use "--namespace" to choose a different namespace.',
$api->getNamespace()));
}
$ref = $api->getRef();
$ref_key = $ref->getRefKey();
$schemata_query = id(new PhabricatorConfigSchemaQuery())
->setAPIs(array($api))
->setRefs(array($ref));
$actual_map = $schemata_query->loadActualSchemata();
$expect_map = $schemata_query->loadExpectedSchemata();
$schemata = $actual_map[$ref_key];
$expect = $expect_map[$ref_key];
if ($database_filter) {
$internal_names = array();
$expect_databases = $expect->getDatabases();
foreach ($expect_databases as $expect_database) {
$database_name = $expect_database->getName();
$internal_name = $api->getInternalDatabaseName($database_name);
if ($internal_name !== null) {
$internal_names[$internal_name] = $database_name;
}
}
ksort($internal_names);
$seen = array();
foreach ($database_filter as $filter) {
if (!isset($internal_names[$filter])) {
throw new PhutilArgumentUsageException(
pht(
'Database "%s" is unknown. This script can only dump '.
- 'databases known to the current version of Phabricator. '.
+ 'databases known to the current version of this software. '.
'Valid databases are: %s.',
$filter,
implode(', ', array_keys($internal_names))));
}
if (isset($seen[$filter])) {
throw new PhutilArgumentUsageException(
pht(
'Database "%s" is specified more than once. Specify each '.
'database at most once.',
$filter));
}
$seen[$filter] = true;
}
$dump_databases = array_select_keys($internal_names, $database_filter);
$dump_databases = array_fuse($dump_databases);
} else {
$dump_databases = array_keys($schemata->getDatabases());
$dump_databases = array_fuse($dump_databases);
}
$with_caches = $is_replica;
$with_indexes = !$is_noindex;
$targets = array();
foreach ($schemata->getDatabases() as $database_name => $database) {
if (!isset($dump_databases[$database_name])) {
continue;
}
$expect_database = $expect->getDatabase($database_name);
foreach ($database->getTables() as $table_name => $table) {
// NOTE: It's possible for us to find tables in these database which
// we don't expect to be there. For example, an older version of
// Phabricator may have had a table that was later dropped. We assume
// these are data tables and always dump them, erring on the side of
// caution.
$persistence = PhabricatorConfigTableSchema::PERSISTENCE_DATA;
if ($expect_database) {
$expect_table = $expect_database->getTable($table_name);
if ($expect_table) {
$persistence = $expect_table->getPersistenceType();
}
}
switch ($persistence) {
case PhabricatorConfigTableSchema::PERSISTENCE_CACHE:
// When dumping tables, leave the data in cache tables in the
// database. This will be automatically rebuild after the data
// is restored and does not need to be persisted in backups.
$with_data = $with_caches;
break;
case PhabricatorConfigTableSchema::PERSISTENCE_INDEX:
// When dumping tables, leave index data behind of the caller
// specified "--no-indexes". These tables can be rebuilt manually
// from other tables, but do not rebuild automatically.
$with_data = $with_indexes;
break;
case PhabricatorConfigTableSchema::PERSISTENCE_DATA:
default:
$with_data = true;
break;
}
$targets[] = array(
'database' => $database_name,
'table' => $table_name,
'data' => $with_data,
);
}
}
list($host, $port) = $this->getBareHostAndPort($api->getHost());
$has_password = false;
$password = $api->getPassword();
if ($password) {
if (strlen($password->openEnvelope())) {
$has_password = true;
}
}
$argv = array();
$argv[] = '--hex-blob';
$argv[] = '--single-transaction';
$argv[] = '--default-character-set';
$argv[] = $api->getClientCharset();
if ($is_replica) {
$argv[] = '--master-data';
}
$argv[] = '-u';
$argv[] = $api->getUser();
$argv[] = '-h';
$argv[] = $host;
// MySQL's default "max_allowed_packet" setting is fairly conservative
// (16MB). If we try to dump a row which is larger than this limit, the
// dump will fail.
// We encourage users to increase this limit during setup, but modifying
// the "[mysqld]" section of the configuration file (instead of
// "[mysqldump]" section) won't apply to "mysqldump" and we can not easily
// detect what the "mysqldump" setting is.
// Since no user would ever reasonably want a dump to fail because a row
// was too large, just manually force this setting to the largest supported
// value.
$argv[] = '--max-allowed-packet';
$argv[] = '1G';
if ($port) {
$argv[] = '--port';
$argv[] = $port;
}
$commands = array();
foreach ($targets as $target) {
$target_argv = $argv;
if (!$target['data']) {
$target_argv[] = '--no-data';
}
if ($has_password) {
$command = csprintf(
'mysqldump -p%P %Ls -- %R %R',
$password,
$target_argv,
$target['database'],
$target['table']);
} else {
$command = csprintf(
'mysqldump %Ls -- %R %R',
$target_argv,
$target['database'],
$target['table']);
}
$commands[] = array(
'command' => $command,
'database' => $target['database'],
);
}
// Decrease the CPU priority of this process so it doesn't contend with
// other more important things.
if (function_exists('proc_nice')) {
proc_nice(19);
}
// If we are writing to a file, stream the command output to disk. This
// mode makes sure the whole command fails if there's an error (commonly,
// a full disk). See T6996 for discussion.
if ($output_file === null) {
$file = null;
} else if ($is_compress) {
$file = gzopen($output_file, 'wb1');
} else {
$file = fopen($output_file, 'wb');
}
if (($output_file !== null) && !$file) {
throw new Exception(
pht(
'Failed to open file "%s" for writing.',
$file));
}
$created = array();
try {
foreach ($commands as $spec) {
// Because we're dumping database-by-database, we need to generate our
// own CREATE DATABASE and USE statements.
$database = $spec['database'];
$preamble = array();
if (!isset($created[$database])) {
$preamble[] =
"CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$database}` ".
"/*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin */;\n";
$created[$database] = true;
}
$preamble[] = "USE `{$database}`;\n";
$preamble = implode('', $preamble);
$this->writeData($preamble, $file, $is_compress, $output_file);
// See T13328. The "mysql" command may produce output very quickly.
// Don't buffer more than a fixed amount.
$future = id(new ExecFuture('%C', $spec['command']))
->setReadBufferSize(32 * 1024 * 1024);
$iterator = id(new FutureIterator(array($future)))
->setUpdateInterval(0.010);
foreach ($iterator as $ready) {
list($stdout, $stderr) = $future->read();
$future->discardBuffers();
if (strlen($stderr)) {
fwrite(STDERR, $stderr);
}
$this->writeData($stdout, $file, $is_compress, $output_file);
if ($ready !== null) {
$ready->resolvex();
}
}
}
if (!$file) {
$ok = true;
} else if ($is_compress) {
$ok = gzclose($file);
} else {
$ok = fclose($file);
}
if ($ok !== true) {
throw new Exception(
pht(
'Failed to close file "%s".',
$output_file));
}
} catch (Exception $ex) {
// If we might have written a partial file to disk, try to remove it so
// we don't leave any confusing artifacts laying around.
try {
if ($file !== null) {
Filesystem::remove($output_file);
}
} catch (Exception $ex) {
// Ignore any errors we hit.
}
throw $ex;
}
return 0;
}
private function writeData($data, $file, $is_compress, $output_file) {
if (!strlen($data)) {
return;
}
if (!$file) {
$ok = fwrite(STDOUT, $data);
} else if ($is_compress) {
$ok = gzwrite($file, $data);
} else {
$ok = fwrite($file, $data);
}
if ($ok !== strlen($data)) {
throw new Exception(
pht(
'Failed to write %d byte(s) to file "%s".',
new PhutilNumber(strlen($data)),
$output_file));
}
}
}
diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php
index e181063ac4..b492764c99 100644
--- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php
+++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php
@@ -1,180 +1,180 @@
<?php
final class PhabricatorStorageManagementQuickstartWorkflow
extends PhabricatorStorageManagementWorkflow {
protected function didConstruct() {
$this
->setName('quickstart')
->setExamples('**quickstart** [__options__]')
->setSynopsis(
pht(
'Generate a new quickstart database dump. This command is mostly '.
- 'useful when developing Phabricator.'))
+ 'useful for internal development.'))
->setArguments(
array(
array(
'name' => 'output',
'param' => 'file',
'help' => pht('Specify output file to write.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
parent::execute($args);
$output = $args->getArg('output');
if (!$output) {
throw new PhutilArgumentUsageException(
pht(
'Specify a file to write with `%s`.',
'--output'));
}
$namespace = 'phabricator_quickstart_'.Filesystem::readRandomCharacters(8);
$bin = dirname(phutil_get_library_root('phabricator')).'/bin/storage';
// We don't care which database we're using to generate a quickstart file,
// since all of the schemata should be identical.
$api = $this->getAnyAPI();
$ref = $api->getRef();
$ref_key = $ref->getRefKey();
if (!$api->isCharacterSetAvailable('utf8mb4')) {
throw new PhutilArgumentUsageException(
pht(
'You can only generate a new quickstart file if MySQL supports '.
'the %s character set (available in MySQL 5.5 and newer). The '.
'configured server does not support %s.',
'utf8mb4',
'utf8mb4'));
}
$err = phutil_passthru(
'%s upgrade --force --no-quickstart --namespace %s --ref %s',
$bin,
$namespace,
$ref_key);
if ($err) {
return $err;
}
$err = phutil_passthru(
'%s adjust --force --namespace %s --ref %s',
$bin,
$namespace,
$ref_key);
if ($err) {
return $err;
}
$tmp = new TempFile();
$err = phutil_passthru(
'%s dump --namespace %s --ref %s > %s',
$bin,
$namespace,
$ref_key,
$tmp);
if ($err) {
return $err;
}
$err = phutil_passthru(
'%s destroy --force --namespace %s --ref %s',
$bin,
$namespace,
$ref_key);
if ($err) {
return $err;
}
$dump = Filesystem::readFile($tmp);
$dump = str_replace(
$namespace,
'{$NAMESPACE}',
$dump);
// NOTE: This is a hack. We can not use `binary` for these columns, because
// they are a part of a fulltext index. This regex is avoiding matching a
// possible NOT NULL at the end of the line.
$old = $dump;
$dump = preg_replace(
'/`corpus` longtext CHARACTER SET .*? COLLATE [^\s,]+/mi',
'`corpus` longtext CHARACTER SET {$CHARSET_FULLTEXT} '.
'COLLATE {$COLLATE_FULLTEXT}',
$dump);
if ($dump == $old) {
// If we didn't make any changes, yell about it. We'll produce an invalid
// dump otherwise.
throw new PhutilArgumentUsageException(
pht(
'Failed to apply hack to adjust %s search column!',
'FULLTEXT'));
}
$dump = str_replace(
'utf8mb4_bin',
'{$COLLATE_TEXT}',
$dump);
$dump = str_replace(
'utf8mb4_unicode_ci',
'{$COLLATE_SORT}',
$dump);
$dump = str_replace(
'utf8mb4',
'{$CHARSET}',
$dump);
$old = $dump;
$dump = preg_replace(
'/CHARACTER SET {\$CHARSET} COLLATE {\$COLLATE_SORT}/mi',
'CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT}',
$dump);
if ($dump == $old) {
throw new PhutilArgumentUsageException(
pht('Failed to adjust SORT columns!'));
}
// Strip out a bunch of unnecessary commands which make the dump harder
// to handle and slower to import.
// Remove character set adjustments and key disables.
$dump = preg_replace(
'(^/\*.*\*/;$)m',
'',
$dump);
// Remove comments.
$dump = preg_replace('/^--.*$/m', '', $dump);
// Remove table drops, locks, and unlocks. These are never relevant when
// performing a quickstart.
$dump = preg_replace(
'/^(DROP TABLE|LOCK TABLES|UNLOCK TABLES).*$/m',
'',
$dump);
// Collapse adjacent newlines.
$dump = preg_replace('/\n\s*\n/', "\n", $dump);
$dump = str_replace(';', ";\n", $dump);
$dump = trim($dump)."\n";
Filesystem::writeFile($output, $dump);
$console = PhutilConsole::getConsole();
$console->writeOut(
"**<bg:green> %s </bg>** %s\n",
pht('SUCCESS'),
pht('Wrote fresh quickstart SQL.'));
return 0;
}
}
diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php
index e0b8d884c5..6a268cd440 100644
--- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php
+++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php
@@ -1,149 +1,149 @@
<?php
final class PhabricatorStorageManagementUpgradeWorkflow
extends PhabricatorStorageManagementWorkflow {
protected function didConstruct() {
$this
->setName('upgrade')
->setExamples('**upgrade** [__options__]')
->setSynopsis(pht('Upgrade database schemata.'))
->setArguments(
array(
array(
'name' => 'apply',
'param' => 'patch',
'help' => pht(
'Apply __patch__ explicitly. This is an advanced feature for '.
'development and debugging; you should not normally use this '.
'flag. This skips adjustment.'),
),
array(
'name' => 'no-quickstart',
'help' => pht(
'Build storage patch-by-patch from scratch, even if it could '.
'be loaded from the quickstart template.'),
),
array(
'name' => 'init-only',
'help' => pht(
'Initialize storage only; do not apply patches or adjustments.'),
),
array(
'name' => 'no-adjust',
'help' => pht(
'Do not apply storage adjustments after storage upgrades.'),
),
));
}
public function didExecute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$patches = $this->getPatches();
if (!$this->isDryRun() && !$this->isForce()) {
$console->writeOut(
phutil_console_wrap(
pht(
'Before running storage upgrades, you should take down the '.
- 'Phabricator web interface and stop any running Phabricator '.
- 'daemons (you can disable this warning with %s).',
+ 'web interface and stop any running daemons (you can disable '.
+ 'this warning with %s).',
'--force')));
if (!phutil_console_confirm(pht('Are you ready to continue?'))) {
$console->writeOut("%s\n", pht('Cancelled.'));
return 1;
}
}
$apply_only = $args->getArg('apply');
if ($apply_only) {
if (empty($patches[$apply_only])) {
throw new PhutilArgumentUsageException(
pht(
"%s argument '%s' is not a valid patch. ".
"Use '%s' to show patch status.",
'--apply',
$apply_only,
'./bin/storage status'));
}
}
$no_quickstart = $args->getArg('no-quickstart');
$init_only = $args->getArg('init-only');
$no_adjust = $args->getArg('no-adjust');
$apis = $this->getMasterAPIs();
$this->upgradeSchemata($apis, $apply_only, $no_quickstart, $init_only);
if ($apply_only || $init_only) {
echo tsprintf(
"%s\n",
pht('Declining to synchronize static tables.'));
} else {
echo tsprintf(
"%s\n",
pht('Synchronizing static tables...'));
$this->synchronizeSchemata();
}
if ($no_adjust || $init_only || $apply_only) {
$console->writeOut(
"%s\n",
pht('Declining to apply storage adjustments.'));
} else {
foreach ($apis as $api) {
$err = $this->adjustSchemata($api, false);
if ($err) {
return $err;
}
}
}
return 0;
}
private function synchronizeSchemata() {
// Synchronize the InnoDB fulltext stopwords table from the stopwords file
// on disk.
$table = new PhabricatorSearchDocument();
$conn = $table->establishConnection('w');
$table_ref = PhabricatorSearchDocument::STOPWORDS_TABLE;
$stopwords_database = queryfx_all(
$conn,
'SELECT value FROM %T',
$table_ref);
$stopwords_database = ipull($stopwords_database, 'value', 'value');
$stopwords_path = phutil_get_library_root('phabricator');
$stopwords_path = $stopwords_path.'/../resources/sql/stopwords.txt';
$stopwords_file = Filesystem::readFile($stopwords_path);
$stopwords_file = phutil_split_lines($stopwords_file, false);
$stopwords_file = array_fuse($stopwords_file);
$rem_words = array_diff_key($stopwords_database, $stopwords_file);
if ($rem_words) {
queryfx(
$conn,
'DELETE FROM %T WHERE value IN (%Ls)',
$table_ref,
$rem_words);
}
$add_words = array_diff_key($stopwords_file, $stopwords_database);
if ($add_words) {
foreach ($add_words as $word) {
queryfx(
$conn,
'INSERT IGNORE INTO %T (value) VALUES (%s)',
$table_ref,
$word);
}
}
}
}
diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php
index 71f6374b2a..38a7b7788c 100644
--- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php
+++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php
@@ -1,1287 +1,1289 @@
<?php
abstract class PhabricatorStorageManagementWorkflow
extends PhabricatorManagementWorkflow {
private $apis = array();
private $dryRun;
private $force;
private $patches;
private $didInitialize;
final public function setAPIs(array $apis) {
$this->apis = $apis;
return $this;
}
final public function getAnyAPI() {
return head($this->getAPIs());
}
final public function getMasterAPIs() {
$apis = $this->getAPIs();
$results = array();
foreach ($apis as $api) {
if ($api->getRef()->getIsMaster()) {
$results[] = $api;
}
}
if (!$results) {
throw new PhutilArgumentUsageException(
pht(
'This command only operates on database masters, but the selected '.
'database hosts do not include any masters.'));
}
return $results;
}
final public function getSingleAPI() {
$apis = $this->getAPIs();
if (count($apis) == 1) {
return head($apis);
}
throw new PhutilArgumentUsageException(
pht(
- 'Phabricator is configured in cluster mode, with multiple database '.
+ 'This server is configured in cluster mode, with multiple database '.
'hosts. Use "--host" to specify which host you want to operate on.'));
}
final public function getAPIs() {
return $this->apis;
}
final protected function isDryRun() {
return $this->dryRun;
}
final protected function setDryRun($dry_run) {
$this->dryRun = $dry_run;
return $this;
}
final protected function isForce() {
return $this->force;
}
final protected function setForce($force) {
$this->force = $force;
return $this;
}
public function getPatches() {
return $this->patches;
}
public function setPatches(array $patches) {
assert_instances_of($patches, 'PhabricatorStoragePatch');
$this->patches = $patches;
return $this;
}
protected function isReadOnlyWorkflow() {
return false;
}
public function execute(PhutilArgumentParser $args) {
$this->setDryRun($args->getArg('dryrun'));
$this->setForce($args->getArg('force'));
if (!$this->isReadOnlyWorkflow()) {
if (PhabricatorEnv::isReadOnly()) {
if ($this->isForce()) {
PhabricatorEnv::setReadOnly(false, null);
} else {
throw new PhutilArgumentUsageException(
pht(
- 'Phabricator is currently in read-only mode. Use --force to '.
+ 'This server is currently in read-only mode. Use --force to '.
'override this mode.'));
}
}
}
return $this->didExecute($args);
}
public function didExecute(PhutilArgumentParser $args) {}
private function loadSchemata(PhabricatorStorageManagementAPI $api) {
$query = id(new PhabricatorConfigSchemaQuery());
$ref = $api->getRef();
$ref_key = $ref->getRefKey();
$query->setAPIs(array($api));
$query->setRefs(array($ref));
$actual = $query->loadActualSchemata();
$expect = $query->loadExpectedSchemata();
$comp = $query->buildComparisonSchemata($expect, $actual);
return array(
$comp[$ref_key],
$expect[$ref_key],
$actual[$ref_key],
);
}
final protected function adjustSchemata(
PhabricatorStorageManagementAPI $api,
$unsafe) {
$lock = $this->lock($api);
try {
$err = $this->doAdjustSchemata($api, $unsafe);
// Analyze tables if we're not doing a dry run and adjustments are either
// all clear or have minor errors like surplus tables.
if (!$this->dryRun) {
$should_analyze = (($err == 0) || ($err == 2));
if ($should_analyze) {
$this->analyzeTables($api);
}
}
} catch (Exception $ex) {
$lock->unlock();
throw $ex;
}
$lock->unlock();
return $err;
}
private function doAdjustSchemata(
PhabricatorStorageManagementAPI $api,
$unsafe) {
$console = PhutilConsole::getConsole();
$console->writeOut(
"%s\n",
pht(
'Verifying database schemata on "%s"...',
$api->getRef()->getRefKey()));
list($adjustments, $errors) = $this->findAdjustments($api);
if (!$adjustments) {
$console->writeOut(
"%s\n",
pht('Found no adjustments for schemata.'));
return $this->printErrors($errors, 0);
}
if (!$this->force && !$api->isCharacterSetAvailable('utf8mb4')) {
$message = pht(
"You have an old version of MySQL (older than 5.5) which does not ".
"support the utf8mb4 character set. We strongly recommend upgrading ".
"to 5.5 or newer.\n\n".
"If you apply adjustments now and later update MySQL to 5.5 or newer, ".
"you'll need to apply adjustments again (and they will take a long ".
"time).\n\n".
"You can exit this workflow, update MySQL now, and then run this ".
"workflow again. This is recommended, but may cause a lot of downtime ".
"right now.\n\n".
- "You can exit this workflow, continue using Phabricator without ".
+ "You can exit this workflow, continue using this software without ".
"applying adjustments, update MySQL at a later date, and then run ".
"this workflow again. This is also a good approach, and will let you ".
"delay downtime until later.\n\n".
"You can proceed with this workflow, and then optionally update ".
"MySQL at a later date. After you do, you'll need to apply ".
"adjustments again.\n\n".
"For more information, see \"Managing Storage Adjustments\" in ".
"the documentation.");
$console->writeOut(
"\n**<bg:yellow> %s </bg>**\n\n%s\n",
pht('OLD MySQL VERSION'),
phutil_console_wrap($message));
$prompt = pht('Continue with old MySQL version?');
if (!phutil_console_confirm($prompt, $default_no = true)) {
return;
}
}
$table = id(new PhutilConsoleTable())
->addColumn('database', array('title' => pht('Database')))
->addColumn('table', array('title' => pht('Table')))
->addColumn('name', array('title' => pht('Name')))
->addColumn('info', array('title' => pht('Issues')));
foreach ($adjustments as $adjust) {
$info = array();
foreach ($adjust['issues'] as $issue) {
$info[] = PhabricatorConfigStorageSchema::getIssueName($issue);
}
$table->addRow(array(
'database' => $adjust['database'],
'table' => idx($adjust, 'table'),
'name' => idx($adjust, 'name'),
'info' => implode(', ', $info),
));
}
$console->writeOut("\n\n");
$table->draw();
if ($this->dryRun) {
$console->writeOut(
"%s\n",
pht('DRYRUN: Would apply adjustments.'));
return 0;
} else if ($this->didInitialize) {
// If we just initialized the database, continue without prompting. This
// is nicer for first-time setup and there's no reasonable reason any
// user would ever answer "no" to the prompt against an empty schema.
} else if (!$this->force) {
$console->writeOut(
"\n%s\n",
pht(
"Found %s adjustment(s) to apply, detailed above.\n\n".
"You can review adjustments in more detail from the web interface, ".
"in Config > Database Status. To better understand the adjustment ".
"workflow, see \"Managing Storage Adjustments\" in the ".
"documentation.\n\n".
"MySQL needs to copy table data to make some adjustments, so these ".
"migrations may take some time.",
phutil_count($adjustments)));
$prompt = pht('Apply these schema adjustments?');
if (!phutil_console_confirm($prompt, $default_no = true)) {
return 1;
}
}
$console->writeOut(
"%s\n",
pht('Applying schema adjustments...'));
$conn = $api->getConn(null);
if ($unsafe) {
queryfx($conn, 'SET SESSION sql_mode = %s', '');
} else {
queryfx($conn, 'SET SESSION sql_mode = %s', 'STRICT_ALL_TABLES');
}
$failed = array();
// We make changes in several phases.
$phases = array(
// Drop surplus autoincrements. This allows us to drop primary keys on
// autoincrement columns.
'drop_auto',
// Drop all keys we're going to adjust. This prevents them from
// interfering with column changes.
'drop_keys',
// Apply all database, table, and column changes.
'main',
// Restore adjusted keys.
'add_keys',
// Add missing autoincrements.
'add_auto',
);
$bar = id(new PhutilConsoleProgressBar())
->setTotal(count($adjustments) * count($phases));
foreach ($phases as $phase) {
foreach ($adjustments as $adjust) {
try {
switch ($adjust['kind']) {
case 'database':
if ($phase == 'main') {
queryfx(
$conn,
'ALTER DATABASE %T CHARACTER SET = %s COLLATE = %s',
$adjust['database'],
$adjust['charset'],
$adjust['collation']);
}
break;
case 'table':
if ($phase == 'main') {
queryfx(
$conn,
'ALTER TABLE %T.%T COLLATE = %s, ENGINE = %s',
$adjust['database'],
$adjust['table'],
$adjust['collation'],
$adjust['engine']);
}
break;
case 'column':
$apply = false;
$auto = false;
$new_auto = idx($adjust, 'auto');
if ($phase == 'drop_auto') {
if ($new_auto === false) {
$apply = true;
$auto = false;
}
} else if ($phase == 'main') {
$apply = true;
if ($new_auto === false) {
$auto = false;
} else {
$auto = $adjust['is_auto'];
}
} else if ($phase == 'add_auto') {
if ($new_auto === true) {
$apply = true;
$auto = true;
}
}
if ($apply) {
$parts = array();
if ($auto) {
$parts[] = qsprintf(
$conn,
'AUTO_INCREMENT');
}
if ($adjust['charset']) {
switch ($adjust['charset']) {
case 'binary':
$charset_value = qsprintf($conn, 'binary');
break;
case 'utf8':
$charset_value = qsprintf($conn, 'utf8');
break;
case 'utf8mb4':
$charset_value = qsprintf($conn, 'utf8mb4');
break;
default:
throw new Exception(
pht(
'Unsupported character set "%s".',
$adjust['charset']));
}
switch ($adjust['collation']) {
case 'binary':
$collation_value = qsprintf($conn, 'binary');
break;
case 'utf8_general_ci':
$collation_value = qsprintf($conn, 'utf8_general_ci');
break;
case 'utf8mb4_bin':
$collation_value = qsprintf($conn, 'utf8mb4_bin');
break;
case 'utf8mb4_unicode_ci':
$collation_value = qsprintf($conn, 'utf8mb4_unicode_ci');
break;
default:
throw new Exception(
pht(
'Unsupported collation set "%s".',
$adjust['collation']));
}
$parts[] = qsprintf(
$conn,
'CHARACTER SET %Q COLLATE %Q',
$charset_value,
$collation_value);
}
if ($parts) {
$parts = qsprintf($conn, '%LJ', $parts);
} else {
$parts = qsprintf($conn, '');
}
if ($adjust['nullable']) {
$nullable = qsprintf($conn, 'NULL');
} else {
$nullable = qsprintf($conn, 'NOT NULL');
}
// TODO: We're using "%Z" here for the column type, which is
// technically unsafe. It would be nice to be able to use "%Q"
// instead, but this requires a fair amount of legwork to
// enumerate all column types.
queryfx(
$conn,
'ALTER TABLE %T.%T MODIFY %T %Z %Q %Q',
$adjust['database'],
$adjust['table'],
$adjust['name'],
$adjust['type'],
$parts,
$nullable);
}
break;
case 'key':
if (($phase == 'drop_keys') && $adjust['exists']) {
if ($adjust['name'] == 'PRIMARY') {
$key_name = qsprintf($conn, 'PRIMARY KEY');
} else {
$key_name = qsprintf($conn, 'KEY %T', $adjust['name']);
}
queryfx(
$conn,
'ALTER TABLE %T.%T DROP %Q',
$adjust['database'],
$adjust['table'],
$key_name);
}
if (($phase == 'add_keys') && $adjust['keep']) {
// Different keys need different creation syntax. Notable
// special cases are primary keys and fulltext keys.
if ($adjust['name'] == 'PRIMARY') {
$key_name = qsprintf($conn, 'PRIMARY KEY');
} else if ($adjust['indexType'] == 'FULLTEXT') {
$key_name = qsprintf($conn, 'FULLTEXT %T', $adjust['name']);
} else {
if ($adjust['unique']) {
$key_name = qsprintf(
$conn,
'UNIQUE KEY %T',
$adjust['name']);
} else {
$key_name = qsprintf(
$conn,
'/* NONUNIQUE */ KEY %T',
$adjust['name']);
}
}
queryfx(
$conn,
'ALTER TABLE %T.%T ADD %Q (%LK)',
$adjust['database'],
$adjust['table'],
$key_name,
$adjust['columns']);
}
break;
default:
throw new Exception(
pht('Unknown schema adjustment kind "%s"!', $adjust['kind']));
}
} catch (AphrontQueryException $ex) {
$failed[] = array($adjust, $ex);
}
$bar->update(1);
}
}
$bar->done();
if (!$failed) {
$console->writeOut(
"%s\n",
pht('Completed applying all schema adjustments.'));
$err = 0;
} else {
$table = id(new PhutilConsoleTable())
->addColumn('target', array('title' => pht('Target')))
->addColumn('error', array('title' => pht('Error')));
foreach ($failed as $failure) {
list($adjust, $ex) = $failure;
$pieces = array_select_keys(
$adjust,
array('database', 'table', 'name'));
$pieces = array_filter($pieces);
$target = implode('.', $pieces);
$table->addRow(
array(
'target' => $target,
'error' => $ex->getMessage(),
));
}
$console->writeOut("\n");
$table->draw();
$console->writeOut(
"\n%s\n",
pht('Failed to make some schema adjustments, detailed above.'));
$console->writeOut(
"%s\n",
pht(
'For help troubleshooting adjustments, see "Managing Storage '.
'Adjustments" in the documentation.'));
$err = 1;
}
return $this->printErrors($errors, $err);
}
private function findAdjustments(
PhabricatorStorageManagementAPI $api) {
list($comp, $expect, $actual) = $this->loadSchemata($api);
$issue_charset = PhabricatorConfigStorageSchema::ISSUE_CHARSET;
$issue_collation = PhabricatorConfigStorageSchema::ISSUE_COLLATION;
$issue_columntype = PhabricatorConfigStorageSchema::ISSUE_COLUMNTYPE;
$issue_surpluskey = PhabricatorConfigStorageSchema::ISSUE_SURPLUSKEY;
$issue_missingkey = PhabricatorConfigStorageSchema::ISSUE_MISSINGKEY;
$issue_columns = PhabricatorConfigStorageSchema::ISSUE_KEYCOLUMNS;
$issue_unique = PhabricatorConfigStorageSchema::ISSUE_UNIQUE;
$issue_longkey = PhabricatorConfigStorageSchema::ISSUE_LONGKEY;
$issue_auto = PhabricatorConfigStorageSchema::ISSUE_AUTOINCREMENT;
$issue_engine = PhabricatorConfigStorageSchema::ISSUE_ENGINE;
$adjustments = array();
$errors = array();
foreach ($comp->getDatabases() as $database_name => $database) {
foreach ($this->findErrors($database) as $issue) {
$errors[] = array(
'database' => $database_name,
'issue' => $issue,
);
}
$expect_database = $expect->getDatabase($database_name);
$actual_database = $actual->getDatabase($database_name);
if (!$expect_database || !$actual_database) {
// If there's a real issue here, skip this stuff.
continue;
}
if ($actual_database->getAccessDenied()) {
// If we can't access the database, we can't access the tables either.
continue;
}
$issues = array();
if ($database->hasIssue($issue_charset)) {
$issues[] = $issue_charset;
}
if ($database->hasIssue($issue_collation)) {
$issues[] = $issue_collation;
}
if ($issues) {
$adjustments[] = array(
'kind' => 'database',
'database' => $database_name,
'issues' => $issues,
'charset' => $expect_database->getCharacterSet(),
'collation' => $expect_database->getCollation(),
);
}
foreach ($database->getTables() as $table_name => $table) {
foreach ($this->findErrors($table) as $issue) {
$errors[] = array(
'database' => $database_name,
'table' => $table_name,
'issue' => $issue,
);
}
$expect_table = $expect_database->getTable($table_name);
$actual_table = $actual_database->getTable($table_name);
if (!$expect_table || !$actual_table) {
continue;
}
$issues = array();
if ($table->hasIssue($issue_collation)) {
$issues[] = $issue_collation;
}
if ($table->hasIssue($issue_engine)) {
$issues[] = $issue_engine;
}
if ($issues) {
$adjustments[] = array(
'kind' => 'table',
'database' => $database_name,
'table' => $table_name,
'issues' => $issues,
'collation' => $expect_table->getCollation(),
'engine' => $expect_table->getEngine(),
);
}
foreach ($table->getColumns() as $column_name => $column) {
foreach ($this->findErrors($column) as $issue) {
$errors[] = array(
'database' => $database_name,
'table' => $table_name,
'name' => $column_name,
'issue' => $issue,
);
}
$expect_column = $expect_table->getColumn($column_name);
$actual_column = $actual_table->getColumn($column_name);
if (!$expect_column || !$actual_column) {
continue;
}
$issues = array();
if ($column->hasIssue($issue_collation)) {
$issues[] = $issue_collation;
}
if ($column->hasIssue($issue_charset)) {
$issues[] = $issue_charset;
}
if ($column->hasIssue($issue_columntype)) {
$issues[] = $issue_columntype;
}
if ($column->hasIssue($issue_auto)) {
$issues[] = $issue_auto;
}
if ($issues) {
if ($expect_column->getCharacterSet() === null) {
// For non-text columns, we won't be specifying a collation or
// character set.
$charset = null;
$collation = null;
} else {
$charset = $expect_column->getCharacterSet();
$collation = $expect_column->getCollation();
}
$adjustment = array(
'kind' => 'column',
'database' => $database_name,
'table' => $table_name,
'name' => $column_name,
'issues' => $issues,
'collation' => $collation,
'charset' => $charset,
'type' => $expect_column->getColumnType(),
// NOTE: We don't adjust column nullability because it is
// dangerous, so always use the current nullability.
'nullable' => $actual_column->getNullable(),
// NOTE: This always stores the current value, because we have
// to make these updates separately.
'is_auto' => $actual_column->getAutoIncrement(),
);
if ($column->hasIssue($issue_auto)) {
$adjustment['auto'] = $expect_column->getAutoIncrement();
}
$adjustments[] = $adjustment;
}
}
foreach ($table->getKeys() as $key_name => $key) {
foreach ($this->findErrors($key) as $issue) {
$errors[] = array(
'database' => $database_name,
'table' => $table_name,
'name' => $key_name,
'issue' => $issue,
);
}
$expect_key = $expect_table->getKey($key_name);
$actual_key = $actual_table->getKey($key_name);
$issues = array();
$keep_key = true;
if ($key->hasIssue($issue_surpluskey)) {
$issues[] = $issue_surpluskey;
$keep_key = false;
}
if ($key->hasIssue($issue_missingkey)) {
$issues[] = $issue_missingkey;
}
if ($key->hasIssue($issue_columns)) {
$issues[] = $issue_columns;
}
if ($key->hasIssue($issue_unique)) {
$issues[] = $issue_unique;
}
// NOTE: We can't really fix this, per se, but we may need to remove
// the key to change the column type. In the best case, the new
// column type won't be overlong and recreating the key really will
// fix the issue. In the worst case, we get the right column type and
// lose the key, which is still better than retaining the key having
// the wrong column type.
if ($key->hasIssue($issue_longkey)) {
$issues[] = $issue_longkey;
}
if ($issues) {
$adjustment = array(
'kind' => 'key',
'database' => $database_name,
'table' => $table_name,
'name' => $key_name,
'issues' => $issues,
'exists' => (bool)$actual_key,
'keep' => $keep_key,
);
if ($keep_key) {
$adjustment += array(
'columns' => $expect_key->getColumnNames(),
'unique' => $expect_key->getUnique(),
'indexType' => $expect_key->getIndexType(),
);
}
$adjustments[] = $adjustment;
}
}
}
}
return array($adjustments, $errors);
}
private function findErrors(PhabricatorConfigStorageSchema $schema) {
$result = array();
foreach ($schema->getLocalIssues() as $issue) {
$status = PhabricatorConfigStorageSchema::getIssueStatus($issue);
if ($status == PhabricatorConfigStorageSchema::STATUS_FAIL) {
$result[] = $issue;
}
}
return $result;
}
private function printErrors(array $errors, $default_return) {
if (!$errors) {
return $default_return;
}
$console = PhutilConsole::getConsole();
$table = id(new PhutilConsoleTable())
->addColumn('target', array('title' => pht('Target')))
->addColumn('error', array('title' => pht('Error')));
$any_surplus = false;
$all_surplus = true;
$any_access = false;
$all_access = true;
foreach ($errors as $error) {
$pieces = array_select_keys(
$error,
array('database', 'table', 'name'));
$pieces = array_filter($pieces);
$target = implode('.', $pieces);
$name = PhabricatorConfigStorageSchema::getIssueName($error['issue']);
$issue = $error['issue'];
if ($issue === PhabricatorConfigStorageSchema::ISSUE_SURPLUS) {
$any_surplus = true;
} else {
$all_surplus = false;
}
if ($issue === PhabricatorConfigStorageSchema::ISSUE_ACCESSDENIED) {
$any_access = true;
} else {
$all_access = false;
}
$table->addRow(
array(
'target' => $target,
'error' => $name,
));
}
$console->writeOut("\n");
$table->draw();
$console->writeOut("\n");
$message = array();
if ($all_surplus) {
$message[] = pht(
- 'You have surplus schemata (extra tables or columns which Phabricator '.
- 'does not expect). For information on resolving these '.
+ 'You have surplus schemata (extra tables or columns which this '.
+ 'software does not expect). For information on resolving these '.
'issues, see the "Surplus Schemata" section in the "Managing Storage '.
'Adjustments" article in the documentation.');
} else if ($all_access) {
$message[] = pht(
'The user you are connecting to MySQL with does not have the correct '.
'permissions, and can not access some databases or tables that it '.
'needs to be able to access. GRANT the user additional permissions.');
} else {
$message[] = pht(
'The schemata have errors (detailed above) which the adjustment '.
'workflow can not fix.');
if ($any_access) {
$message[] = pht(
'Some of these errors are caused by access control problems. '.
'The user you are connecting with does not have permission to see '.
- 'all of the database or tables that Phabricator uses. You need to '.
+ 'all of the database or tables that this software uses. You need to '.
'GRANT the user more permission, or use a different user.');
}
if ($any_surplus) {
$message[] = pht(
'Some of these errors are caused by surplus schemata (extra '.
- 'tables or columns which Phabricator does not expect). These are '.
+ 'tables or columns which this software does not expect). These are '.
'not serious. For information on resolving these issues, see the '.
'"Surplus Schemata" section in the "Managing Storage Adjustments" '.
'article in the documentation.');
}
$message[] = pht(
- 'If you are not developing Phabricator itself, report this issue to '.
- 'the upstream.');
+ 'If you are not developing %s itself, report this issue to '.
+ 'the upstream.',
+ PlatformSymbols::getPlatformServerName());
$message[] = pht(
- 'If you are developing Phabricator, these errors usually indicate '.
+ 'If you are developing %s, these errors usually indicate '.
'that your schema specifications do not agree with the schemata your '.
- 'code actually builds.');
+ 'code actually builds.',
+ PlatformSymbols::getPlatformServerName());
}
$message = implode("\n\n", $message);
if ($all_surplus) {
$console->writeOut(
"**<bg:yellow> %s </bg>**\n\n%s\n",
pht('SURPLUS SCHEMATA'),
phutil_console_wrap($message));
} else if ($all_access) {
$console->writeOut(
"**<bg:yellow> %s </bg>**\n\n%s\n",
pht('ACCESS DENIED'),
phutil_console_wrap($message));
} else {
$console->writeOut(
"**<bg:red> %s </bg>**\n\n%s\n",
pht('SCHEMATA ERRORS'),
phutil_console_wrap($message));
}
return 2;
}
final protected function upgradeSchemata(
array $apis,
$apply_only = null,
$no_quickstart = false,
$init_only = false) {
$locks = array();
foreach ($apis as $api) {
$locks[] = $this->lock($api);
}
try {
$this->doUpgradeSchemata($apis, $apply_only, $no_quickstart, $init_only);
} catch (Exception $ex) {
foreach ($locks as $lock) {
$lock->unlock();
}
throw $ex;
}
foreach ($locks as $lock) {
$lock->unlock();
}
}
private function doUpgradeSchemata(
array $apis,
$apply_only,
$no_quickstart,
$init_only) {
$patches = $this->patches;
$is_dryrun = $this->dryRun;
// We expect that patches should already be sorted properly. However,
// phase behavior will be wrong if they aren't, so make sure.
$patches = msortv($patches, 'newSortVector');
$api_map = array();
foreach ($apis as $api) {
$api_map[$api->getRef()->getRefKey()] = $api;
}
foreach ($api_map as $ref_key => $api) {
$applied = $api->getAppliedPatches();
$needs_init = ($applied === null);
if (!$needs_init) {
continue;
}
if ($is_dryrun) {
echo tsprintf(
"%s\n",
pht(
'DRYRUN: Storage on host "%s" does not exist yet, so it '.
'would be created.',
$ref_key));
continue;
}
if ($apply_only) {
throw new PhutilArgumentUsageException(
pht(
'Storage on host "%s" has not been initialized yet. You must '.
'initialize storage before selectively applying patches.',
$ref_key));
}
// If we're initializing storage for the first time on any host, track
// it so that we can give the user a nicer experience during the
// subsequent adjustment phase.
$this->didInitialize = true;
$legacy = $api->getLegacyPatches($patches);
if ($legacy || $no_quickstart || $init_only) {
// If we have legacy patches, we can't quickstart.
$api->createDatabase('meta_data');
$api->createTable(
'meta_data',
'patch_status',
array(
'patch VARCHAR(255) NOT NULL PRIMARY KEY COLLATE utf8_general_ci',
'applied INT UNSIGNED NOT NULL',
));
foreach ($legacy as $patch) {
$api->markPatchApplied($patch);
}
} else {
echo tsprintf(
"%s\n",
pht(
'Loading quickstart template onto "%s"...',
$ref_key));
$root = dirname(phutil_get_library_root('phabricator'));
$sql = $root.'/resources/sql/quickstart.sql';
$api->applyPatchSQL($sql);
}
}
if ($init_only) {
echo pht('Storage initialized.')."\n";
return 0;
}
$applied_map = array();
$state_map = array();
foreach ($api_map as $ref_key => $api) {
$applied = $api->getAppliedPatches();
// If we still have nothing applied, this is a dry run and we didn't
// actually initialize storage. Here, just do nothing.
if ($applied === null) {
if ($is_dryrun) {
continue;
} else {
throw new Exception(
pht(
'Database initialization on host "%s" applied no patches!',
$ref_key));
}
}
$applied = array_fuse($applied);
$state_map[$ref_key] = $applied;
if ($apply_only) {
if (isset($applied[$apply_only])) {
if (!$this->force && !$is_dryrun) {
echo phutil_console_wrap(
pht(
'Patch "%s" has already been applied on host "%s". Are you '.
'sure you want to apply it again? This may put your storage '.
'in a state that the upgrade scripts can not automatically '.
'manage.',
$apply_only,
$ref_key));
if (!phutil_console_confirm(pht('Apply patch again?'))) {
echo pht('Cancelled.')."\n";
return 1;
}
}
// Mark this patch as not yet applied on this host.
unset($applied[$apply_only]);
}
}
$applied_map[$ref_key] = $applied;
}
// If we're applying only a specific patch, select just that patch.
if ($apply_only) {
$patches = array_select_keys($patches, array($apply_only));
}
// Apply each patch to each database. We apply patches patch-by-patch,
// not database-by-database: for each patch we apply it to every database,
// then move to the next patch.
// We must do this because ".php" patches may depend on ".sql" patches
// being up to date on all masters, and that will work fine if we put each
// patch on every host before moving on. If we try to bring database hosts
// up to date one at a time we can end up in a big mess.
$duration_map = array();
// First, find any global patches which have been applied to ANY database.
// We are just going to mark these as applied without actually running
// them. Otherwise, adding new empty masters to an existing cluster will
// try to apply them against invalid states.
foreach ($patches as $key => $patch) {
if ($patch->getIsGlobalPatch()) {
foreach ($applied_map as $ref_key => $applied) {
if (isset($applied[$key])) {
$duration_map[$key] = 1;
}
}
}
}
while (true) {
$applied_something = false;
foreach ($patches as $key => $patch) {
// First, check if any databases need this patch. We can just skip it
// if it has already been applied everywhere.
$need_patch = array();
foreach ($applied_map as $ref_key => $applied) {
if (isset($applied[$key])) {
continue;
}
$need_patch[] = $ref_key;
}
if (!$need_patch) {
unset($patches[$key]);
continue;
}
// Check if we can apply this patch yet. Before we can apply a patch,
// all of the dependencies for the patch must have been applied on all
// databases. Requiring that all databases stay in sync prevents one
// database from racing ahead if it happens to get a patch that nothing
// else has yet.
$missing_patch = null;
foreach ($patch->getAfter() as $after) {
foreach ($applied_map as $ref_key => $applied) {
if (isset($applied[$after])) {
// This database already has the patch. We can apply it to
// other databases but don't need to apply it here.
continue;
}
$missing_patch = $after;
break 2;
}
}
if ($missing_patch) {
if ($apply_only) {
echo tsprintf(
"%s\n",
pht(
'Unable to apply patch "%s" because it depends on patch '.
'"%s", which has not been applied on some hosts: %s.',
$apply_only,
$missing_patch,
implode(', ', $need_patch)));
return 1;
} else {
// Some databases are missing the dependencies, so keep trying
// other patches instead. If everything goes right, we'll apply the
// dependencies and then come back and apply this patch later.
continue;
}
}
$is_global = $patch->getIsGlobalPatch();
$patch_apis = array_select_keys($api_map, $need_patch);
foreach ($patch_apis as $ref_key => $api) {
if ($is_global) {
// If this is a global patch which we previously applied, just
// read the duration from the map without actually applying
// the patch.
$duration = idx($duration_map, $key);
} else {
$duration = null;
}
if ($duration === null) {
if ($is_dryrun) {
echo tsprintf(
"%s\n",
pht(
'DRYRUN: Would apply patch "%s" to host "%s".',
$key,
$ref_key));
} else {
echo tsprintf(
"%s\n",
pht(
'Applying patch "%s" to host "%s"...',
$key,
$ref_key));
}
$t_begin = microtime(true);
if (!$is_dryrun) {
$api->applyPatch($patch);
}
$t_end = microtime(true);
$duration = ($t_end - $t_begin);
$duration_map[$key] = $duration;
}
// If we're explicitly reapplying this patch, we don't need to
// mark it as applied.
if (!isset($state_map[$ref_key][$key])) {
if (!$is_dryrun) {
$api->markPatchApplied($key, ($t_end - $t_begin));
}
$applied_map[$ref_key][$key] = true;
}
}
// We applied this everywhere, so we're done with the patch.
unset($patches[$key]);
$applied_something = true;
}
if (!$applied_something) {
if ($patches) {
throw new Exception(
pht(
'Some patches could not be applied: %s',
implode(', ', array_keys($patches))));
} else if (!$is_dryrun && !$apply_only) {
echo pht(
'Storage is up to date. Use "%s" for details.',
'storage status')."\n";
}
break;
}
}
}
final protected function getBareHostAndPort($host) {
// Split out port information, since the command-line client requires a
// separate flag for the port.
$uri = new PhutilURI('mysql://'.$host);
if ($uri->getPort()) {
$port = $uri->getPort();
$bare_hostname = $uri->getDomain();
} else {
$port = null;
$bare_hostname = $host;
}
return array($bare_hostname, $port);
}
/**
* Acquires a @{class:PhabricatorGlobalLock}.
*
* @return PhabricatorGlobalLock
*/
final protected function lock(PhabricatorStorageManagementAPI $api) {
// Although we're holding this lock on different databases so it could
// have the same name on each as far as the database is concerned, the
// locks would be the same within this process.
$parameters = array(
'refKey' => $api->getRef()->getRefKey(),
);
// We disable logging for this lock because we may not have created the
// log table yet, or may need to adjust it.
return PhabricatorGlobalLock::newLock('adjust', $parameters)
->setExternalConnection($api->getConn(null))
->setDisableLogging(true)
->lock();
}
final protected function analyzeTables(
PhabricatorStorageManagementAPI $api) {
// Analyzing tables can sometimes have a significant effect on query
// performance, particularly for the fulltext ngrams tables. See T12819
// for some specific examples.
$conn = $api->getConn(null);
$patches = $this->getPatches();
$databases = $api->getDatabaseList($patches, true);
$this->logInfo(
pht('ANALYZE'),
pht('Analyzing tables...'));
$targets = array();
foreach ($databases as $database) {
queryfx($conn, 'USE %C', $database);
$tables = queryfx_all($conn, 'SHOW TABLE STATUS');
foreach ($tables as $table) {
$table_name = $table['Name'];
$targets[] = array(
'database' => $database,
'table' => $table_name,
);
}
}
$bar = id(new PhutilConsoleProgressBar())
->setTotal(count($targets));
foreach ($targets as $target) {
queryfx(
$conn,
'ANALYZE TABLE %T.%T',
$target['database'],
$target['table']);
$bar->update(1);
}
$bar->done();
$this->logOkay(
pht('ANALYZED'),
pht(
'Analyzed %d table(s).',
count($targets)));
}
}
diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
index 8adcfa64df..f68635860a 100644
--- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
+++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
@@ -1,783 +1,781 @@
<?php
final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
public function getNamespace() {
return 'phabricator';
}
private function getPatchPath($file) {
$root = dirname(phutil_get_library_root('phabricator'));
$path = $root.'/resources/sql/patches/'.$file;
// Make sure it exists.
Filesystem::readFile($path);
return $path;
}
public function getPatches() {
$patches = array();
foreach ($this->getOldPatches() as $old_name => $old_patch) {
if (preg_match('/^db\./', $old_name)) {
$old_patch['name'] = substr($old_name, 3);
$old_patch['type'] = 'db';
} else {
if (empty($old_patch['name'])) {
$old_patch['name'] = $this->getPatchPath($old_name);
}
if (empty($old_patch['type'])) {
$matches = null;
preg_match('/\.(sql|php)$/', $old_name, $matches);
$old_patch['type'] = $matches[1];
}
}
$patches[$old_name] = $old_patch;
}
$root = dirname(phutil_get_library_root('phabricator'));
$auto_root = $root.'/resources/sql/autopatches/';
$patches += $this->buildPatchesFromDirectory($auto_root);
return $patches;
}
public function getOldPatches() {
return array(
'db.audit' => array(
'after' => array( /* First Patch */ ),
),
'db.calendar' => array(),
'db.chatlog' => array(),
'db.conduit' => array(),
'db.countdown' => array(),
'db.daemon' => array(),
'db.differential' => array(),
'db.draft' => array(),
'db.drydock' => array(),
'db.feed' => array(),
'db.file' => array(),
'db.flag' => array(),
'db.harbormaster' => array(),
'db.herald' => array(),
'db.maniphest' => array(),
'db.meta_data' => array(),
'db.metamta' => array(),
'db.oauth_server' => array(),
'db.owners' => array(),
'db.pastebin' => array(
'dead' => true,
),
'db.phame' => array(),
'db.phriction' => array(),
'db.project' => array(),
'db.repository' => array(),
'db.search' => array(),
'db.slowvote' => array(),
'db.timeline' => array(
'dead' => true,
),
'db.user' => array(),
'db.worker' => array(),
'db.xhpast' => array(),
'db.xhpastview' => array(
'dead' => true,
),
'db.cache' => array(),
'db.fact' => array(),
'db.ponder' => array(),
'db.xhprof' => array(),
'db.pholio' => array(),
'db.conpherence' => array(),
'db.config' => array(),
'db.token' => array(),
- 'db.releeph' => array(),
'db.phlux' => array(),
'db.phortune' => array(),
'db.phrequent' => array(),
'db.diviner' => array(),
'db.auth' => array(),
'db.doorkeeper' => array(),
'db.legalpad' => array(),
'db.policy' => array(),
'db.nuance' => array(),
'db.passphrase' => array(),
- 'db.phragment' => array(),
'db.dashboard' => array(),
'db.system' => array(),
'db.fund' => array(),
'db.almanac' => array(),
'db.multimeter' => array(),
'db.spaces' => array(),
'db.phurl' => array(),
'db.badges' => array(),
'db.packages' => array(),
'db.application' => array(),
'db.paste' => array(),
'0000.legacy.sql' => array(
'legacy' => 0,
),
'000.project.sql' => array(
'legacy' => 0,
),
'001.maniphest_projects.sql' => array(
'legacy' => 1,
),
'002.oauth.sql' => array(
'legacy' => 2,
),
'003.more_oauth.sql' => array(
'legacy' => 3,
),
'004.daemonrepos.sql' => array(
'legacy' => 4,
),
'005.workers.sql' => array(
'legacy' => 5,
),
'006.repository.sql' => array(
'legacy' => 6,
),
'007.daemonlog.sql' => array(
'legacy' => 7,
),
'008.repoopt.sql' => array(
'legacy' => 8,
),
'009.repo_summary.sql' => array(
'legacy' => 9,
),
'010.herald.sql' => array(
'legacy' => 10,
),
'011.badcommit.sql' => array(
'legacy' => 11,
),
'012.dropphidtype.sql' => array(
'legacy' => 12,
),
'013.commitdetail.sql' => array(
'legacy' => 13,
),
'014.shortcuts.sql' => array(
'legacy' => 14,
),
'015.preferences.sql' => array(
'legacy' => 15,
),
'016.userrealnameindex.sql' => array(
'legacy' => 16,
),
'017.sessionkeys.sql' => array(
'legacy' => 17,
),
'018.owners.sql' => array(
'legacy' => 18,
),
'019.arcprojects.sql' => array(
'legacy' => 19,
),
'020.pathcapital.sql' => array(
'legacy' => 20,
),
'021.xhpastview.sql' => array(
'legacy' => 21,
),
'022.differentialcommit.sql' => array(
'legacy' => 22,
),
'023.dxkeys.sql' => array(
'legacy' => 23,
),
'024.mlistkeys.sql' => array(
'legacy' => 24,
),
'025.commentopt.sql' => array(
'legacy' => 25,
),
'026.diffpropkey.sql' => array(
'legacy' => 26,
),
'027.metamtakeys.sql' => array(
'legacy' => 27,
),
'028.systemagent.sql' => array(
'legacy' => 28,
),
'029.cursors.sql' => array(
'legacy' => 29,
),
'030.imagemacro.sql' => array(
'legacy' => 30,
),
'031.workerrace.sql' => array(
'legacy' => 31,
),
'032.viewtime.sql' => array(
'legacy' => 32,
),
'033.privtest.sql' => array(
'legacy' => 33,
),
'034.savedheader.sql' => array(
'legacy' => 34,
),
'035.proxyimage.sql' => array(
'legacy' => 35,
),
'036.mailkey.sql' => array(
'legacy' => 36,
),
'037.setuptest.sql' => array(
'legacy' => 37,
),
'038.admin.sql' => array(
'legacy' => 38,
),
'039.userlog.sql' => array(
'legacy' => 39,
),
'040.transform.sql' => array(
'legacy' => 40,
),
'041.heraldrepetition.sql' => array(
'legacy' => 41,
),
'042.commentmetadata.sql' => array(
'legacy' => 42,
),
'043.pastebin.sql' => array(
'legacy' => 43,
),
'044.countdown.sql' => array(
'legacy' => 44,
),
'045.timezone.sql' => array(
'legacy' => 45,
),
'046.conduittoken.sql' => array(
'legacy' => 46,
),
'047.projectstatus.sql' => array(
'legacy' => 47,
),
'048.relationshipkeys.sql' => array(
'legacy' => 48,
),
'049.projectowner.sql' => array(
'legacy' => 49,
),
'050.taskdenormal.sql' => array(
'legacy' => 50,
),
'051.projectfilter.sql' => array(
'legacy' => 51,
),
'052.pastelanguage.sql' => array(
'legacy' => 52,
),
'053.feed.sql' => array(
'legacy' => 53,
),
'054.subscribers.sql' => array(
'legacy' => 54,
),
'055.add_author_to_files.sql' => array(
'legacy' => 55,
),
'056.slowvote.sql' => array(
'legacy' => 56,
),
'057.parsecache.sql' => array(
'legacy' => 57,
),
'058.missingkeys.sql' => array(
'legacy' => 58,
),
'059.engines.php' => array(
'legacy' => 59,
),
'060.phriction.sql' => array(
'legacy' => 60,
),
'061.phrictioncontent.sql' => array(
'legacy' => 61,
),
'062.phrictionmenu.sql' => array(
'legacy' => 62,
),
'063.pasteforks.sql' => array(
'legacy' => 63,
),
'064.subprojects.sql' => array(
'legacy' => 64,
),
'065.sshkeys.sql' => array(
'legacy' => 65,
),
'066.phrictioncontent.sql' => array(
'legacy' => 66,
),
'067.preferences.sql' => array(
'legacy' => 67,
),
'068.maniphestauxiliarystorage.sql' => array(
'legacy' => 68,
),
'069.heraldxscript.sql' => array(
'legacy' => 69,
),
'070.differentialaux.sql' => array(
'legacy' => 70,
),
'071.contentsource.sql' => array(
'legacy' => 71,
),
'072.blamerevert.sql' => array(
'legacy' => 72,
),
'073.reposymbols.sql' => array(
'legacy' => 73,
),
'074.affectedpath.sql' => array(
'legacy' => 74,
),
'075.revisionhash.sql' => array(
'legacy' => 75,
),
'076.indexedlanguages.sql' => array(
'legacy' => 76,
),
'077.originalemail.sql' => array(
'legacy' => 77,
),
'078.nametoken.sql' => array(
'legacy' => 78,
),
'079.nametokenindex.php' => array(
'legacy' => 79,
),
'080.filekeys.sql' => array(
'legacy' => 80,
),
'081.filekeys.php' => array(
'legacy' => 81,
),
'082.xactionkey.sql' => array(
'legacy' => 82,
),
'083.dxviewtime.sql' => array(
'legacy' => 83,
),
'084.pasteauthorkey.sql' => array(
'legacy' => 84,
),
'085.packagecommitrelationship.sql' => array(
'legacy' => 85,
),
'086.formeraffil.sql' => array(
'legacy' => 86,
),
'087.phrictiondelete.sql' => array(
'legacy' => 87,
),
'088.audit.sql' => array(
'legacy' => 88,
),
'089.projectwiki.sql' => array(
'legacy' => 89,
),
'090.forceuniqueprojectnames.php' => array(
'legacy' => 90,
),
'091.uniqueslugkey.sql' => array(
'legacy' => 91,
),
'092.dropgithubnotification.sql' => array(
'legacy' => 92,
),
'093.gitremotes.php' => array(
'legacy' => 93,
),
'094.phrictioncolumn.sql' => array(
'legacy' => 94,
),
'095.directory.sql' => array(
'legacy' => 95,
),
'096.filename.sql' => array(
'legacy' => 96,
),
'097.heraldruletypes.sql' => array(
'legacy' => 97,
),
'098.heraldruletypemigration.php' => array(
'legacy' => 98,
),
'099.drydock.sql' => array(
'legacy' => 99,
),
'100.projectxaction.sql' => array(
'legacy' => 100,
),
'101.heraldruleapplied.sql' => array(
'legacy' => 101,
),
'102.heraldcleanup.php' => array(
'legacy' => 102,
),
'103.heraldedithistory.sql' => array(
'legacy' => 103,
),
'104.searchkey.sql' => array(
'legacy' => 104,
),
'105.mimetype.sql' => array(
'legacy' => 105,
),
'106.chatlog.sql' => array(
'legacy' => 106,
),
'107.oauthserver.sql' => array(
'legacy' => 107,
),
'108.oauthscope.sql' => array(
'legacy' => 108,
),
'109.oauthclientphidkey.sql' => array(
'legacy' => 109,
),
'110.commitaudit.sql' => array(
'legacy' => 110,
),
'111.commitauditmigration.php' => array(
'legacy' => 111,
),
'112.oauthaccesscoderedirecturi.sql' => array(
'legacy' => 112,
),
'113.lastreviewer.sql' => array(
'legacy' => 113,
),
'114.auditrequest.sql' => array(
'legacy' => 114,
),
'115.prepareutf8.sql' => array(
'legacy' => 115,
),
'116.utf8-backup-first-expect-wait.sql' => array(
'legacy' => 116,
),
'117.repositorydescription.php' => array(
'legacy' => 117,
),
'118.auditinline.sql' => array(
'legacy' => 118,
),
'119.filehash.sql' => array(
'legacy' => 119,
),
'120.noop.sql' => array(
'legacy' => 120,
),
'121.drydocklog.sql' => array(
'legacy' => 121,
),
'122.flag.sql' => array(
'legacy' => 122,
),
'123.heraldrulelog.sql' => array(
'legacy' => 123,
),
'124.subpriority.sql' => array(
'legacy' => 124,
),
'125.ipv6.sql' => array(
'legacy' => 125,
),
'126.edges.sql' => array(
'legacy' => 126,
),
'127.userkeybody.sql' => array(
'legacy' => 127,
),
'128.phabricatorcom.sql' => array(
'legacy' => 128,
),
'129.savedquery.sql' => array(
'legacy' => 129,
),
'130.denormalrevisionquery.sql' => array(
'legacy' => 130,
),
'131.migraterevisionquery.php' => array(
'legacy' => 131,
),
'132.phame.sql' => array(
'legacy' => 132,
),
'133.imagemacro.sql' => array(
'legacy' => 133,
),
'134.emptysearch.sql' => array(
'legacy' => 134,
),
'135.datecommitted.sql' => array(
'legacy' => 135,
),
'136.sex.sql' => array(
'legacy' => 136,
),
'137.auditmetadata.sql' => array(
'legacy' => 137,
),
'138.notification.sql' => array(),
'holidays.sql' => array(),
'userstatus.sql' => array(),
'emailtable.sql' => array(),
'emailtableport.sql' => array(
// NOTE: This is a ".php" patch, but the key is ".sql".
'type' => 'php',
'name' => $this->getPatchPath('emailtableport.php'),
),
'emailtableremove.sql' => array(),
'phiddrop.sql' => array(),
'testdatabase.sql' => array(),
'ldapinfo.sql' => array(),
'threadtopic.sql' => array(),
'usertranslation.sql' => array(),
'differentialbookmarks.sql' => array(),
'harbormasterobject.sql' => array(),
'markupcache.sql' => array(),
'maniphestxcache.sql' => array(),
'migrate-maniphest-dependencies.php' => array(),
'migrate-differential-dependencies.php' => array(),
'phameblog.sql' => array(),
'migrate-maniphest-revisions.php' => array(),
'daemonstatus.sql' => array(),
'symbolcontexts.sql' => array(),
'migrate-project-edges.php' => array(),
'fact-raw.sql' => array(),
'ponder.sql' => array(),
'policy-project.sql' => array(),
'daemonstatuskey.sql' => array(),
'edgetype.sql' => array(),
'ponder-comments.sql' => array(),
'pastepolicy.sql' => array(),
'xhprof.sql' => array(),
'draft-metadata.sql' => array(),
'phamedomain.sql' => array(),
'ponder-mailkey.sql' => array(),
'ponder-mailkey-populate.php' => array(),
'phamepolicy.sql' => array(),
'phameoneblog.sql' => array(),
'statustxt.sql' => array(),
'daemontaskarchive.sql' => array(),
'drydocktaskid.sql' => array(),
'drydockresoucetype.sql' => array(
// NOTE: The key for this patch misspells "resource" as "resouce".
'name' => $this->getPatchPath('drydockresourcetype.sql'),
),
'liskcounters.sql' => array(),
'liskcounters.php' => array(),
'dropfileproxyimage.sql' => array(),
'repository-lint.sql' => array(),
'liskcounters-task.sql' => array(),
'pholio.sql' => array(),
'owners-exclude.sql' => array(),
'20121209.pholioxactions.sql' => array(),
'20121209.xmacroadd.sql' => array(),
'20121209.xmacromigrate.php' => array(),
'20121209.xmacromigratekey.sql' => array(),
'20121220.generalcache.sql' => array(),
'20121226.config.sql' => array(),
'20130101.confxaction.sql' => array(),
'20130102.metamtareceivedmailmessageidhash.sql' => array(),
'20130103.filemetadata.sql' => array(),
'20130111.conpherence.sql' => array(),
'20130127.altheraldtranscript.sql' => array(),
'20130201.revisionunsubscribed.php' => array(),
'20130201.revisionunsubscribed.sql' => array(),
'20130131.conpherencepics.sql' => array(),
'20130214.chatlogchannel.sql' => array(),
'20130214.chatlogchannelid.sql' => array(),
'20130214.token.sql' => array(),
'20130215.phabricatorfileaddttl.sql' => array(),
'20130217.cachettl.sql' => array(),
'20130218.updatechannelid.php' => array(),
'20130218.longdaemon.sql' => array(),
'20130219.commitsummary.sql' => array(),
'20130219.commitsummarymig.php' => array(),
'20130222.dropchannel.sql' => array(),
'20130226.commitkey.sql' => array(),
'20131302.maniphestvalue.sql' => array(),
'20130304.lintauthor.sql' => array(),
'releeph.sql' => array(),
'20130319.phabricatorfileexplicitupload.sql' => array(),
'20130319.conpherence.sql' => array(),
'20130320.phlux.sql' => array(),
'20130317.phrictionedge.sql' => array(),
'20130321.token.sql' => array(),
'20130310.xactionmeta.sql' => array(),
'20130322.phortune.sql' => array(),
'20130323.phortunepayment.sql' => array(),
'20130324.phortuneproduct.sql' => array(),
'20130330.phrequent.sql' => array(),
'20130403.conpherencecache.sql' => array(),
'20130403.conpherencecachemig.php' => array(),
'20130409.commitdrev.php' => array(),
'20130417.externalaccount.sql' => array(),
'20130423.updateexternalaccount.sql' => array(),
'20130423.phortunepaymentrevised.sql' => array(),
'20130423.conpherenceindices.sql' => array(),
'20130426.search_savedquery.sql' => array(),
'20130502.countdownrevamp1.sql' => array(),
'20130502.countdownrevamp2.php' => array(),
'20130502.countdownrevamp3.sql' => array(),
'20130507.releephrqsimplifycols.sql' => array(),
'20130507.releephrqmailkey.sql' => array(),
'20130507.releephrqmailkeypop.php' => array(),
'20130508.search_namedquery.sql' => array(),
'20130508.releephtransactions.sql' => array(),
'20130508.releephtransactionsmig.php' => array(),
'20130513.receviedmailstatus.sql' => array(),
'20130519.diviner.sql' => array(),
'20130521.dropconphimages.sql' => array(),
'20130523.maniphest_owners.sql' => array(),
'20130524.repoxactions.sql' => array(),
'20130529.macroauthor.sql' => array(),
'20130529.macroauthormig.php' => array(),
'20130530.sessionhash.php' => array(),
'20130530.macrodatekey.sql' => array(),
'20130530.pastekeys.sql' => array(),
'20130531.filekeys.sql' => array(),
'20130602.morediviner.sql' => array(),
'20130602.namedqueries.sql' => array(),
'20130606.userxactions.sql' => array(),
'20130607.xaccount.sql' => array(),
'20130611.migrateoauth.php' => array(),
'20130611.nukeldap.php' => array(),
'20130613.authdb.sql' => array(),
'20130619.authconf.php' => array(),
'20130620.diffxactions.sql' => array(),
'20130621.diffcommentphid.sql' => array(),
'20130621.diffcommentphidmig.php' => array(),
'20130621.diffcommentunphid.sql' => array(),
'20130622.doorkeeper.sql' => array(),
'20130628.legalpadv0.sql' => array(),
'20130701.conduitlog.sql' => array(),
'legalpad-mailkey.sql' => array(),
'legalpad-mailkey-populate.php' => array(),
'20130703.legalpaddocdenorm.sql' => array(),
'20130703.legalpaddocdenorm.php' => array(),
'20130709.legalpadsignature.sql' => array(),
'20130709.droptimeline.sql' => array(),
'20130711.trimrealnames.php' => array(),
'20130714.votexactions.sql' => array(),
'20130715.votecomments.php' => array(),
'20130715.voteedges.sql' => array(),
'20130711.pholioimageobsolete.sql' => array(),
'20130711.pholioimageobsolete.php' => array(),
'20130711.pholioimageobsolete2.sql' => array(),
'20130716.archivememberlessprojects.php' => array(),
'20130722.pholioreplace.sql' => array(),
'20130723.taskstarttime.sql' => array(),
'20130727.ponderquestionstatus.sql' => array(),
'20130726.ponderxactions.sql' => array(),
'20130728.ponderunique.php' => array(),
'20130728.ponderuniquekey.sql' => array(),
'20130728.ponderxcomment.php' => array(),
'20130801.pastexactions.sql' => array(),
'20130801.pastexactions.php' => array(),
'20130805.pastemailkey.sql' => array(),
'20130805.pasteedges.sql' => array(),
'20130805.pastemailkeypop.php' => array(),
'20130802.heraldphid.sql' => array(),
'20130802.heraldphids.php' => array(),
'20130802.heraldphidukey.sql' => array(),
'20130802.heraldxactions.sql' => array(),
'20130731.releephrepoid.sql' => array(),
'20130731.releephproject.sql' => array(),
'20130731.releephcutpointidentifier.sql' => array(),
'20130814.usercustom.sql' => array(),
'20130820.releephxactions.sql' => array(),
'20130826.divinernode.sql' => array(),
'20130820.filexactions.sql' => array(),
'20130820.filemailkey.sql' => array(),
'20130820.file-mailkey-populate.php' => array(),
'20130912.maniphest.1.touch.sql' => array(),
'20130912.maniphest.2.created.sql' => array(),
'20130912.maniphest.3.nameindex.sql' => array(),
'20130912.maniphest.4.fillindex.php' => array(),
'20130913.maniphest.1.migratesearch.php' => array(),
'20130914.usercustom.sql' => array(),
'20130915.maniphestcustom.sql' => array(),
'20130915.maniphestmigrate.php' => array(),
'20130919.mfieldconf.php' => array(),
'20130920.repokeyspolicy.sql' => array(),
'20130921.mtransactions.sql' => array(),
'20130921.xmigratemaniphest.php' => array(),
'20130923.mrename.sql' => array(),
'20130924.mdraftkey.sql' => array(),
'20130925.mpolicy.sql' => array(),
'20130925.xpolicy.sql' => array(),
'20130926.dcustom.sql' => array(),
'20130926.dinkeys.sql' => array(),
'20130927.audiomacro.sql' => array(),
'20130929.filepolicy.sql' => array(),
'20131004.dxedgekey.sql' => array(),
'20131004.dxreviewers.php' => array(),
'20131006.hdisable.sql' => array(),
'20131010.pstorage.sql' => array(),
'20131015.cpolicy.sql' => array(),
'20130915.maniphestqdrop.sql' => array(),
'20130926.dinline.php' => array(),
'20131020.pcustom.sql' => array(),
'20131020.col1.sql' => array(),
'20131020.pxaction.sql' => array(),
'20131020.pxactionmig.php' => array(),
'20131020.harbormaster.sql' => array(),
'20131025.repopush.sql' => array(),
'20131026.commitstatus.sql' => array(),
'20131030.repostatusmessage.sql' => array(),
'20131031.vcspassword.sql' => array(),
'20131105.buildstep.sql' => array(),
'20131106.diffphid.1.col.sql' => array(),
'20131106.diffphid.2.mig.php' => array(),
'20131106.diffphid.3.key.sql' => array(),
'20131106.nuance-v0.sql' => array(),
'20131107.buildlog.sql' => array(),
'20131112.userverified.1.col.sql' => array(),
'20131112.userverified.2.mig.php' => array(),
'20131118.ownerorder.php' => array(),
'20131119.passphrase.sql' => array(),
'20131120.nuancesourcetype.sql' => array(),
'20131121.passphraseedge.sql' => array(),
'20131121.repocredentials.1.col.sql' => array(),
'20131121.repocredentials.2.mig.php' => array(),
'20131122.repomirror.sql' => array(),
'20131123.drydockblueprintpolicy.sql' => array(),
'20131129.drydockresourceblueprint.sql' => array(),
'20131205.buildtargets.sql' => array(),
'20131204.pushlog.sql' => array(),
'20131205.buildsteporder.sql' => array(),
'20131205.buildstepordermig.php' => array(),
'20131206.phragment.sql' => array(),
'20131206.phragmentnull.sql' => array(),
'20131208.phragmentsnapshot.sql' => array(),
'20131211.phragmentedges.sql' => array(),
'20131217.pushlogphid.1.col.sql' => array(),
'20131217.pushlogphid.2.mig.php' => array(),
'20131217.pushlogphid.3.key.sql' => array(),
'20131219.pxdrop.sql' => array(),
'20131224.harbormanual.sql' => array(),
'20131227.heraldobject.sql' => array(),
'20131231.dropshortcut.sql' => array(),
);
// NOTE: STOP! Don't add new patches here.
// Use 'resources/sql/autopatches/' instead!
}
}
diff --git a/src/infrastructure/util/PhabricatorHash.php b/src/infrastructure/util/PhabricatorHash.php
index fdd6609288..8916b37a0e 100644
--- a/src/infrastructure/util/PhabricatorHash.php
+++ b/src/infrastructure/util/PhabricatorHash.php
@@ -1,281 +1,281 @@
<?php
final class PhabricatorHash extends Phobject {
const INDEX_DIGEST_LENGTH = 12;
const ANCHOR_DIGEST_LENGTH = 12;
/**
* Digest a string using HMAC+SHA1.
*
* Because a SHA1 collision is now known, this method should be considered
* weak. Callers should prefer @{method:digestWithNamedKey}.
*
* @param string Input string.
* @return string 32-byte hexadecimal SHA1+HMAC hash.
*/
public static function weakDigest($string, $key = null) {
if ($key === null) {
$key = PhabricatorEnv::getEnvConfig('security.hmac-key');
}
if (!$key) {
throw new Exception(
pht(
- "Set a '%s' in your Phabricator configuration!",
+ "Set a '%s' in your configuration!",
'security.hmac-key'));
}
return hash_hmac('sha1', $string, $key);
}
/**
* Digest a string for use in, e.g., a MySQL index. This produces a short
* (12-byte), case-sensitive alphanumeric string with 72 bits of entropy,
* which is generally safe in most contexts (notably, URLs).
*
* This method emphasizes compactness, and should not be used for security
* related hashing (for general purpose hashing, see @{method:digest}).
*
* @param string Input string.
* @return string 12-byte, case-sensitive, mostly-alphanumeric hash of
* the string.
*/
public static function digestForIndex($string) {
$hash = sha1($string, $raw_output = true);
static $map;
if ($map === null) {
$map = '0123456789'.
'abcdefghij'.
'klmnopqrst'.
'uvwxyzABCD'.
'EFGHIJKLMN'.
'OPQRSTUVWX'.
'YZ._';
}
$result = '';
for ($ii = 0; $ii < self::INDEX_DIGEST_LENGTH; $ii++) {
$result .= $map[(ord($hash[$ii]) & 0x3F)];
}
return $result;
}
/**
* Digest a string for use in HTML page anchors. This is similar to
* @{method:digestForIndex} but produces purely alphanumeric output.
*
* This tries to be mostly compatible with the index digest to limit how
* much stuff we're breaking by switching to it. For additional discussion,
* see T13045.
*
* @param string Input string.
* @return string 12-byte, case-sensitive, purely-alphanumeric hash of
* the string.
*/
public static function digestForAnchor($string) {
$hash = sha1($string, $raw_output = true);
static $map;
if ($map === null) {
$map = '0123456789'.
'abcdefghij'.
'klmnopqrst'.
'uvwxyzABCD'.
'EFGHIJKLMN'.
'OPQRSTUVWX'.
'YZ';
}
$result = '';
$accum = 0;
$map_size = strlen($map);
for ($ii = 0; $ii < self::ANCHOR_DIGEST_LENGTH; $ii++) {
$byte = ord($hash[$ii]);
$low_bits = ($byte & 0x3F);
$accum = ($accum + $byte) % $map_size;
if ($low_bits < $map_size) {
// If an index digest would produce any alphanumeric character, just
// use that character. This means that these digests are the same as
// digests created with "digestForIndex()" in all positions where the
// output character is some character other than "." or "_".
$result .= $map[$low_bits];
} else {
// If an index digest would produce a non-alphumeric character ("." or
// "_"), pick an alphanumeric character instead. We accumulate an
// index into the alphanumeric character list to try to preserve
// entropy here. We could use this strategy for all bytes instead,
// but then these digests would differ from digests created with
// "digestForIndex()" in all positions, instead of just a small number
// of positions.
$result .= $map[$accum];
}
}
return $result;
}
public static function digestToRange($string, $min, $max) {
if ($min > $max) {
throw new Exception(pht('Maximum must be larger than minimum.'));
}
if ($min == $max) {
return $min;
}
$hash = sha1($string, $raw_output = true);
// Make sure this ends up positive, even on 32-bit machines.
$value = head(unpack('L', $hash)) & 0x7FFFFFFF;
return $min + ($value % (1 + $max - $min));
}
/**
* Shorten a string to a maximum byte length in a collision-resistant way
* while retaining some degree of human-readability.
*
* This function converts an input string into a prefix plus a hash. For
* example, a very long string beginning with "crabapplepie..." might be
* digested to something like "crabapp-N1wM1Nz3U84k".
*
* This allows the maximum length of identifiers to be fixed while
* maintaining a high degree of collision resistance and a moderate degree
* of human readability.
*
* @param string The string to shorten.
* @param int Maximum length of the result.
* @return string String shortened in a collision-resistant way.
*/
public static function digestToLength($string, $length) {
// We need at least two more characters than the hash length to fit in a
// a 1-character prefix and a separator.
$min_length = self::INDEX_DIGEST_LENGTH + 2;
if ($length < $min_length) {
throw new Exception(
pht(
'Length parameter in %s must be at least %s, '.
'but %s was provided.',
'digestToLength()',
new PhutilNumber($min_length),
new PhutilNumber($length)));
}
// We could conceivably return the string unmodified if it's shorter than
// the specified length. Instead, always hash it. This makes the output of
// the method more recognizable and consistent (no surprising new behavior
// once you hit a string longer than `$length`) and prevents an attacker
// who can control the inputs from intentionally using the hashed form
// of a string to cause a collision.
$hash = self::digestForIndex($string);
$prefix = substr($string, 0, ($length - ($min_length - 1)));
return $prefix.'-'.$hash;
}
public static function digestWithNamedKey($message, $key_name) {
$key_bytes = self::getNamedHMACKey($key_name);
return self::digestHMACSHA256($message, $key_bytes);
}
public static function digestHMACSHA256($message, $key) {
if (!is_string($message)) {
throw new Exception(
pht('HMAC-SHA256 can only digest strings.'));
}
if (!is_string($key)) {
throw new Exception(
pht('HMAC-SHA256 keys must be strings.'));
}
if (!strlen($key)) {
throw new Exception(
pht('HMAC-SHA256 requires a nonempty key.'));
}
$result = hash_hmac('sha256', $message, $key, $raw_output = false);
// Although "hash_hmac()" is documented as returning `false` when it fails,
// it can also return `null` if you pass an object as the "$message".
if ($result === false || $result === null) {
throw new Exception(
pht('Unable to compute HMAC-SHA256 digest of message.'));
}
return $result;
}
/* -( HMAC Key Management )------------------------------------------------ */
private static function getNamedHMACKey($hmac_name) {
$cache = PhabricatorCaches::getImmutableCache();
$cache_key = "hmac.key({$hmac_name})";
$hmac_key = $cache->getKey($cache_key);
if (($hmac_key === null) || !strlen($hmac_key)) {
$hmac_key = self::readHMACKey($hmac_name);
if ($hmac_key === null) {
$hmac_key = self::newHMACKey($hmac_name);
self::writeHMACKey($hmac_name, $hmac_key);
}
$cache->setKey($cache_key, $hmac_key);
}
// The "hex2bin()" function doesn't exist until PHP 5.4.0 so just
// implement it inline.
$result = '';
for ($ii = 0; $ii < strlen($hmac_key); $ii += 2) {
$result .= pack('H*', substr($hmac_key, $ii, 2));
}
return $result;
}
private static function newHMACKey($hmac_name) {
$hmac_key = Filesystem::readRandomBytes(64);
return bin2hex($hmac_key);
}
private static function writeHMACKey($hmac_name, $hmac_key) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
id(new PhabricatorAuthHMACKey())
->setKeyName($hmac_name)
->setKeyValue($hmac_key)
->save();
unset($unguarded);
}
private static function readHMACKey($hmac_name) {
$table = new PhabricatorAuthHMACKey();
$conn = $table->establishConnection('r');
$row = queryfx_one(
$conn,
'SELECT keyValue FROM %T WHERE keyName = %s',
$table->getTableName(),
$hmac_name);
if (!$row) {
return null;
}
return $row['keyValue'];
}
}
diff --git a/src/infrastructure/util/password/PhabricatorPasswordHasher.php b/src/infrastructure/util/password/PhabricatorPasswordHasher.php
index f0f30045c0..fe35d2c296 100644
--- a/src/infrastructure/util/password/PhabricatorPasswordHasher.php
+++ b/src/infrastructure/util/password/PhabricatorPasswordHasher.php
@@ -1,420 +1,420 @@
<?php
/**
* Provides a mechanism for hashing passwords, like "iterated md5", "bcrypt",
* "scrypt", etc.
*
* Hashers define suitability and strength, and the system automatically
* chooses the strongest available hasher and can prompt users to upgrade as
* soon as a stronger hasher is available.
*
* @task hasher Implementing a Hasher
* @task hashing Using Hashers
*/
abstract class PhabricatorPasswordHasher extends Phobject {
const MAXIMUM_STORAGE_SIZE = 128;
/* -( Implementing a Hasher )---------------------------------------------- */
/**
* Return a human-readable description of this hasher, like "Iterated MD5".
*
* @return string Human readable hash name.
* @task hasher
*/
abstract public function getHumanReadableName();
/**
* Return a short, unique, key identifying this hasher, like "md5" or
* "bcrypt". This identifier should not be translated.
*
* @return string Short, unique hash name.
* @task hasher
*/
abstract public function getHashName();
/**
* Return the maximum byte length of hashes produced by this hasher. This is
* used to prevent storage overflows.
*
* @return int Maximum number of bytes in hashes this class produces.
* @task hasher
*/
abstract public function getHashLength();
/**
* Return `true` to indicate that any required extensions or dependencies
* are available, and this hasher is able to perform hashing.
*
* @return bool True if this hasher can execute.
* @task hasher
*/
abstract public function canHashPasswords();
/**
* Return a human-readable string describing why this hasher is unable
* to operate. For example, "To use bcrypt, upgrade to PHP 5.5.0 or newer.".
*
* @return string Human-readable description of how to enable this hasher.
* @task hasher
*/
abstract public function getInstallInstructions();
/**
* Return an indicator of this hasher's strength. When choosing to hash
* new passwords, the strongest available hasher which is usable for new
* passwords will be used, and the presence of a stronger hasher will
* prompt users to update their hashes.
*
* Generally, this method should return a larger number than hashers it is
* preferable to, but a smaller number than hashers which are better than it
* is. This number does not need to correspond directly with the actual hash
* strength.
*
* @return float Strength of this hasher.
* @task hasher
*/
abstract public function getStrength();
/**
* Return a short human-readable indicator of this hasher's strength, like
* "Weak", "Okay", or "Good".
*
* This is only used to help administrators make decisions about
* configuration.
*
* @return string Short human-readable description of hash strength.
* @task hasher
*/
abstract public function getHumanReadableStrength();
/**
* Produce a password hash.
*
* @param PhutilOpaqueEnvelope Text to be hashed.
* @return PhutilOpaqueEnvelope Hashed text.
* @task hasher
*/
abstract protected function getPasswordHash(PhutilOpaqueEnvelope $envelope);
/**
* Verify that a password matches a hash.
*
* The default implementation checks for equality; if a hasher embeds salt in
* hashes it should override this method and perform a salt-aware comparison.
*
* @param PhutilOpaqueEnvelope Password to compare.
* @param PhutilOpaqueEnvelope Bare password hash.
* @return bool True if the passwords match.
* @task hasher
*/
protected function verifyPassword(
PhutilOpaqueEnvelope $password,
PhutilOpaqueEnvelope $hash) {
$actual_hash = $this->getPasswordHash($password)->openEnvelope();
$expect_hash = $hash->openEnvelope();
return phutil_hashes_are_identical($actual_hash, $expect_hash);
}
/**
* Check if an existing hash created by this algorithm is upgradeable.
*
* The default implementation returns `false`. However, hash algorithms which
* have (for example) an internal cost function may be able to upgrade an
* existing hash to a stronger one with a higher cost.
*
* @param PhutilOpaqueEnvelope Bare hash.
* @return bool True if the hash can be upgraded without
* changing the algorithm (for example, to a
* higher cost).
* @task hasher
*/
protected function canUpgradeInternalHash(PhutilOpaqueEnvelope $hash) {
return false;
}
/* -( Using Hashers )------------------------------------------------------ */
/**
* Get the hash of a password for storage.
*
* @param PhutilOpaqueEnvelope Password text.
* @return PhutilOpaqueEnvelope Hashed text.
* @task hashing
*/
final public function getPasswordHashForStorage(
PhutilOpaqueEnvelope $envelope) {
$name = $this->getHashName();
$hash = $this->getPasswordHash($envelope);
$actual_len = strlen($hash->openEnvelope());
$expect_len = $this->getHashLength();
if ($actual_len > $expect_len) {
throw new Exception(
pht(
"Password hash '%s' produced a hash of length %d, but a ".
"maximum length of %d was expected.",
$name,
new PhutilNumber($actual_len),
new PhutilNumber($expect_len)));
}
return new PhutilOpaqueEnvelope($name.':'.$hash->openEnvelope());
}
/**
* Parse a storage hash into its components, like the hash type and hash
* data.
*
* @return map Dictionary of information about the hash.
* @task hashing
*/
private static function parseHashFromStorage(PhutilOpaqueEnvelope $hash) {
$raw_hash = $hash->openEnvelope();
if (strpos($raw_hash, ':') === false) {
throw new Exception(
pht(
'Malformed password hash, expected "name:hash".'));
}
list($name, $hash) = explode(':', $raw_hash);
return array(
'name' => $name,
'hash' => new PhutilOpaqueEnvelope($hash),
);
}
/**
* Get all available password hashers. This may include hashers which can not
* actually be used (for example, a required extension is missing).
*
* @return list<PhabricatorPasswordHasher> Hasher objects.
* @task hashing
*/
public static function getAllHashers() {
$objects = id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getHashName')
->execute();
foreach ($objects as $object) {
$name = $object->getHashName();
$potential_length = strlen($name) + $object->getHashLength() + 1;
$maximum_length = self::MAXIMUM_STORAGE_SIZE;
if ($potential_length > $maximum_length) {
throw new Exception(
pht(
'Hasher "%s" may produce hashes which are too long to fit in '.
'storage. %d characters are available, but its hashes may be '.
'up to %d characters in length.',
$name,
$maximum_length,
$potential_length));
}
}
return $objects;
}
/**
* Get all usable password hashers. This may include hashers which are
* not desirable or advisable.
*
* @return list<PhabricatorPasswordHasher> Hasher objects.
* @task hashing
*/
public static function getAllUsableHashers() {
$hashers = self::getAllHashers();
foreach ($hashers as $key => $hasher) {
if (!$hasher->canHashPasswords()) {
unset($hashers[$key]);
}
}
return $hashers;
}
/**
* Get the best (strongest) available hasher.
*
* @return PhabricatorPasswordHasher Best hasher.
* @task hashing
*/
public static function getBestHasher() {
$hashers = self::getAllUsableHashers();
$hashers = msort($hashers, 'getStrength');
$hasher = last($hashers);
if (!$hasher) {
throw new PhabricatorPasswordHasherUnavailableException(
pht(
'There are no password hashers available which are usable for '.
'new passwords.'));
}
return $hasher;
}
/**
* Get the hasher for a given stored hash.
*
* @return PhabricatorPasswordHasher Corresponding hasher.
* @task hashing
*/
public static function getHasherForHash(PhutilOpaqueEnvelope $hash) {
$info = self::parseHashFromStorage($hash);
$name = $info['name'];
$usable = self::getAllUsableHashers();
if (isset($usable[$name])) {
return $usable[$name];
}
$all = self::getAllHashers();
if (isset($all[$name])) {
throw new PhabricatorPasswordHasherUnavailableException(
pht(
'Attempting to compare a password saved with the "%s" hash. The '.
'hasher exists, but is not currently usable. %s',
$name,
$all[$name]->getInstallInstructions()));
}
throw new PhabricatorPasswordHasherUnavailableException(
pht(
'Attempting to compare a password saved with the "%s" hash. No such '.
- 'hasher is known to Phabricator.',
+ 'hasher is known.',
$name));
}
/**
* Test if a password is using an weaker hash than the strongest available
* hash. This can be used to prompt users to upgrade, or automatically upgrade
* on login.
*
* @return bool True to indicate that rehashing this password will improve
* the hash strength.
* @task hashing
*/
public static function canUpgradeHash(PhutilOpaqueEnvelope $hash) {
if (!strlen($hash->openEnvelope())) {
throw new Exception(
pht('Expected a password hash, received nothing!'));
}
$current_hasher = self::getHasherForHash($hash);
$best_hasher = self::getBestHasher();
if ($current_hasher->getHashName() != $best_hasher->getHashName()) {
// If the algorithm isn't the best one, we can upgrade.
return true;
}
$info = self::parseHashFromStorage($hash);
if ($current_hasher->canUpgradeInternalHash($info['hash'])) {
// If the algorithm provides an internal upgrade, we can also upgrade.
return true;
}
// Already on the best algorithm with the best settings.
return false;
}
/**
* Generate a new hash for a password, using the best available hasher.
*
* @param PhutilOpaqueEnvelope Password to hash.
* @return PhutilOpaqueEnvelope Hashed password, using best available
* hasher.
* @task hashing
*/
public static function generateNewPasswordHash(
PhutilOpaqueEnvelope $password) {
$hasher = self::getBestHasher();
return $hasher->getPasswordHashForStorage($password);
}
/**
* Compare a password to a stored hash.
*
* @param PhutilOpaqueEnvelope Password to compare.
* @param PhutilOpaqueEnvelope Stored password hash.
* @return bool True if the passwords match.
* @task hashing
*/
public static function comparePassword(
PhutilOpaqueEnvelope $password,
PhutilOpaqueEnvelope $hash) {
$hasher = self::getHasherForHash($hash);
$parts = self::parseHashFromStorage($hash);
return $hasher->verifyPassword($password, $parts['hash']);
}
/**
* Get the human-readable algorithm name for a given hash.
*
* @param PhutilOpaqueEnvelope Storage hash.
* @return string Human-readable algorithm name.
*/
public static function getCurrentAlgorithmName(PhutilOpaqueEnvelope $hash) {
$raw_hash = $hash->openEnvelope();
if (!strlen($raw_hash)) {
return pht('None');
}
try {
$current_hasher = self::getHasherForHash($hash);
return $current_hasher->getHumanReadableName();
} catch (Exception $ex) {
$info = self::parseHashFromStorage($hash);
$name = $info['name'];
return pht('Unknown ("%s")', $name);
}
}
/**
* Get the human-readable algorithm name for the best available hash.
*
* @return string Human-readable name for best hash.
*/
public static function getBestAlgorithmName() {
try {
$best_hasher = self::getBestHasher();
return $best_hasher->getHumanReadableName();
} catch (Exception $ex) {
return pht('Unknown');
}
}
}
diff --git a/src/view/form/control/PhabricatorRemarkupControl.php b/src/view/form/control/PhabricatorRemarkupControl.php
index dba0ef0546..67afd10630 100644
--- a/src/view/form/control/PhabricatorRemarkupControl.php
+++ b/src/view/form/control/PhabricatorRemarkupControl.php
@@ -1,359 +1,393 @@
<?php
-final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl {
+final class PhabricatorRemarkupControl
+ extends AphrontFormTextAreaControl {
- private $disableMacro = false;
private $disableFullScreen = false;
private $canPin;
private $sendOnEnter = false;
-
- public function setDisableMacros($disable) {
- $this->disableMacro = $disable;
- return $this;
- }
+ private $remarkupMetadata = array();
public function setDisableFullScreen($disable) {
$this->disableFullScreen = $disable;
return $this;
}
public function setCanPin($can_pin) {
$this->canPin = $can_pin;
return $this;
}
public function getCanPin() {
return $this->canPin;
}
public function setSendOnEnter($soe) {
$this->sendOnEnter = $soe;
return $this;
}
public function getSendOnEnter() {
return $this->sendOnEnter;
}
+ public function setRemarkupMetadata(array $value) {
+ $this->remarkupMetadata = $value;
+ return $this;
+ }
+
+ public function getRemarkupMetadata() {
+ return $this->remarkupMetadata;
+ }
+
+ public function setValue($value) {
+ if ($value instanceof RemarkupValue) {
+ $this->setRemarkupMetadata($value->getMetadata());
+ $value = $value->getCorpus();
+ }
+
+ return parent::setValue($value);
+ }
+
protected function renderInput() {
$id = $this->getID();
if (!$id) {
$id = celerity_generate_unique_node_id();
$this->setID($id);
}
$viewer = $this->getUser();
if (!$viewer) {
throw new PhutilInvalidStateException('setUser');
}
+ // NOTE: Metadata is passed to Javascript in a structured way, and also
+ // dumped directly into the form as an encoded string. This makes it less
+ // likely that we'll lose server-provided metadata (for example, from a
+ // saved draft) if there is a client-side error.
+
+ $metadata_name = $this->getName().'_metadata';
+ $metadata_value = (object)$this->getRemarkupMetadata();
+ $metadata_string = phutil_json_encode($metadata_value);
+
+ $metadata_id = celerity_generate_unique_node_id();
+ $metadata_input = phutil_tag(
+ 'input',
+ array(
+ 'type' => 'hidden',
+ 'id' => $metadata_id,
+ 'name' => $metadata_name,
+ 'value' => $metadata_string,
+ ));
+
// We need to have this if previews render images, since Ajax can not
// currently ship JS or CSS.
require_celerity_resource('phui-lightbox-css');
if (!$this->getDisabled()) {
Javelin::initBehavior(
'aphront-drag-and-drop-textarea',
array(
'target' => $id,
+ 'remarkupMetadataID' => $metadata_id,
+ 'remarkupMetadataValue' => $metadata_value,
'activatedClass' => 'aphront-textarea-drag-and-drop',
'uri' => '/file/dropupload/',
'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(),
));
}
$root_id = celerity_generate_unique_node_id();
$user_datasource = new PhabricatorPeopleDatasource();
$emoji_datasource = new PhabricatorEmojiDatasource();
$proj_datasource = id(new PhabricatorProjectDatasource())
->setParameters(
array(
'autocomplete' => 1,
));
$phriction_datasource = new PhrictionDocumentDatasource();
$phurl_datasource = new PhabricatorPhurlURLDatasource();
Javelin::initBehavior(
'phabricator-remarkup-assist',
array(
'pht' => array(
'bold text' => pht('bold text'),
'italic text' => pht('italic text'),
'monospaced text' => pht('monospaced text'),
'List Item' => pht('List Item'),
'Quoted Text' => pht('Quoted Text'),
'data' => pht('data'),
'name' => pht('name'),
'URL' => pht('URL'),
'key-help' => pht('Pin or unpin the comment form.'),
),
'canPin' => $this->getCanPin(),
'disabled' => $this->getDisabled(),
'sendOnEnter' => $this->getSendOnEnter(),
'rootID' => $root_id,
'autocompleteMap' => (object)array(
64 => array( // "@"
'datasourceURI' => $user_datasource->getDatasourceURI(),
'headerIcon' => 'fa-user',
'headerText' => pht('Find User:'),
'hintText' => $user_datasource->getPlaceholderText(),
),
35 => array( // "#"
'datasourceURI' => $proj_datasource->getDatasourceURI(),
'headerIcon' => 'fa-briefcase',
'headerText' => pht('Find Project:'),
'hintText' => $proj_datasource->getPlaceholderText(),
),
58 => array( // ":"
'datasourceURI' => $emoji_datasource->getDatasourceURI(),
'headerIcon' => 'fa-smile-o',
'headerText' => pht('Find Emoji:'),
'hintText' => $emoji_datasource->getPlaceholderText(),
// Cancel on emoticons like ":3".
'ignore' => array(
'3',
')',
'(',
'-',
'/',
),
),
91 => array( // "["
'datasourceURI' => $phriction_datasource->getDatasourceURI(),
'headerIcon' => 'fa-book',
'headerText' => pht('Find Document:'),
'hintText' => $phriction_datasource->getPlaceholderText(),
'cancel' => array(
':', // Cancel on "http:" and similar.
'|',
']',
),
'prefix' => '^\\[',
),
40 => array( // "("
'datasourceURI' => $phurl_datasource->getDatasourceURI(),
'headerIcon' => 'fa-compress',
'headerText' => pht('Find Phurl:'),
'hintText' => $phurl_datasource->getPlaceholderText(),
'cancel' => array(
')',
),
'prefix' => '^\\(',
),
),
));
Javelin::initBehavior('phabricator-tooltips', array());
$actions = array(
'fa-bold' => array(
'tip' => pht('Bold'),
'nodevice' => true,
),
'fa-italic' => array(
'tip' => pht('Italics'),
'nodevice' => true,
),
'fa-text-width' => array(
'tip' => pht('Monospaced'),
'nodevice' => true,
),
'fa-link' => array(
'tip' => pht('Link'),
'nodevice' => true,
),
array(
'spacer' => true,
'nodevice' => true,
),
'fa-list-ul' => array(
'tip' => pht('Bulleted List'),
'nodevice' => true,
),
'fa-list-ol' => array(
'tip' => pht('Numbered List'),
'nodevice' => true,
),
'fa-code' => array(
'tip' => pht('Code Block'),
'nodevice' => true,
),
'fa-quote-right' => array(
'tip' => pht('Quote'),
'nodevice' => true,
),
'fa-table' => array(
'tip' => pht('Table'),
'nodevice' => true,
),
'fa-cloud-upload' => array(
'tip' => pht('Upload File'),
),
);
- $can_use_macros =
- (!$this->disableMacro) &&
- (function_exists('imagettftext'));
+ $can_use_macros = function_exists('imagettftext');
if ($can_use_macros) {
$can_use_macros = PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorMacroApplication',
$viewer);
}
if ($can_use_macros) {
$actions[] = array(
'spacer' => true,
);
$actions['fa-meh-o'] = array(
'tip' => pht('Meme'),
);
}
$actions['fa-eye'] = array(
'tip' => pht('Preview'),
'align' => 'right',
);
$actions['fa-book'] = array(
'tip' => pht('Help'),
'align' => 'right',
'href' => PhabricatorEnv::getDoclink('Remarkup Reference'),
);
$mode_actions = array();
if (!$this->disableFullScreen) {
$mode_actions['fa-arrows-alt'] = array(
'tip' => pht('Fullscreen Mode'),
'align' => 'right',
);
}
if ($this->getCanPin()) {
$mode_actions['fa-thumb-tack'] = array(
'tip' => pht('Pin Form On Screen'),
'align' => 'right',
);
}
if ($mode_actions) {
$actions += $mode_actions;
}
$buttons = array();
foreach ($actions as $action => $spec) {
$classes = array();
if (idx($spec, 'align') == 'right') {
$classes[] = 'remarkup-assist-right';
}
if (idx($spec, 'nodevice')) {
$classes[] = 'remarkup-assist-nodevice';
}
if (idx($spec, 'spacer')) {
$classes[] = 'remarkup-assist-separator';
$buttons[] = phutil_tag(
'span',
array(
'class' => implode(' ', $classes),
),
'');
continue;
} else {
$classes[] = 'remarkup-assist-button';
}
if ($action == 'fa-cloud-upload') {
$classes[] = 'remarkup-assist-upload';
}
$href = idx($spec, 'href', '#');
if ($href == '#') {
$meta = array('action' => $action);
$mustcapture = true;
$target = null;
} else {
$meta = array();
$mustcapture = null;
$target = '_blank';
}
$content = null;
$tip = idx($spec, 'tip');
if ($tip) {
$meta['tip'] = $tip;
$content = javelin_tag(
'span',
array(
'aural' => true,
),
$tip);
}
$sigils = array();
$sigils[] = 'remarkup-assist';
if (!$this->getDisabled()) {
$sigils[] = 'has-tooltip';
}
$buttons[] = javelin_tag(
'a',
array(
'class' => implode(' ', $classes),
'href' => $href,
'sigil' => implode(' ', $sigils),
'meta' => $meta,
'mustcapture' => $mustcapture,
'target' => $target,
'tabindex' => -1,
),
phutil_tag(
'div',
array(
'class' =>
'remarkup-assist phui-icon-view phui-font-fa bluegrey '.$action,
),
$content));
}
$buttons = phutil_tag(
'div',
array(
'class' => 'remarkup-assist-bar',
),
$buttons);
$use_monospaced = $viewer->compareUserSetting(
PhabricatorMonospacedTextareasSetting::SETTINGKEY,
PhabricatorMonospacedTextareasSetting::VALUE_TEXT_MONOSPACED);
if ($use_monospaced) {
$monospaced_textareas_class = 'PhabricatorMonospaced';
} else {
$monospaced_textareas_class = null;
}
$this->setCustomClass(
'remarkup-assist-textarea '.$monospaced_textareas_class);
return javelin_tag(
'div',
array(
'sigil' => 'remarkup-assist-control',
'class' => $this->getDisabled() ? 'disabled-control' : null,
'id' => $root_id,
),
array(
$buttons,
parent::renderInput(),
+ $metadata_input,
));
}
}
diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php
index ecb12bb81d..6f6ce56cad 100644
--- a/src/view/page/PhabricatorStandardPageView.php
+++ b/src/view/page/PhabricatorStandardPageView.php
@@ -1,914 +1,914 @@
<?php
/**
* This is a standard Phabricator page with menus, Javelin, DarkConsole, and
* basic styles.
*/
final class PhabricatorStandardPageView extends PhabricatorBarePageView
implements AphrontResponseProducerInterface {
private $baseURI;
private $applicationName;
private $glyph;
private $menuContent;
private $showChrome = true;
private $classes = array();
private $disableConsole;
private $pageObjects = array();
private $applicationMenu;
private $showFooter = true;
private $showDurableColumn = true;
private $quicksandConfig = array();
private $tabs;
private $crumbs;
private $navigation;
private $footer;
public function setShowFooter($show_footer) {
$this->showFooter = $show_footer;
return $this;
}
public function getShowFooter() {
return $this->showFooter;
}
public function setApplicationName($application_name) {
$this->applicationName = $application_name;
return $this;
}
public function setDisableConsole($disable) {
$this->disableConsole = $disable;
return $this;
}
public function getApplicationName() {
return $this->applicationName;
}
public function setBaseURI($base_uri) {
$this->baseURI = $base_uri;
return $this;
}
public function getBaseURI() {
return $this->baseURI;
}
public function setShowChrome($show_chrome) {
$this->showChrome = $show_chrome;
return $this;
}
public function getShowChrome() {
return $this->showChrome;
}
public function addClass($class) {
$this->classes[] = $class;
return $this;
}
public function setPageObjectPHIDs(array $phids) {
$this->pageObjects = $phids;
return $this;
}
public function setShowDurableColumn($show) {
$this->showDurableColumn = $show;
return $this;
}
public function getShowDurableColumn() {
$request = $this->getRequest();
if (!$request) {
return false;
}
$viewer = $request->getUser();
if (!$viewer->isLoggedIn()) {
return false;
}
$conpherence_installed = PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorConpherenceApplication',
$viewer);
if (!$conpherence_installed) {
return false;
}
if ($this->isQuicksandBlacklistURI()) {
return false;
}
return true;
}
private function isQuicksandBlacklistURI() {
$request = $this->getRequest();
if (!$request) {
return false;
}
$patterns = $this->getQuicksandURIPatternBlacklist();
$path = $request->getRequestURI()->getPath();
foreach ($patterns as $pattern) {
if (preg_match('(^'.$pattern.'$)', $path)) {
return true;
}
}
return false;
}
public function getDurableColumnVisible() {
$column_key = PhabricatorConpherenceColumnVisibleSetting::SETTINGKEY;
return (bool)$this->getUserPreference($column_key, false);
}
public function getDurableColumnMinimize() {
$column_key = PhabricatorConpherenceColumnMinimizeSetting::SETTINGKEY;
return (bool)$this->getUserPreference($column_key, false);
}
public function addQuicksandConfig(array $config) {
$this->quicksandConfig = $config + $this->quicksandConfig;
return $this;
}
public function getQuicksandConfig() {
return $this->quicksandConfig;
}
public function setCrumbs(PHUICrumbsView $crumbs) {
$this->crumbs = $crumbs;
return $this;
}
public function getCrumbs() {
return $this->crumbs;
}
public function setTabs(PHUIListView $tabs) {
$tabs->setType(PHUIListView::TABBAR_LIST);
$tabs->addClass('phabricator-standard-page-tabs');
$this->tabs = $tabs;
return $this;
}
public function getTabs() {
return $this->tabs;
}
public function setNavigation(AphrontSideNavFilterView $navigation) {
$this->navigation = $navigation;
return $this;
}
public function getNavigation() {
return $this->navigation;
}
public function getTitle() {
$glyph_key = PhabricatorTitleGlyphsSetting::SETTINGKEY;
$glyph_on = PhabricatorTitleGlyphsSetting::VALUE_TITLE_GLYPHS;
$glyph_setting = $this->getUserPreference($glyph_key, $glyph_on);
$use_glyph = ($glyph_setting == $glyph_on);
$title = parent::getTitle();
$prefix = null;
if ($use_glyph) {
$prefix = $this->getGlyph();
} else {
$application_name = $this->getApplicationName();
if (strlen($application_name)) {
$prefix = '['.$application_name.']';
}
}
if (strlen($prefix)) {
$title = $prefix.' '.$title;
}
return $title;
}
protected function willRenderPage() {
$footer = $this->renderFooter();
// NOTE: A cleaner solution would be to let body layout elements implement
// some kind of "LayoutInterface" so content can be embedded inside frames,
// but there's only really one use case for this for now.
$children = $this->renderChildren();
if ($children) {
$layout = head($children);
if ($layout instanceof PHUIFormationView) {
$layout->setFooter($footer);
$footer = null;
}
}
$this->footer = $footer;
parent::willRenderPage();
if (!$this->getRequest()) {
throw new Exception(
pht(
'You must set the %s to render a %s.',
'Request',
__CLASS__));
}
$console = $this->getConsole();
require_celerity_resource('phabricator-core-css');
require_celerity_resource('phabricator-zindex-css');
require_celerity_resource('phui-button-css');
require_celerity_resource('phui-spacing-css');
require_celerity_resource('phui-form-css');
require_celerity_resource('phabricator-standard-page-view');
require_celerity_resource('conpherence-durable-column-view');
require_celerity_resource('font-lato');
Javelin::initBehavior('workflow', array());
$request = $this->getRequest();
$user = null;
if ($request) {
$user = $request->getUser();
}
if ($user) {
if ($user->isUserActivated()) {
$offset = $user->getTimeZoneOffset();
$ignore_key = PhabricatorTimezoneIgnoreOffsetSetting::SETTINGKEY;
$ignore = $user->getUserSetting($ignore_key);
Javelin::initBehavior(
'detect-timezone',
array(
'offset' => $offset,
'uri' => '/settings/timezone/',
'message' => pht(
'Your browser timezone setting differs from the timezone '.
'setting in your profile, click to reconcile.'),
'ignoreKey' => $ignore_key,
'ignore' => $ignore,
));
if ($user->getIsAdmin()) {
$server_https = $request->isHTTPS();
$server_protocol = $server_https ? 'HTTPS' : 'HTTP';
$client_protocol = $server_https ? 'HTTP' : 'HTTPS';
$doc_name = 'Configuring a Preamble Script';
$doc_href = PhabricatorEnv::getDoclink($doc_name);
Javelin::initBehavior(
'setup-check-https',
array(
'server_https' => $server_https,
'doc_name' => pht('See Documentation'),
'doc_href' => $doc_href,
'message' => pht(
- 'Phabricator thinks you are using %s, but your '.
+ 'This server thinks you are using %s, but your '.
'client is convinced that it is using %s. This is a serious '.
'misconfiguration with subtle, but significant, consequences.',
$server_protocol, $client_protocol),
));
}
}
Javelin::initBehavior('lightbox-attachments');
}
Javelin::initBehavior('aphront-form-disable-on-submit');
Javelin::initBehavior('toggle-class', array());
Javelin::initBehavior('history-install');
Javelin::initBehavior('phabricator-gesture');
$current_token = null;
if ($user) {
$current_token = $user->getCSRFToken();
}
Javelin::initBehavior(
'refresh-csrf',
array(
'tokenName' => AphrontRequest::getCSRFTokenName(),
'header' => AphrontRequest::getCSRFHeaderName(),
'viaHeader' => AphrontRequest::getViaHeaderName(),
'current' => $current_token,
));
Javelin::initBehavior('device');
Javelin::initBehavior(
'high-security-warning',
$this->getHighSecurityWarningConfig());
if (PhabricatorEnv::isReadOnly()) {
Javelin::initBehavior(
'read-only-warning',
array(
'message' => PhabricatorEnv::getReadOnlyMessage(),
'uri' => PhabricatorEnv::getReadOnlyURI(),
));
}
// If we aren't showing the page chrome, skip rendering DarkConsole and the
// main menu, since they won't be visible on the page.
if (!$this->getShowChrome()) {
return;
}
if ($console) {
require_celerity_resource('aphront-dark-console-css');
$headers = array();
if (DarkConsoleXHProfPluginAPI::isProfilerStarted()) {
$headers[DarkConsoleXHProfPluginAPI::getProfilerHeader()] = 'page';
}
if (DarkConsoleServicesPlugin::isQueryAnalyzerRequested()) {
$headers[DarkConsoleServicesPlugin::getQueryAnalyzerHeader()] = true;
}
Javelin::initBehavior(
'dark-console',
$this->getConsoleConfig());
}
if ($user) {
$viewer = $user;
} else {
$viewer = new PhabricatorUser();
}
$menu = id(new PhabricatorMainMenuView())
->setUser($viewer);
if ($this->getController()) {
$menu->setController($this->getController());
}
$application_menu = $this->applicationMenu;
if ($application_menu) {
if ($application_menu instanceof PHUIApplicationMenuView) {
$crumbs = $this->getCrumbs();
if ($crumbs) {
$application_menu->setCrumbs($crumbs);
}
$application_menu = $application_menu->buildListView();
}
$menu->setApplicationMenu($application_menu);
}
$this->menuContent = $menu->render();
}
protected function getHead() {
$monospaced = null;
$request = $this->getRequest();
if ($request) {
$user = $request->getUser();
if ($user) {
$monospaced = $user->getUserSetting(
PhabricatorMonospacedFontSetting::SETTINGKEY);
}
}
$response = CelerityAPI::getStaticResourceResponse();
$font_css = null;
if (!empty($monospaced)) {
// We can't print this normally because escaping quotation marks will
// break the CSS. Instead, filter it strictly and then mark it as safe.
$monospaced = new PhutilSafeHTML(
PhabricatorMonospacedFontSetting::filterMonospacedCSSRule(
$monospaced));
$font_css = hsprintf(
'<style type="text/css">'.
'.PhabricatorMonospaced, '.
'.phabricator-remarkup .remarkup-code-block '.
'.remarkup-code { font: %s !important; } '.
'</style>',
$monospaced);
}
return hsprintf(
'%s%s%s',
parent::getHead(),
$font_css,
$response->renderSingleResource('javelin-magical-init', 'phabricator'));
}
public function setGlyph($glyph) {
$this->glyph = $glyph;
return $this;
}
public function getGlyph() {
return $this->glyph;
}
protected function willSendResponse($response) {
$request = $this->getRequest();
$response = parent::willSendResponse($response);
$console = $request->getApplicationConfiguration()->getConsole();
if ($console) {
$response = PhutilSafeHTML::applyFunction(
'str_replace',
hsprintf('<darkconsole />'),
$console->render($request),
$response);
}
return $response;
}
protected function getBody() {
$user = null;
$request = $this->getRequest();
if ($request) {
$user = $request->getUser();
}
$header_chrome = null;
if ($this->getShowChrome()) {
$header_chrome = $this->menuContent;
}
$classes = array();
$classes[] = 'main-page-frame';
$developer_warning = null;
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode') &&
DarkConsoleErrorLogPluginAPI::getErrors()) {
$developer_warning = phutil_tag_div(
'aphront-developer-error-callout',
pht(
'This page raised PHP errors. Find them in DarkConsole '.
'or the error log.'));
}
$main_page = phutil_tag(
'div',
array(
'id' => 'phabricator-standard-page',
'class' => 'phabricator-standard-page',
),
array(
$developer_warning,
$header_chrome,
phutil_tag(
'div',
array(
'id' => 'phabricator-standard-page-body',
'class' => 'phabricator-standard-page-body',
),
$this->renderPageBodyContent()),
));
$durable_column = null;
if ($this->getShowDurableColumn()) {
$is_visible = $this->getDurableColumnVisible();
$is_minimize = $this->getDurableColumnMinimize();
$durable_column = id(new ConpherenceDurableColumnView())
->setSelectedConpherence(null)
->setUser($user)
->setQuicksandConfig($this->buildQuicksandConfig())
->setVisible($is_visible)
->setMinimize($is_minimize)
->setInitialLoad(true);
if ($is_minimize) {
$this->classes[] = 'minimize-column';
}
}
Javelin::initBehavior('quicksand-blacklist', array(
'patterns' => $this->getQuicksandURIPatternBlacklist(),
));
return phutil_tag(
'div',
array(
'class' => implode(' ', $classes),
'id' => 'main-page-frame',
),
array(
$main_page,
$durable_column,
));
}
private function renderPageBodyContent() {
$console = $this->getConsole();
$body = parent::getBody();
$nav = $this->getNavigation();
$tabs = $this->getTabs();
if ($nav) {
$crumbs = $this->getCrumbs();
if ($crumbs) {
$nav->setCrumbs($crumbs);
}
$nav->appendChild($body);
$nav->appendFooter($this->footer);
$content = phutil_implode_html('', array($nav->render()));
} else {
$content = array();
$crumbs = $this->getCrumbs();
if ($crumbs) {
if ($this->getTabs()) {
$crumbs->setBorder(true);
}
$content[] = $crumbs;
}
$tabs = $this->getTabs();
if ($tabs) {
$content[] = $tabs;
}
$content[] = $body;
$content[] = $this->footer;
$content = phutil_implode_html('', $content);
}
return array(
($console ? hsprintf('<darkconsole />') : null),
$content,
);
}
protected function getTail() {
$request = $this->getRequest();
$user = $request->getUser();
$tail = array(
parent::getTail(),
);
$response = CelerityAPI::getStaticResourceResponse();
if ($request->isHTTPS()) {
$with_protocol = 'https';
} else {
$with_protocol = 'http';
}
$servers = PhabricatorNotificationServerRef::getEnabledClientServers(
$with_protocol);
if ($servers) {
if ($user && $user->isLoggedIn()) {
// TODO: We could tell the browser about all the servers and let it
// do random reconnects to improve reliability.
shuffle($servers);
$server = head($servers);
$client_uri = $server->getWebsocketURI();
Javelin::initBehavior(
'aphlict-listen',
array(
'websocketURI' => (string)$client_uri,
) + $this->buildAphlictListenConfigData());
CelerityAPI::getStaticResourceResponse()
->addContentSecurityPolicyURI('connect-src', $client_uri);
}
}
$tail[] = $response->renderHTMLFooter($this->getFrameable());
return $tail;
}
protected function getBodyClasses() {
$classes = array();
if (!$this->getShowChrome()) {
$classes[] = 'phabricator-chromeless-page';
}
$agent = AphrontRequest::getHTTPHeader('User-Agent');
// Try to guess the device resolution based on UA strings to avoid a flash
// of incorrectly-styled content.
$device_guess = 'device-desktop';
if (preg_match('@iPhone|iPod|(Android.*Chrome/[.0-9]* Mobile)@', $agent)) {
$device_guess = 'device-phone device';
} else if (preg_match('@iPad|(Android.*Chrome/)@', $agent)) {
$device_guess = 'device-tablet device';
}
$classes[] = $device_guess;
if (preg_match('@Windows@', $agent)) {
$classes[] = 'platform-windows';
} else if (preg_match('@Macintosh@', $agent)) {
$classes[] = 'platform-mac';
} else if (preg_match('@X11@', $agent)) {
$classes[] = 'platform-linux';
}
if ($this->getRequest()->getStr('__print__')) {
$classes[] = 'printable';
}
if ($this->getRequest()->getStr('__aural__')) {
$classes[] = 'audible';
}
$classes[] = 'phui-theme-'.PhabricatorEnv::getEnvConfig('ui.header-color');
foreach ($this->classes as $class) {
$classes[] = $class;
}
return implode(' ', $classes);
}
private function getConsole() {
if ($this->disableConsole) {
return null;
}
return $this->getRequest()->getApplicationConfiguration()->getConsole();
}
private function getConsoleConfig() {
$user = $this->getRequest()->getUser();
$headers = array();
if (DarkConsoleXHProfPluginAPI::isProfilerStarted()) {
$headers[DarkConsoleXHProfPluginAPI::getProfilerHeader()] = 'page';
}
if (DarkConsoleServicesPlugin::isQueryAnalyzerRequested()) {
$headers[DarkConsoleServicesPlugin::getQueryAnalyzerHeader()] = true;
}
if ($user) {
$setting_tab = PhabricatorDarkConsoleTabSetting::SETTINGKEY;
$setting_visible = PhabricatorDarkConsoleVisibleSetting::SETTINGKEY;
$tab = $user->getUserSetting($setting_tab);
$visible = $user->getUserSetting($setting_visible);
} else {
$tab = null;
$visible = true;
}
return array(
// NOTE: We use a generic label here to prevent input reflection
// and mitigate compression attacks like BREACH. See discussion in
// T3684.
'uri' => pht('Main Request'),
'selected' => $tab,
'visible' => $visible,
'headers' => $headers,
);
}
private function getHighSecurityWarningConfig() {
$user = $this->getRequest()->getUser();
$show = false;
if ($user->hasSession()) {
$hisec = ($user->getSession()->getHighSecurityUntil() - time());
if ($hisec > 0) {
$show = true;
}
}
return array(
'show' => $show,
'uri' => '/auth/session/downgrade/',
'message' => pht(
'Your session is in high security mode. When you '.
'finish using it, click here to leave.'),
);
}
private function renderFooter() {
if (!$this->getShowChrome()) {
return null;
}
if (!$this->getShowFooter()) {
return null;
}
$items = PhabricatorEnv::getEnvConfig('ui.footer-items');
if (!$items) {
return null;
}
$foot = array();
foreach ($items as $item) {
$name = idx($item, 'name', pht('Unnamed Footer Item'));
$href = idx($item, 'href');
if (!PhabricatorEnv::isValidURIForLink($href)) {
$href = null;
}
if ($href !== null) {
$tag = 'a';
} else {
$tag = 'span';
}
$foot[] = phutil_tag(
$tag,
array(
'href' => $href,
),
$name);
}
$foot = phutil_implode_html(" \xC2\xB7 ", $foot);
return phutil_tag(
'div',
array(
'class' => 'phabricator-standard-page-footer grouped',
),
$foot);
}
public function renderForQuicksand() {
parent::willRenderPage();
$response = $this->renderPageBodyContent();
$response = $this->willSendResponse($response);
$extra_config = $this->getQuicksandConfig();
return array(
'content' => hsprintf('%s', $response),
) + $this->buildQuicksandConfig()
+ $extra_config;
}
private function buildQuicksandConfig() {
$viewer = $this->getRequest()->getUser();
$controller = $this->getController();
$dropdown_query = id(new AphlictDropdownDataQuery())
->setViewer($viewer);
$dropdown_query->execute();
$hisec_warning_config = $this->getHighSecurityWarningConfig();
$console_config = null;
$console = $this->getConsole();
if ($console) {
$console_config = $this->getConsoleConfig();
}
$upload_enabled = false;
if ($controller) {
$upload_enabled = $controller->isGlobalDragAndDropUploadEnabled();
}
$application_class = null;
$application_search_icon = null;
$application_help = null;
$controller = $this->getController();
if ($controller) {
$application = $controller->getCurrentApplication();
if ($application) {
$application_class = get_class($application);
if ($application->getApplicationSearchDocumentTypes()) {
$application_search_icon = $application->getIcon();
}
$help_items = $application->getHelpMenuItems($viewer);
if ($help_items) {
$help_list = id(new PhabricatorActionListView())
->setViewer($viewer);
foreach ($help_items as $help_item) {
$help_list->addAction($help_item);
}
$application_help = $help_list->getDropdownMenuMetadata();
}
}
}
return array(
'title' => $this->getTitle(),
'bodyClasses' => $this->getBodyClasses(),
'aphlictDropdownData' => array(
$dropdown_query->getNotificationData(),
$dropdown_query->getConpherenceData(),
),
'globalDragAndDrop' => $upload_enabled,
'hisecWarningConfig' => $hisec_warning_config,
'consoleConfig' => $console_config,
'applicationClass' => $application_class,
'applicationSearchIcon' => $application_search_icon,
'helpItems' => $application_help,
) + $this->buildAphlictListenConfigData();
}
private function buildAphlictListenConfigData() {
$user = $this->getRequest()->getUser();
$subscriptions = $this->pageObjects;
$subscriptions[] = $user->getPHID();
return array(
'pageObjects' => array_fill_keys($this->pageObjects, true),
'subscriptions' => $subscriptions,
);
}
private function getQuicksandURIPatternBlacklist() {
$applications = PhabricatorApplication::getAllApplications();
$blacklist = array();
foreach ($applications as $application) {
$blacklist[] = $application->getQuicksandURIPatternBlacklist();
}
// See T4340. Currently, Phortune and Auth both require pulling in external
// Javascript (for Stripe card management and Recaptcha, respectively).
// This can put us in a position where the user loads a page with a
// restrictive Content-Security-Policy, then uses Quicksand to navigate to
// a page which needs to load external scripts. For now, just blacklist
// these entire applications since we aren't giving up anything
// significant by doing so.
$blacklist[] = array(
'/phortune/.*',
'/auth/.*',
);
return array_mergev($blacklist);
}
private function getUserPreference($key, $default = null) {
$request = $this->getRequest();
if (!$request) {
return $default;
}
$user = $request->getUser();
if (!$user) {
return $default;
}
return $user->getUserSetting($key);
}
public function produceAphrontResponse() {
$controller = $this->getController();
$viewer = $this->getUser();
if ($viewer && $viewer->getPHID()) {
$object_phids = $this->pageObjects;
foreach ($object_phids as $object_phid) {
PhabricatorFeedStoryNotification::updateObjectNotificationViews(
$viewer,
$object_phid);
}
}
if ($this->getRequest()->isQuicksand()) {
$content = $this->renderForQuicksand();
$response = id(new AphrontAjaxResponse())
->setContent($content);
} else {
// See T13247. Try to find some navigational menu items to create a
// mobile navigation menu from.
$application_menu = $controller->buildApplicationMenu();
if (!$application_menu) {
$navigation = $this->getNavigation();
if ($navigation) {
$application_menu = $navigation->getMenu();
}
}
$this->applicationMenu = $application_menu;
$content = $this->render();
$response = id(new AphrontWebpageResponse())
->setContent($content)
->setFrameable($this->getFrameable());
}
return $response;
}
}
diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php
index 67badce911..29a259b44c 100644
--- a/src/view/page/menu/PhabricatorMainMenuView.php
+++ b/src/view/page/menu/PhabricatorMainMenuView.php
@@ -1,736 +1,736 @@
<?php
final class PhabricatorMainMenuView extends AphrontView {
private $controller;
private $applicationMenu;
public function setApplicationMenu(PHUIListView $application_menu) {
$this->applicationMenu = $application_menu;
return $this;
}
public function getApplicationMenu() {
return $this->applicationMenu;
}
public function setController(PhabricatorController $controller) {
$this->controller = $controller;
return $this;
}
public function getController() {
return $this->controller;
}
private static function getFavicons() {
$refs = array();
$refs['favicon'] = id(new PhabricatorFaviconRef())
->setWidth(64)
->setHeight(64);
$refs['message_favicon'] = id(new PhabricatorFaviconRef())
->setWidth(64)
->setHeight(64)
->setEmblems(
array(
'dot-pink',
null,
null,
null,
));
id(new PhabricatorFaviconRefQuery())
->withRefs($refs)
->execute();
return mpull($refs, 'getURI');
}
public function render() {
$viewer = $this->getViewer();
require_celerity_resource('phabricator-main-menu-view');
$header_id = celerity_generate_unique_node_id();
$menu_bar = array();
$alerts = array();
$search_button = '';
$app_button = '';
$aural = null;
$is_full = $this->isFullSession($viewer);
if ($is_full) {
list($menu, $dropdowns, $aural) = $this->renderNotificationMenu();
if (array_filter($menu)) {
$alerts[] = $menu;
}
$menu_bar = array_merge($menu_bar, $dropdowns);
$app_button = $this->renderApplicationMenuButton();
$search_button = $this->renderSearchMenuButton($header_id);
} else if (!$viewer->isLoggedIn()) {
$app_button = $this->renderApplicationMenuButton();
if (PhabricatorEnv::getEnvConfig('policy.allow-public')) {
$search_button = $this->renderSearchMenuButton($header_id);
}
}
if ($search_button) {
$search_menu = $this->renderPhabricatorSearchMenu();
} else {
$search_menu = null;
}
if ($alerts) {
$alerts = javelin_tag(
'div',
array(
'class' => 'phabricator-main-menu-alerts',
'aural' => false,
),
$alerts);
}
if ($aural) {
$aural = javelin_tag(
'span',
array(
'aural' => true,
),
phutil_implode_html(' ', $aural));
}
$extensions = PhabricatorMainMenuBarExtension::getAllEnabledExtensions();
foreach ($extensions as $extension) {
$extension
->setViewer($viewer)
->setIsFullSession($is_full);
$controller = $this->getController();
if ($controller) {
$extension->setController($controller);
$application = $controller->getCurrentApplication();
if ($application) {
$extension->setApplication($application);
}
}
}
if (!$is_full) {
foreach ($extensions as $key => $extension) {
if ($extension->shouldRequireFullSession()) {
unset($extensions[$key]);
}
}
}
foreach ($extensions as $key => $extension) {
if (!$extension->isExtensionEnabledForViewer($extension->getViewer())) {
unset($extensions[$key]);
}
}
$menus = array();
foreach ($extensions as $extension) {
foreach ($extension->buildMainMenus() as $menu) {
$menus[] = $menu;
}
}
// Because we display these with "float: right", reverse their order before
// rendering them into the document so that the extension order and display
// order are the same.
$menus = array_reverse($menus);
foreach ($menus as $menu) {
$menu_bar[] = $menu;
}
$classes = array();
$classes[] = 'phabricator-main-menu';
$classes[] = 'phabricator-main-menu-background';
return phutil_tag(
'div',
array(
'class' => implode(' ', $classes),
'id' => $header_id,
),
array(
$app_button,
$search_button,
$this->renderPhabricatorLogo(),
$alerts,
$aural,
$search_menu,
$menu_bar,
));
}
private function renderSearch() {
$viewer = $this->getViewer();
$result = null;
$keyboard_config = array(
'helpURI' => '/help/keyboardshortcut/',
);
if ($viewer->isLoggedIn()) {
$show_search = $viewer->isUserActivated();
} else {
$show_search = PhabricatorEnv::getEnvConfig('policy.allow-public');
}
if ($show_search) {
$search = new PhabricatorMainMenuSearchView();
$search->setViewer($viewer);
$application = null;
$controller = $this->getController();
if ($controller) {
$application = $controller->getCurrentApplication();
}
if ($application) {
$search->setApplication($application);
}
$result = $search;
$keyboard_config['searchID'] = $search->getID();
}
$keyboard_config['pht'] = array(
'/' => pht('Give keyboard focus to the search box.'),
'?' => pht('Show keyboard shortcut help for the current page.'),
);
Javelin::initBehavior(
'phabricator-keyboard-shortcuts',
$keyboard_config);
if ($result) {
$result = id(new PHUIListItemView())
->addClass('phabricator-main-menu-search')
->appendChild($result);
}
return $result;
}
public function renderApplicationMenuButton() {
$dropdown = $this->renderApplicationMenu();
if (!$dropdown) {
return null;
}
return id(new PHUIButtonView())
->setTag('a')
->setHref('#')
->setIcon('fa-bars')
->addClass('phabricator-core-user-menu')
->addClass('phabricator-core-user-mobile-menu')
->setNoCSS(true)
->setDropdownMenu($dropdown)
->setAuralLabel(pht('Page Menu'));
}
private function renderApplicationMenu() {
$viewer = $this->getViewer();
$view = $this->getApplicationMenu();
if ($view) {
$items = $view->getItems();
$view = id(new PhabricatorActionListView())
->setViewer($viewer);
foreach ($items as $item) {
$view->addAction(
id(new PhabricatorActionView())
->setName($item->getName())
->setHref($item->getHref())
->setType($item->getType()));
}
}
return $view;
}
public function renderSearchMenuButton($header_id) {
$button_id = celerity_generate_unique_node_id();
return javelin_tag(
'a',
array(
'class' => 'phabricator-main-menu-search-button '.
'phabricator-expand-application-menu',
'sigil' => 'jx-toggle-class',
'meta' => array(
'map' => array(
$header_id => 'phabricator-search-menu-expanded',
$button_id => 'menu-icon-selected',
),
),
),
phutil_tag(
'span',
array(
'class' => 'phabricator-menu-button-icon phui-icon-view '.
'phui-font-fa fa-search',
'id' => $button_id,
),
''));
}
private function renderPhabricatorSearchMenu() {
$view = new PHUIListView();
$view->addClass('phabricator-search-menu');
$search = $this->renderSearch();
if ($search) {
$view->addMenuItem($search);
}
return $view;
}
private function renderPhabricatorLogo() {
$custom_header = PhabricatorCustomLogoConfigType::getLogoImagePHID();
$logo_style = array();
if ($custom_header) {
$cache = PhabricatorCaches::getImmutableCache();
$cache_key_logo = 'ui.custom-header.logo-phid.v3.'.$custom_header;
$logo_uri = $cache->getKey($cache_key_logo);
if (!$logo_uri) {
// NOTE: If the file policy has been changed to be restrictive, we'll
// miss here and just show the default logo. The cache will fill later
// when someone who can see the file loads the page. This might be a
// little spooky, see T11982.
$files = id(new PhabricatorFileQuery())
->setViewer($this->getViewer())
->withPHIDs(array($custom_header))
->execute();
$file = head($files);
if ($file) {
$logo_uri = $file->getViewURI();
$cache->setKey($cache_key_logo, $logo_uri);
}
}
if ($logo_uri) {
$logo_style[] = 'background-size: 40px 40px;';
$logo_style[] = 'background-position: 0 0;';
$logo_style[] = 'background-image: url('.$logo_uri.')';
}
}
$logo_node = phutil_tag(
'span',
array(
'class' => 'phabricator-main-menu-eye',
'style' => implode(' ', $logo_style),
));
$wordmark_text = PhabricatorCustomLogoConfigType::getLogoWordmark();
if (!strlen($wordmark_text)) {
- $wordmark_text = pht('Phabricator');
+ $wordmark_text = PlatformSymbols::getPlatformServerName();
}
$wordmark_node = phutil_tag(
'span',
array(
'class' => 'phabricator-wordmark',
),
$wordmark_text);
return phutil_tag(
'a',
array(
'class' => 'phabricator-main-menu-brand',
'href' => '/',
),
array(
javelin_tag(
'span',
array(
'aural' => true,
),
pht('Home')),
$logo_node,
$wordmark_node,
));
}
private function renderNotificationMenu() {
$viewer = $this->getViewer();
require_celerity_resource('phabricator-notification-css');
require_celerity_resource('phabricator-notification-menu-css');
$container_classes = array('alert-notifications');
$aural = array();
$dropdown_query = id(new AphlictDropdownDataQuery())
->setViewer($viewer);
$dropdown_data = $dropdown_query->execute();
$message_tag = '';
$message_notification_dropdown = '';
$conpherence_app = 'PhabricatorConpherenceApplication';
$conpherence_data = $dropdown_data[$conpherence_app];
if ($conpherence_data['isInstalled']) {
$message_id = celerity_generate_unique_node_id();
$message_count_id = celerity_generate_unique_node_id();
$message_dropdown_id = celerity_generate_unique_node_id();
$message_count_number = $conpherence_data['rawCount'];
if ($message_count_number) {
$aural[] = phutil_tag(
'a',
array(
'href' => '/conpherence/',
),
pht(
'%s unread messages.',
new PhutilNumber($message_count_number)));
} else {
$aural[] = pht('No messages.');
}
$message_count_tag = phutil_tag(
'span',
array(
'id' => $message_count_id,
'class' => 'phabricator-main-menu-message-count',
),
$conpherence_data['count']);
$message_icon_tag = javelin_tag(
'span',
array(
'class' => 'phabricator-main-menu-message-icon phui-icon-view '.
'phui-font-fa fa-comments',
'sigil' => 'menu-icon',
),
'');
if ($message_count_number) {
$container_classes[] = 'message-unread';
}
$message_tag = phutil_tag(
'a',
array(
'href' => '/conpherence/',
'class' => implode(' ', $container_classes),
'id' => $message_id,
),
array(
$message_icon_tag,
$message_count_tag,
));
Javelin::initBehavior(
'aphlict-dropdown',
array(
'bubbleID' => $message_id,
'countID' => $message_count_id,
'dropdownID' => $message_dropdown_id,
'loadingText' => pht('Loading...'),
'uri' => '/conpherence/panel/',
'countType' => $conpherence_data['countType'],
'countNumber' => $message_count_number,
'unreadClass' => 'message-unread',
) + self::getFavicons());
$message_notification_dropdown = javelin_tag(
'div',
array(
'id' => $message_dropdown_id,
'class' => 'phabricator-notification-menu',
'sigil' => 'phabricator-notification-menu',
'style' => 'display: none;',
),
'');
}
$bubble_tag = '';
$notification_dropdown = '';
$notification_app = 'PhabricatorNotificationsApplication';
$notification_data = $dropdown_data[$notification_app];
if ($notification_data['isInstalled']) {
$count_id = celerity_generate_unique_node_id();
$dropdown_id = celerity_generate_unique_node_id();
$bubble_id = celerity_generate_unique_node_id();
$count_number = $notification_data['rawCount'];
if ($count_number) {
$aural[] = phutil_tag(
'a',
array(
'href' => '/notification/',
),
pht(
'%s unread notifications.',
new PhutilNumber($count_number)));
} else {
$aural[] = pht('No notifications.');
}
$count_tag = phutil_tag(
'span',
array(
'id' => $count_id,
'class' => 'phabricator-main-menu-alert-count',
),
$notification_data['count']);
$icon_tag = javelin_tag(
'span',
array(
'class' => 'phabricator-main-menu-alert-icon phui-icon-view '.
'phui-font-fa fa-bell',
'sigil' => 'menu-icon',
),
'');
if ($count_number) {
$container_classes[] = 'alert-unread';
}
$bubble_tag = phutil_tag(
'a',
array(
'href' => '/notification/',
'class' => implode(' ', $container_classes),
'id' => $bubble_id,
),
array($icon_tag, $count_tag));
Javelin::initBehavior(
'aphlict-dropdown',
array(
'bubbleID' => $bubble_id,
'countID' => $count_id,
'dropdownID' => $dropdown_id,
'loadingText' => pht('Loading...'),
'uri' => '/notification/panel/',
'countType' => $notification_data['countType'],
'countNumber' => $count_number,
'unreadClass' => 'alert-unread',
) + self::getFavicons());
$notification_dropdown = javelin_tag(
'div',
array(
'id' => $dropdown_id,
'class' => 'phabricator-notification-menu',
'sigil' => 'phabricator-notification-menu',
'style' => 'display: none;',
),
'');
}
// Admin Level Urgent Notification Channel
$setup_tag = '';
$setup_notification_dropdown = '';
if ($viewer && $viewer->getIsAdmin()) {
$open = PhabricatorSetupCheck::getOpenSetupIssueKeys();
if ($open) {
$setup_id = celerity_generate_unique_node_id();
$setup_count_id = celerity_generate_unique_node_id();
$setup_dropdown_id = celerity_generate_unique_node_id();
$setup_count_number = count($open);
if ($setup_count_number) {
$aural[] = phutil_tag(
'a',
array(
'href' => '/config/issue/',
),
pht(
'%s unresolved issues.',
new PhutilNumber($setup_count_number)));
} else {
$aural[] = pht('No issues.');
}
$setup_count_tag = phutil_tag(
'span',
array(
'id' => $setup_count_id,
'class' => 'phabricator-main-menu-setup-count',
),
$setup_count_number);
$setup_icon_tag = javelin_tag(
'span',
array(
'class' => 'phabricator-main-menu-setup-icon phui-icon-view '.
'phui-font-fa fa-exclamation-circle',
'sigil' => 'menu-icon',
),
'');
if ($setup_count_number) {
$container_classes[] = 'setup-unread';
}
$setup_tag = phutil_tag(
'a',
array(
'href' => '/config/issue/',
'class' => implode(' ', $container_classes),
'id' => $setup_id,
),
array(
$setup_icon_tag,
$setup_count_tag,
));
Javelin::initBehavior(
'aphlict-dropdown',
array(
'bubbleID' => $setup_id,
'countID' => $setup_count_id,
'dropdownID' => $setup_dropdown_id,
'loadingText' => pht('Loading...'),
'uri' => '/config/issue/panel/',
'countType' => null,
'countNumber' => null,
'unreadClass' => 'setup-unread',
) + self::getFavicons());
$setup_notification_dropdown = javelin_tag(
'div',
array(
'id' => $setup_dropdown_id,
'class' => 'phabricator-notification-menu',
'sigil' => 'phabricator-notification-menu',
'style' => 'display: none;',
),
'');
}
}
$user_dropdown = null;
$user_tag = null;
if ($viewer->isLoggedIn()) {
if (!$viewer->getIsEmailVerified()) {
$bubble_id = celerity_generate_unique_node_id();
$count_id = celerity_generate_unique_node_id();
$dropdown_id = celerity_generate_unique_node_id();
$settings_uri = id(new PhabricatorEmailAddressesSettingsPanel())
->setViewer($viewer)
->setUser($viewer)
->getPanelURI();
$user_icon = javelin_tag(
'span',
array(
'class' => 'phabricator-main-menu-setup-icon phui-icon-view '.
'phui-font-fa fa-user',
'sigil' => 'menu-icon',
));
$user_count = javelin_tag(
'span',
array(
'class' => 'phabricator-main-menu-setup-count',
'id' => $count_id,
),
1);
$user_tag = phutil_tag(
'a',
array(
'href' => $settings_uri,
'class' => 'setup-unread',
'id' => $bubble_id,
),
array(
$user_icon,
$user_count,
));
Javelin::initBehavior(
'aphlict-dropdown',
array(
'bubbleID' => $bubble_id,
'countID' => $count_id,
'dropdownID' => $dropdown_id,
'loadingText' => pht('Loading...'),
'uri' => '/settings/issue/',
'unreadClass' => 'setup-unread',
));
$user_dropdown = javelin_tag(
'div',
array(
'id' => $dropdown_id,
'class' => 'phabricator-notification-menu',
'sigil' => 'phabricator-notification-menu',
'style' => 'display: none;',
));
}
}
$dropdowns = array(
$notification_dropdown,
$message_notification_dropdown,
$setup_notification_dropdown,
$user_dropdown,
);
return array(
array(
$bubble_tag,
$message_tag,
$setup_tag,
$user_tag,
),
$dropdowns,
$aural,
);
}
private function isFullSession(PhabricatorUser $viewer) {
if (!$viewer->isLoggedIn()) {
return false;
}
if (!$viewer->isUserActivated()) {
return false;
}
if (!$viewer->hasSession()) {
return false;
}
$session = $viewer->getSession();
if ($session->getIsPartial()) {
return false;
}
if (!$session->getSignedLegalpadDocuments()) {
return false;
}
$mfa_key = 'security.require-multi-factor-auth';
$need_mfa = PhabricatorEnv::getEnvConfig($mfa_key);
if ($need_mfa) {
$have_mfa = $viewer->getIsEnrolledInMultiFactor();
if (!$have_mfa) {
return false;
}
}
return true;
}
}
diff --git a/src/view/phui/PHUICurtainObjectRefView.php b/src/view/phui/PHUICurtainObjectRefView.php
index aa07d7fc4f..e7ead114c7 100644
--- a/src/view/phui/PHUICurtainObjectRefView.php
+++ b/src/view/phui/PHUICurtainObjectRefView.php
@@ -1,223 +1,231 @@
<?php
final class PHUICurtainObjectRefView
extends AphrontTagView {
private $handle;
private $epoch;
private $highlighted;
private $exiled;
+ private $exileNote = false;
public function setHandle(PhabricatorObjectHandle $handle) {
$this->handle = $handle;
return $this;
}
public function setEpoch($epoch) {
$this->epoch = $epoch;
return $this;
}
public function setHighlighted($highlighted) {
$this->highlighted = $highlighted;
return $this;
}
- public function setExiled($is_exiled) {
+ public function setExiled($is_exiled, $note = false) {
$this->exiled = $is_exiled;
+ $this->exileNote = $note;
return $this;
}
protected function getTagAttributes() {
$classes = array();
$classes[] = 'phui-curtain-object-ref-view';
if ($this->highlighted) {
$classes[] = 'phui-curtain-object-ref-view-highlighted';
}
if ($this->exiled) {
$classes[] = 'phui-curtain-object-ref-view-exiled';
}
$classes = implode(' ', $classes);
return array(
'class' => $classes,
);
}
protected function getTagContent() {
require_celerity_resource('phui-curtain-object-ref-view-css');
$viewer = $this->getViewer();
$handle = $this->handle;
$more_rows = array();
$epoch = $this->epoch;
if ($epoch !== null) {
$epoch_view = phabricator_dual_datetime($epoch, $viewer);
$epoch_cells = array();
$epoch_cells[] = phutil_tag(
'td',
array(
'class' => 'phui-curtain-object-ref-view-epoch-cell',
),
$epoch_view);
$more_rows[] = phutil_tag('tr', array(), $epoch_cells);
}
if ($this->exiled) {
+ if ($this->exileNote !== false) {
+ $exile_note = $this->exileNote;
+ } else {
+ $exile_note = pht('No View Permission');
+ }
+
$exiled_view = array(
id(new PHUIIconView())->setIcon('fa-eye-slash red'),
' ',
- pht('No View Permission'),
+ $exile_note,
);
$exiled_cells = array();
$exiled_cells[] = phutil_tag(
'td',
array(
'class' => 'phui-curtain-object-ref-view-exiled-cell',
),
$exiled_view);
$more_rows[] = phutil_tag('tr', array(), $exiled_cells);
}
$header_cells = array();
$image_view = $this->newImage();
if ($more_rows) {
$row_count = 1 + count($more_rows);
} else {
$row_count = null;
}
$header_cells[] = phutil_tag(
'td',
array(
'rowspan' => $row_count,
'class' => 'phui-curtain-object-ref-view-image-cell',
),
$image_view);
$title_view = $this->newTitle();
$header_cells[] = phutil_tag(
'td',
array(
'class' => 'phui-curtain-object-ref-view-title-cell',
),
$title_view);
$rows = array();
if (!$more_rows) {
$title_row_class = 'phui-curtain-object-ref-view-without-content';
} else {
$title_row_class = 'phui-curtain-object-ref-view-with-content';
}
$rows[] = phutil_tag(
'tr',
array(
'class' => $title_row_class,
),
$header_cells);
$body = phutil_tag(
'tbody',
array(),
array(
$rows,
$more_rows,
));
return phutil_tag('table', array(), $body);
}
private function newTitle() {
$title_view = null;
$handle = $this->handle;
if ($handle) {
$title_view = $handle->renderLink();
}
return $title_view;
}
private function newImage() {
$image_uri = $this->getImageURI();
$target_uri = $this->getTargetURI();
$icon_view = null;
if ($image_uri == null) {
$icon_view = $this->newIconView();
}
if ($image_uri !== null) {
$image_view = javelin_tag(
'a',
array(
'style' => sprintf('background-image: url(%s)', $image_uri),
'href' => $target_uri,
'aural' => false,
));
} else if ($icon_view !== null) {
$image_view = javelin_tag(
'a',
array(
'href' => $target_uri,
'class' => 'phui-curtain-object-ref-view-icon-image',
'aural' => false,
),
$icon_view);
} else {
$image_view = null;
}
return $image_view;
}
private function getTargetURI() {
$target_uri = null;
$handle = $this->handle;
if ($handle) {
$target_uri = $handle->getURI();
}
return $target_uri;
}
private function getImageURI() {
$image_uri = null;
$handle = $this->handle;
if ($handle) {
$image_uri = $handle->getImageURI();
}
return $image_uri;
}
private function newIconView() {
$handle = $this->handle;
if ($handle) {
$icon_view = id(new PHUIIconView())
->setIcon($handle->getIcon());
}
return $icon_view;
}
}
diff --git a/webroot/rsrc/css/application/releeph/releeph-core.css b/webroot/rsrc/css/application/releeph/releeph-core.css
deleted file mode 100644
index 878415975c..0000000000
--- a/webroot/rsrc/css/application/releeph/releeph-core.css
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * @provides releeph-core
- */
-
-
-/* Colors: match differential colors */
-
-.releeph-request-comment {
- border-color: #ddd;
-}
-
-.releeph-request-comment-pusher {
- background: #8DEE8D;
- border-color: #096;
-}
-
-.releeph-request-comment-pusher div {
- background: #8DEE8D;
-}
-
-/* The diff size bar */
-
-.diff-bar {
- border: 0px;
-}
-
-.diff-bar div {
- width: 100px;
- border: 1px solid;
- border-top-color: #A4A4A4;
- border-right-color: #BBB;
- border-bottom-color: #D5D5D5;
- border-left-color: #BBB;
- background: white;
- float: left;
- margin-right: 1em;
-}
-
-.diff-bar div div {
- height: 10px;
-}
-
-.diff-bar span {
- color: #555;
-}
diff --git a/webroot/rsrc/css/application/releeph/releeph-preview-branch.css b/webroot/rsrc/css/application/releeph/releeph-preview-branch.css
deleted file mode 100644
index 6215703ccf..0000000000
--- a/webroot/rsrc/css/application/releeph/releeph-preview-branch.css
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * @provides releeph-preview-branch
- */
-
-.releeph-preview-branch {
- min-height: 4em;
- position: relative;
-}
-
-.releeph-preview-branch .error {
- padding-left: 22px;
- background-repeat: no-repeat;
- background-size: 16px auto;
- float: left;
- position: absolute;
- top: 2.5em;
-
- /* TODO: This had a background that's still at Facebook? */
-}
-
-.releeph-preview-branch .name {
- clear: both;
- float: left;
- position: absolute;
- font-family: monospace;
- font-size: 9pt !important;
- background: white;
- top: 0.7em;
- padding: 2px;
-}
diff --git a/webroot/rsrc/css/application/releeph/releeph-request-differential-create-dialog.css b/webroot/rsrc/css/application/releeph/releeph-request-differential-create-dialog.css
deleted file mode 100644
index 7101f78789..0000000000
--- a/webroot/rsrc/css/application/releeph/releeph-request-differential-create-dialog.css
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * @provides releeph-request-differential-create-dialog
- */
-
-.releeph-request-differential-create-dialog h1 {
- color: gray;
- font-style: italic;
- font-size: 16px;
- margin-top: 0.8em;
-}
-
-.releeph-request-differential-create-dialog a {
- font-weight: bold;
- margin-left: 2em;
- display: block;
- margin-top: 1em;
-}
diff --git a/webroot/rsrc/css/application/releeph/releeph-request-typeahead.css b/webroot/rsrc/css/application/releeph/releeph-request-typeahead.css
deleted file mode 100644
index 91a81acea6..0000000000
--- a/webroot/rsrc/css/application/releeph/releeph-request-typeahead.css
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * @provides releeph-request-typeahead-css
- */
-
-.releeph-request-typeahead .commit-id {
- color: #aaf; /* blue... */
- font-family: monospace;
- font-size: 100%;
- display: block;
- float: left;
-}
-
-.releeph-request-typeahead .author-info {
- color: #080; /* ...and green, for search results! */
- text-align: right;
- display: block;
- float: right;
- padding-left: 1em;
-}
-
-.releeph-request-typeahead .focused .author-info {
- color: #8b8;
-}
-
-.releeph-request-typeahead .summary {
- clear: both;
-}
diff --git a/webroot/rsrc/css/phui/phui-curtain-object-ref-view.css b/webroot/rsrc/css/phui/phui-curtain-object-ref-view.css
index 8ae14be04f..4be03ace8f 100644
--- a/webroot/rsrc/css/phui/phui-curtain-object-ref-view.css
+++ b/webroot/rsrc/css/phui/phui-curtain-object-ref-view.css
@@ -1,97 +1,98 @@
/**
* @provides phui-curtain-object-ref-view-css
*/
.phui-curtain-object-ref-list-view-empty {
font-style: italic;
color: {$greytext};
}
.phui-curtain-object-ref-view {
padding: 4px 6px;
border-radius: 3px;
}
.phui-curtain-object-ref-view + .phui-curtain-object-ref-view {
margin-top: 1px;
}
.phui-curtain-object-ref-view-image-cell {
min-width: 32px;
padding-bottom: 24px;
}
.phui-curtain-object-ref-view-image-cell > a {
height: 24px;
width: 24px;
background-size: 100%;
border-radius: 3px;
display: block;
position: absolute;
}
.phui-curtain-object-ref-view-image-cell .phui-icon-view {
font-size: 16px;
line-height: 16px;
vertical-align: middle;
text-align: center;
width: 24px;
height: 24px;
top: 3px;
display: block;
position: absolute;
color: #ffffff;
}
.phui-curtain-object-ref-view-icon-image {
background-color: {$backdrop};
}
.phui-curtain-object-ref-view-title-cell {
font-weight: bold;
text-overflow: ellipsis;
overflow: hidden;
/* This is forcing "text-overflow: ellipsis" to actually work. */
max-width: 210px;
}
.phui-curtain-object-ref-view-without-content >
.phui-curtain-object-ref-view-title-cell {
vertical-align: middle;
}
.phui-curtain-object-ref-view-with-content >
.phui-curtain-object-ref-view-image-cell > a {
margin-top: 4px;
}
.phui-curtain-object-ref-view-title-cell > a {
color: {$darkgreytext};
}
.phui-curtain-object-ref-view-epoch-cell {
color: {$greytext};
}
.phui-curtain-object-ref-list-view-tail {
text-align: center;
margin-top: 8px;
padding: 4px;
background: {$lightgreybackground};
border-top: 1px dashed {$thinblueborder};
box-shadow: inset 0 2px 3px rgba(0, 0, 0, 0.04);
}
.phui-curtain-object-ref-view-highlighted {
background: {$bluebackground};
}
.phui-curtain-object-ref-view-exiled {
background: {$lightred};
opacity: 0.75;
}
-.phui-curtain-object-ref-view-exiled-cell {
+.phui-curtain-object-ref-view-exiled-cell,
+.phui-curtain-object-ref-view-exiled-cell a {
color: {$red};
}
diff --git a/webroot/rsrc/js/application/releeph/releeph-preview-branch.js b/webroot/rsrc/js/application/releeph/releeph-preview-branch.js
deleted file mode 100644
index b08f17faf4..0000000000
--- a/webroot/rsrc/js/application/releeph/releeph-preview-branch.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * @provides javelin-behavior-releeph-preview-branch
- * @requires javelin-behavior
- * javelin-dom
- * javelin-uri
- * javelin-request
- */
-
-JX.behavior('releeph-preview-branch', function(config) {
-
- var uri = JX.$U(config.uri);
- for (var param_name in config.params.static) {
- var value = config.params.static[param_name];
- uri.setQueryParam(param_name, value);
- }
-
- var output = JX.$(config.outputID);
-
- var dynamics = config.params.dynamic;
-
- function renderPreview() {
- for (var param_name in dynamics) {
- var node_id = dynamics[param_name];
- var input = JX.$(node_id);
- uri.setQueryParam(param_name, input.value);
- }
- var request = new JX.Request(uri, function(response) {
- JX.DOM.setContent(output, JX.$H(response.markup));
- });
- request.send();
- }
-
- renderPreview();
-
- for (var ii in dynamics) {
- var node_id = dynamics[ii];
- var input = JX.$(node_id);
- JX.DOM.listen(
- input,
- ['keyup', 'click', 'change'],
- null,
- function() {
- renderPreview();
- }
- );
- }
-
-});
diff --git a/webroot/rsrc/js/application/releeph/releeph-request-state-change.js b/webroot/rsrc/js/application/releeph/releeph-request-state-change.js
deleted file mode 100644
index 8b05b081de..0000000000
--- a/webroot/rsrc/js/application/releeph/releeph-request-state-change.js
+++ /dev/null
@@ -1,121 +0,0 @@
-/**
- * @provides javelin-behavior-releeph-request-state-change
- * @requires javelin-behavior
- * javelin-dom
- * javelin-stratcom
- * javelin-workflow
- * javelin-util
- * phabricator-keyboard-shortcut
- */
-
-JX.behavior('releeph-request-state-change', function() {
- function getRequestHeaderNodes() {
- return JX.DOM.scry(document.body, 'div', 'releeph-request-box');
- }
-
- var keynav_cursor = -1;
-
- function keynavJump(manager, delta) {
- // Calculate this everytime, because the DOM changes.
- var headers = getRequestHeaderNodes();
- keynav_cursor += delta;
-
- if (keynav_cursor < 0) {
- keynav_cursor = -1;
- JX.DOM.scrollToPosition(0, 0);
- keynavMarkup();
- return;
- }
-
- if (keynav_cursor >= headers.length) {
- keynav_cursor = headers.length - 1;
- }
-
- var focus = headers[keynav_cursor];
- manager.scrollTo(focus);
-
- keynavMarkup();
- }
-
- function keynavMarkup() {
- var headers = getRequestHeaderNodes();
- for (var k in headers) {
- JX.DOM.alterClass(headers[k], 'focus', k == keynav_cursor);
- }
- }
-
- function keynavAction(manager, action_name) {
- var headers = getRequestHeaderNodes();
- var header = headers[keynav_cursor];
-
- if (keynav_cursor < 0) {
- return;
- }
-
- var sigil = action_name;
- var button = JX.DOM.find(header, 'a', sigil);
- if (button) {
- button.click();
- }
- }
-
- function keynavNavigateToRequestPage() {
- var headers = getRequestHeaderNodes();
- var header = headers[keynav_cursor];
- window.open(JX.Stratcom.getData(header).uri);
- }
-
- new JX.KeyboardShortcut('j', 'Jump to next request.')
- .setHandler(function(manager) {
- keynavJump(manager, +1);
- })
- .register();
-
- new JX.KeyboardShortcut('k', 'Jump to previous request.')
- .setHandler(function(manager) {
- keynavJump(manager, -1);
- })
- .register();
-
- new JX.KeyboardShortcut('a', 'Approve the selected request.')
- .setHandler(function(manager) {
- keynavAction(manager, 'want');
- })
- .register();
-
- new JX.KeyboardShortcut('r', 'Reject the selected request.')
- .setHandler(function(manager) {
- keynavAction(manager, 'pass');
- })
- .register();
-
- new JX.KeyboardShortcut(
- ['g', 'return'],
- 'Open selected request\'s page in a new tab.')
- .setHandler(function() {
- keynavNavigateToRequestPage();
- })
- .register();
-
- function onresponse(box, response) {
- JX.DOM.replace(box, JX.$H(response.markup));
- keynavMarkup();
- }
-
- JX.Stratcom.listen(
- 'click',
- 'releeph-request-state-change',
- function(e) {
- e.kill();
-
- var box = e.getNode('releeph-request-box');
- var link = e.getNode('releeph-request-state-change');
-
- box.style.opacity = '0.5';
-
- JX.Workflow.newFromLink(link)
- .setData({render: true})
- .setHandler(JX.bind(null, onresponse, box))
- .start();
- });
-});
diff --git a/webroot/rsrc/js/application/releeph/releeph-request-typeahead.js b/webroot/rsrc/js/application/releeph/releeph-request-typeahead.js
deleted file mode 100644
index 43f9366311..0000000000
--- a/webroot/rsrc/js/application/releeph/releeph-request-typeahead.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * @provides javelin-behavior-releeph-request-typeahead
- * @requires javelin-behavior
- * javelin-dom
- * javelin-typeahead
- * javelin-typeahead-ondemand-source
- * javelin-dom
- */
-
-JX.behavior('releeph-request-typeahead', function(config) {
- var root = JX.$(config.id);
- var datasource = new JX.TypeaheadOnDemandSource(config.src);
- var callsign = config.aux.callsign;
-
- datasource.setAuxiliaryData(config.aux);
-
- datasource.setTransformer(
- function(object) {
- var full_commit_id = object[0];
- var short_commit_id = object[1];
- var author = object[2];
- var ago = object[3];
- var summary = object[4];
-
- var callsign_commit_id = 'r' + callsign + short_commit_id;
-
- var box =
- JX.$N(
- 'div',
- {},
- [
- JX.$N(
- 'div',
- { className: 'commit-id' },
- callsign_commit_id
- ),
- JX.$N(
- 'div',
- { className: 'author-info' },
- ago + ' ago by ' + author
- ),
- JX.$N(
- 'div',
- { className: 'summary' },
- summary
- )
- ]
- );
-
- return {
- name: callsign_commit_id,
- tokenizable: callsign_commit_id + ' '+ short_commit_id + ' ' + summary,
- display: box,
- uri: null,
- id: full_commit_id
- };
- });
-
- /**
- * The default normalizer removes useful control characters that would help
- * out search. For example, I was just trying to search for a commit with
- * the string "a_file" in the message, which was normalized to "afile".
- */
- datasource.setNormalizer(function(query) {
- return query;
- });
-
- datasource.setMaximumResultCount(config.aux.limit);
-
- var typeahead = new JX.Typeahead(root);
- typeahead.setDatasource(datasource);
-
- var placeholder = config.value || config.placeholder;
- if (placeholder) {
- typeahead.setPlaceholder(placeholder);
- }
-
- typeahead.start();
-});
diff --git a/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js b/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js
index 4493ae3b2b..8cf14349b0 100644
--- a/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js
+++ b/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js
@@ -1,33 +1,59 @@
/**
* @provides javelin-behavior-aphront-drag-and-drop-textarea
* @requires javelin-behavior
* javelin-dom
+ * javelin-json
* phabricator-drag-and-drop-file-upload
* phabricator-textareautils
*/
JX.behavior('aphront-drag-and-drop-textarea', function(config) {
var target = JX.$(config.target);
+ var metadata_node = JX.$(config.remarkupMetadataID);
+ var metadata_value = config.remarkupMetadataValue;
+
+ function set_metadata(key, value) {
+ metadata_value[key] = value;
+ write_metadata();
+ }
+
+ function get_metadata(key, default_value) {
+ if (metadata_value.hasOwnProperty(key)) {
+ return metadata_value[key];
+ }
+ return default_value;
+ }
+
+ function write_metadata() {
+ metadata_node.value = JX.JSON.stringify(metadata_value);
+ }
+
+ write_metadata();
+
if (JX.PhabricatorDragAndDropFileUpload.isSupported()) {
var drop = new JX.PhabricatorDragAndDropFileUpload(target)
.setURI(config.uri)
.setChunkThreshold(config.chunkThreshold);
drop.listen('didBeginDrag', function() {
JX.DOM.alterClass(target, config.activatedClass, true);
});
drop.listen('didEndDrag', function() {
JX.DOM.alterClass(target, config.activatedClass, false);
});
drop.listen('didUpload', function(file) {
JX.TextAreaUtils.insertFileReference(target, file);
+
+ var phids = get_metadata('attachedFilePHIDs', []);
+ phids.push(file.getPHID());
+ set_metadata('attachedFilePHIDs', phids);
});
drop.start();
}
});

File Metadata

Mime Type
application/octet-stream
Expires
Thu, May 9, 1:59 PM (2 d)
Storage Engine
chunks
Storage Format
Chunks
Storage Handle
f6FDnKCXKRBz
Default Alt Text
(5 MB)

Event Timeline