Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php
index 316cee69b2..3476a9c056 100644
--- a/src/applications/config/controller/PhabricatorConfigEditController.php
+++ b/src/applications/config/controller/PhabricatorConfigEditController.php
@@ -1,485 +1,491 @@
<?php
final class PhabricatorConfigEditController
extends PhabricatorConfigController {
private $key;
public function willProcessRequest(array $data) {
$this->key = $data['key'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$options = PhabricatorApplicationConfigOptions::loadAllOptions();
if (empty($options[$this->key])) {
// 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($this->key)
->setType('wild')
->setDefault(null)
->setDescription(
pht(
"This configuration option is unknown. It may be misspelled, ".
"or have existed in a previous version of Phabricator."));
$group = null;
$group_uri = $this->getApplicationURI();
} else {
$option = $options[$this->key];
$group = $option->getGroup();
$group_uri = $this->getApplicationURI('group/'.$group->getKey().'/');
}
$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 = $group_uri;
}
// 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',
$this->key,
'default');
if (!$config_entry) {
$config_entry = id(new PhabricatorConfigEntry())
->setConfigKey($this->key)
->setNamespace('default')
->setIsDeleted(true);
}
$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($user)
->setContinueOnNoEffect(true)
->setContentSource(
PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_WEB,
array(
'ip' => $request->getRemoteAddr(),
)));
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 {
$display_value = $this->getDisplayValue($option, $config_entry);
}
$form = new AphrontFormView();
$form->setFlexible(true);
$error_view = null;
if ($errors) {
$error_view = id(new AphrontErrorView())
->setTitle(pht('You broke everything!'))
->setErrors($errors);
} else if ($option->getHidden()) {
$msg = pht(
"This configuration is hidden and can not be edited or viewed from ".
"the web interface.");
$error_view = id(new AphrontErrorView())
->setTitle(pht('Configuration Hidden'))
->setSeverity(AphrontErrorView::SEVERITY_WARNING)
->appendChild('<p>'.phutil_escape_html($msg).'</p>');
} else if ($option->getLocked()) {
$msg = pht(
"This configuration is locked and can not be edited from the web ".
"interface.");
$error_view = id(new AphrontErrorView())
->setTitle(pht('Configuration Locked'))
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->appendChild('<p>'.phutil_escape_html($msg).'</p>');
}
if ($option->getHidden()) {
$control = null;
} else {
$control = $this->renderControl(
$option,
$display_value,
$e_value);
}
$engine = new PhabricatorMarkupEngine();
$engine->addObject($option, 'description');
$engine->process();
$description = phutil_render_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$engine->getOutput($option, 'description'));
$form
->setUser($user)
->addHiddenInput('issue', $request->getStr('issue'))
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Description'))
->setValue($description))
->appendChild($control);
$submit_control = id(new AphrontFormSubmitControl())
->addCancelButton($done_uri);
if (!$option->getLocked()) {
$submit_control->setValue(pht('Save Config Entry'));
}
$form->appendChild($submit_control);
$examples = $this->renderExamples($option);
if ($examples) {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Examples'))
->setValue($examples));
}
if (!$option->getHidden()) {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Default'))
->setValue($this->renderDefaults($option)));
}
$title = pht('Edit %s', $this->key);
$short = pht('Edit');
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Config'))
->setHref($this->getApplicationURI()));
if ($group) {
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($group->getName())
->setHref($group_uri));
}
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($this->key)
->setHref('/config/edit/'.$this->key));
$xactions = id(new PhabricatorConfigTransactionQuery())
->withObjectPHIDs(array($config_entry->getPHID()))
->setViewer($user)
->execute();
$xaction_view = id(new PhabricatorApplicationTransactionView())
->setUser($user)
->setTransactions($xactions);
return $this->buildApplicationPage(
array(
$crumbs,
id(new PhabricatorHeaderView())->setHeader($title),
$error_view,
$form,
$xaction_view,
),
array(
'title' => $title,
'device' => true,
));
}
private function readRequest(
PhabricatorConfigOption $option,
AphrontRequest $request) {
$xaction = new PhabricatorConfigTransaction();
$xaction->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT);
$e_value = null;
$errors = array();
$value = $request->getStr('value');
if (!strlen($value)) {
$value = null;
$xaction->setNewValue(
array(
'deleted' => true,
'value' => null,
));
return array($e_value, $errors, $value, $xaction);
}
$type = $option->getType();
$set_value = null;
switch ($type) {
case 'int':
if (preg_match('/^-?[0-9]+$/', trim($value))) {
$set_value = (int)$value;
} else {
$e_value = pht('Invalid');
$errors[] = pht('Value must be an integer.');
}
break;
case 'string':
$set_value = (string)$value;
break;
case 'list<string>':
$set_value = $request->getStrList('value');
break;
+ case 'set':
+ $set_value = array_fill_keys($request->getStrList('value'), true);
+ break;
case 'bool':
switch ($value) {
case 'true':
$set_value = true;
break;
case 'false':
$set_value = false;
break;
default:
$e_value = pht('Invalid');
$errors[] = pht('Value must be boolean, "true" or "false".');
break;
}
break;
case 'class':
if (!class_exists($value)) {
$e_value = pht('Invalid');
$errors[] = pht('Class does not exist.');
} else {
$base = $option->getBaseClass();
if (!is_subclass_of($value, $base)) {
$e_value = pht('Invalid');
$errors[] = pht('Class is not of valid type.');
} else {
$set_value = $value;
}
}
break;
default:
$json = json_decode($value, true);
if ($json === null && strtolower($value) != 'null') {
$e_value = pht('Invalid');
$errors[] = pht(
'The given value must be valid JSON. This means, among '.
'other things, that you must wrap strings in double-quotes.');
$set_value = $json;
}
break;
}
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) {
if ($entry->getIsDeleted()) {
return null;
}
$type = $option->getType();
$value = $entry->getValue();
switch ($type) {
case 'int':
case 'string':
return $value;
case 'bool':
return $value ? 'true' : 'false';
case 'list<string>':
return implode("\n", nonempty($value, array()));
+ case 'set':
+ return implode("\n", nonempty(array_keys($value), array()));
default:
return PhabricatorConfigJSON::prettyPrintJSON($value);
}
}
private function renderControl(
PhabricatorConfigOption $option,
$display_value,
$e_value) {
$type = $option->getType();
switch ($type) {
case 'int':
case 'string':
$control = id(new AphrontFormTextControl());
break;
case 'bool':
$control = id(new AphrontFormSelectControl())
->setOptions(
array(
'' => pht('(Use Default)'),
'true' => idx($option->getBoolOptions(), 0),
'false' => idx($option->getBoolOptions(), 1),
));
break;
case 'class':
$symbols = id(new PhutilSymbolLoader())
->setType('class')
->setAncestorClass($option->getBaseClass())
->setConcreteOnly(true)
->selectSymbolsWithoutLoading();
$names = ipull($symbols, 'name', 'name');
asort($names);
$names = array(
'' => pht('(Use Default)'),
) + $names;
$control = id(new AphrontFormSelectControl())
->setOptions($names);
break;
case 'list<string>':
+ case 'set':
$control = id(new AphrontFormTextAreaControl())
->setCaption(pht('Separate values with newlines or commas.'));
break;
default:
$control = id(new AphrontFormTextAreaControl())
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setCustomClass('PhabricatorMonospaced')
->setCaption(pht('Enter value in JSON.'));
break;
}
$control
->setLabel('Value')
->setError($e_value)
->setValue($display_value)
->setName('value');
if ($option->getLocked()) {
$control->setDisabled(true);
}
return $control;
}
private function renderExamples(PhabricatorConfigOption $option) {
$examples = $option->getExamples();
if (!$examples) {
return null;
}
$table = array();
$table[] = '<tr class="column-labels">';
$table[] = '<th>'.pht('Example').'</th>';
$table[] = '<th>'.pht('Value').'</th>';
$table[] = '</tr>';
foreach ($examples as $example) {
list($value, $description) = $example;
if ($value === null) {
$value = '<em>'.pht('(empty)').'</em>';
} else {
$value = nl2br(phutil_escape_html($value));
}
$table[] = '<tr>';
$table[] = '<th>'.phutil_escape_html($description).'</th>';
$table[] = '<td>'.$value.'</td>';
$table[] = '</tr>';
}
require_celerity_resource('config-options-css');
return phutil_render_tag(
'table',
array(
'class' => 'config-option-table',
),
implode("\n", $table));
}
private function renderDefaults(PhabricatorConfigOption $option) {
$stack = PhabricatorEnv::getConfigSourceStack();
$stack = $stack->getStack();
/*
TODO: Once DatabaseSource lands, do this:
foreach ($stack as $key => $source) {
unset($stack[$key]);
if ($source instanceof PhabricatorConfigDatabaseSource) {
break;
}
}
*/
$table = array();
$table[] = '<tr class="column-labels">';
$table[] = '<th>'.pht('Source').'</th>';
$table[] = '<th>'.pht('Value').'</th>';
$table[] = '</tr>';
foreach ($stack as $key => $source) {
$value = $source->getKeys(
array(
$option->getKey(),
));
if (!array_key_exists($option->getKey(), $value)) {
$value = '<em>'.pht('(empty)').'</em>';
} else {
$value = PhabricatorConfigJSON::prettyPrintJSON(
$value[$option->getKey()]);
}
$table[] = '<tr>';
$table[] = '<th>'.phutil_escape_html($source->getName()).'</th>';
$table[] = '<td>'.$value.'</td>';
$table[] = '</tr>';
}
require_celerity_resource('config-options-css');
return phutil_render_tag(
'table',
array(
'class' => 'config-option-table',
),
implode("\n", $table));
}
}
diff --git a/src/applications/config/json/PhabricatorConfigJSON.php b/src/applications/config/json/PhabricatorConfigJSON.php
index 2027533a20..2e82d1297f 100644
--- a/src/applications/config/json/PhabricatorConfigJSON.php
+++ b/src/applications/config/json/PhabricatorConfigJSON.php
@@ -1,25 +1,27 @@
<?php
final class PhabricatorConfigJSON {
/**
* Properly format a JSON value.
*
* @param wild Any value, but should be a raw value, not a string of JSON.
* @return string
*/
public static function prettyPrintJSON($value) {
// Check not only that it's an array, but that it's an "unnatural" array
// meaning that the keys aren't 0 -> size_of_array.
if (is_array($value) && array_keys($value) != range(0, count($value) - 1)) {
- return id(new PhutilJSON())->encodeFormatted($value);
+ $result = id(new PhutilJSON())->encodeFormatted($value);
} else {
$result = json_encode($value);
- // For readability, unescape forward slashes. These are normally escaped
- // to prevent the string "</script>" from appearing in a JSON literal,
- // but it's irrelevant here and makes reading paths more difficult than
- // necessary.
- $result = str_replace('\\/', '/', $result);
- return $result;
}
+
+ // For readability, unescape forward slashes. These are normally escaped
+ // to prevent the string "</script>" from appearing in a JSON literal,
+ // but it's irrelevant here and makes reading paths more difficult than
+ // necessary.
+ $result = str_replace('\\/', '/', $result);
+ return $result;
+
}
}
diff --git a/src/applications/config/option/PhabricatorApplicationConfigOptions.php b/src/applications/config/option/PhabricatorApplicationConfigOptions.php
index 9bd6d402db..8c14f42370 100644
--- a/src/applications/config/option/PhabricatorApplicationConfigOptions.php
+++ b/src/applications/config/option/PhabricatorApplicationConfigOptions.php
@@ -1,161 +1,179 @@
<?php
abstract class PhabricatorApplicationConfigOptions extends Phobject {
abstract public function getName();
abstract public function getDescription();
abstract public function getOptions();
public function validateOption(PhabricatorConfigOption $option, $value) {
if ($value === $option->getDefault()) {
return;
}
if ($value === null) {
return;
}
switch ($option->getType()) {
case 'bool':
if ($value !== true &&
$value !== false) {
throw new PhabricatorConfigValidationException(
pht(
"Option '%s' is of type bool, but value is not true or false.",
$option->getKey()));
}
break;
case 'int':
if (!is_int($value)) {
throw new PhabricatorConfigValidationException(
pht(
"Option '%s' is of type int, but value is not an integer.",
$option->getKey()));
}
break;
case 'string':
if (!is_string($value)) {
throw new PhabricatorConfigValidationException(
pht(
"Option '%s' is of type string, but value is not a string.",
$option->getKey()));
}
break;
case 'class':
$symbols = id(new PhutilSymbolLoader())
->setType('class')
->setAncestorClass($option->getBaseClass())
->setConcreteOnly(true)
->selectSymbolsWithoutLoading();
$names = ipull($symbols, 'name', 'name');
if (empty($names[$value])) {
throw new PhabricatorConfigValidationException(
pht(
"Option '%s' value must name a class extending '%s'.",
$option->getKey(),
$option->getBaseClass()));
}
break;
+ case 'set':
+ $valid = true;
+ if (!is_array($value)) {
+ throw new PhabricatorConfigValidationException(
+ pht(
+ "Option '%s' must be a set, but value is not an array.",
+ $option->getKey()));
+ }
+ foreach ($value as $v) {
+ if ($v !== true) {
+ throw new PhabricatorConfigValidationException(
+ pht(
+ "Option '%s' must be a set, but array contains values other ".
+ "than 'true'.",
+ $option->getKey()));
+ }
+ }
+ break;
case 'list<string>':
$valid = true;
if (!is_array($value)) {
throw new PhabricatorConfigValidationException(
pht(
"Option '%s' must be a list of strings, but value is not ".
"an array.",
$option->getKey()));
}
if ($value && array_keys($value) != range(0, count($value) - 1)) {
throw new PhabricatorConfigValidationException(
pht(
"Option '%s' must be a list of strings, but the value is a ".
"map with unnatural keys.",
$option->getKey()));
}
foreach ($value as $v) {
if (!is_string($v)) {
throw new PhabricatorConfigValidationException(
pht(
"Option '%s' must be a list of strings, but it contains one ".
"or more non-strings.",
$option->getKey()));
}
}
break;
case 'wild':
default:
break;
}
$this->didValidateOption($option, $value);
}
protected function didValidateOption(
PhabricatorConfigOption $option,
$value) {
// Hook for subclasses to do complex validation.
return;
}
public function getKey() {
$class = get_class($this);
$matches = null;
if (preg_match('/^Phabricator(.*)ConfigOptions$/', $class, $matches)) {
return strtolower($matches[1]);
}
return strtolower(get_class($this));
}
final protected function newOption($key, $type, $default) {
return id(new PhabricatorConfigOption())
->setKey($key)
->setType($type)
->setDefault($default)
->setGroup($this);
}
final public static function loadAll() {
$symbols = id(new PhutilSymbolLoader())
->setAncestorClass('PhabricatorApplicationConfigOptions')
->setConcreteOnly(true)
->selectAndLoadSymbols();
$groups = array();
foreach ($symbols as $symbol) {
$obj = newv($symbol['name'], array());
$key = $obj->getKey();
if (isset($groups[$key])) {
$pclass = get_class($groups[$key]);
$nclass = $symbol['name'];
throw new Exception(
"Multiple PhabricatorApplicationConfigOptions subclasses have the ".
"same key ('{$key}'): {$pclass}, {$nclass}.");
}
$groups[$key] = $obj;
}
return $groups;
}
final public static function loadAllOptions() {
$groups = self::loadAll();
$options = array();
foreach ($groups as $group) {
foreach ($group->getOptions() as $option) {
$key = $option->getKey();
if (isset($options[$key])) {
throw new Exception(
"Mulitple PhabricatorApplicationConfigOptions subclasses contain ".
"an option named '{$key}'!");
}
$options[$key] = $option;
}
}
return $options;
}
}
diff --git a/src/applications/config/option/PhabricatorCoreConfigOptions.php b/src/applications/config/option/PhabricatorCoreConfigOptions.php
index ad5040d4cc..07bf393581 100644
--- a/src/applications/config/option/PhabricatorCoreConfigOptions.php
+++ b/src/applications/config/option/PhabricatorCoreConfigOptions.php
@@ -1,186 +1,186 @@
<?php
final class PhabricatorCoreConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht("Core");
}
public function getDescription() {
return pht("Configure core options, including URIs.");
}
public function getOptions() {
return array(
$this->newOption('phabricator.base-uri', 'string', null)
->setSummary(pht("URI where Phabricator is installed."))
->setDescription(
pht(
"Set the URI where Phabricator 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')),
$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), ".
"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 ".
"{{phabricator.base-uri}}. Most installs do not need to set ".
"this option."))
->addExample('http://phabricator.example.com/', pht('Valid Setting')),
$this->newOption('phabricator.timezone', 'string', null)
->setSummary(
pht("The timezone Phabricator should use."))
->setDescription(
pht(
"PHP requires that you set a timezone in your php.ini before ".
"using date functions, or it will emit a warning. If this isn't ".
"possible (for instance, because you are using HPHP) you can set ".
"some valid constant for date_default_timezone_set() here and ".
"Phabricator will set it on your behalf, silencing the warning."))
->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.serious-business', 'bool', false)
->setBoolOptions(
array(
pht('Serious business'),
pht('Shenanigans'), // That should be interesting to translate. :P
))
->setSummary(
pht("Should Phabricator be serious?"))
->setDescription(
pht(
"By default, Phabricator includes some silly nonsense in the UI, ".
"such as a submit button called 'Clowncopterize' in Differential ".
"and a call to 'Leap Into Action'. If you'd prefer more ".
"traditional UI strings like 'Submit', you can set this flag to ".
"disable most of the jokes and easter eggs.")),
$this->newOption('environment.append-paths', 'list<string>', null)
->setSummary(
pht("These paths get appended to your \$PATH envrionment variable."))
->setDescription(
pht(
"Phabricator occasionally shells out to other binaries on the ".
"server. An example of this is the \"pygmentize\" command, used ".
"to syntax-highlight code written in languages other than PHP. ".
"By default, it is assumed that these binaries are in the \$PATH ".
"of the user running Phabricator (normally 'apache', 'httpd', or ".
"'nobody'). Here you can add extra directories to the \$PATH ".
"environment variable, for when these binaries are in ".
"non-standard locations."))
->addExample('/usr/local/bin', pht('Add One Path'))
->addExample("/usr/bin\n/usr/local/bin", pht('Add Multiple Paths')),
$this->newOption('tokenizer.ondemand', 'bool', false)
->setBoolOptions(
array(
pht("Query on demand"),
pht("Query on page load"),
))
->setSummary(
pht("Query for tokenizer fields on demand."))
->setDescription(
pht(
"Tokenizers are UI controls which let the user select other ".
"users, email addresses, project names, etc., by typing the ".
"first few letters and having the control autocomplete from a ".
"list. They can load their data in two ways: either in a big ".
"chunk up front, or as the user types. By default, the data is ".
"loaded in a big chunk. This is simpler and performs better for ".
"small datasets. However, if you have a very large number of ".
"users or projects, (in the ballpark of more than a thousand), ".
"loading all that data may become slow enough that it's ".
"worthwhile to query on demand instead. This makes the typeahead ".
"slightly less responsive but overall performance will be much ".
"better if you have a ton of stuff. You can figure out which ".
"setting is best for your install by changing this setting and ".
"then playing with a user tokenizer (like the user selectors in ".
"Maniphest or Differential) and seeing which setting loads ".
"faster and feels better.")),
- $this->newOption('config.lock', 'wild', array())
+ $this->newOption('config.lock', 'set', array())
->setLocked(true)
->setDescription(pht('Additional configuration options to lock.')),
- $this->newOption('config.hide', 'wild', array())
+ $this->newOption('config.hide', 'set', array())
->setLocked(true)
->setDescription(pht('Additional configuration options to hide.')),
- $this->newOption('config.mask', 'wild', array())
+ $this->newOption('config.mask', 'set', array())
->setLocked(true)
->setDescription(pht('Additional configuration options to mask.')),
);
}
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 ".
"'http://' or 'https://'.",
$key));
}
$domain = $uri->getDomain();
if (strpos($domain, '.') === false) {
throw new PhabricatorConfigValidationException(
pht(
"Config option '%s' is invalid. The URI must contain a dot ('.'), ".
"like 'http://example.com/', not just a bare name like ".
"'http://example/'. Some web browsers will not set cookies on ".
"domains with no TLD.",
$key));
}
$path = $uri->getPath();
if ($path !== '' && $path !== '/') {
throw new PhabricatorConfigValidationException(
pht(
"Config option '%s' is invalid. The URI must NOT have a path, ".
"e.g. 'http://phabricator.example.com/' is OK, but ".
"'http://example.com/phabricator/' is not. Phabricator must be ".
"installed on an entire domain; it can not be installed on a ".
"path.",
$key));
}
}
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 ".
"'America/Los_Angeles'. You can find a list of valid identifiers ".
"here: %s",
$key,
'http://php.net/manual/timezones.php'));
}
}
}
}
diff --git a/src/applications/config/option/PhabricatorSecurityConfigOptions.php b/src/applications/config/option/PhabricatorSecurityConfigOptions.php
index 7225bf3e0e..44f4e1bb8f 100644
--- a/src/applications/config/option/PhabricatorSecurityConfigOptions.php
+++ b/src/applications/config/option/PhabricatorSecurityConfigOptions.php
@@ -1,196 +1,197 @@
<?php
final class PhabricatorSecurityConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht("Security");
}
public function getDescription() {
return pht("Security options.");
}
public function getOptions() {
return array(
$this->newOption('security.alternate-file-domain', 'string', null)
->setSummary(pht("Alternate domain to serve files from."))
->setDescription(
pht(
"IMPORTANT: By default, Phabricator serves files from the same ".
"domain the application lives on. This is convenient but not ".
"secure: it creates a large class of vulnerabilities which can ".
"not be generally mitigated.\n\n".
"To avoid this, you should configure a second domain in the same ".
"way you have the primary domain configured (i.e., point it at ".
"the same machine and set up the same vhost rules) and provide ".
"it here. For instance, if your primary install is on ".
"'http://www.phabricator-example.com/', you could configure ".
"'http://www.phabricator-files.com/' and specify the entire ".
"domain (with protocol) here. This will enforce that files are ".
"served only from the alternate domain. Ideally, you should use ".
"a completely separate domain name rather than just a different ".
"subdomain.\n\n".
"It is **STRONGLY RECOMMENDED** that you configure this. Your ".
"install is **NOT SECURE** unless you do so."))
->addExample('http://www.phabricator-files.com/', pht('Valid Setting')),
$this->newOption(
'security.hmac-key',
'string',
'[D\t~Y7eNmnQGJ;rnH6aF;m2!vJ8@v8C=Cs:aQS\.Qw')
+ ->setMasked(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->newOption('security.require-https', 'bool', false)
->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.\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".
"NOTE: Phabricator determines if a request is HTTPS or not by ".
"examining the PHP \$_SERVER['HTTPS'] 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. Alternatively, you can add a PHP snippet to ".
"the top of this configuration file to directly set ".
"\$_SERVER['HTTPS'] to the correct value."))
->setBoolOptions(
array(
pht('Force HTTPS'),
pht('Allow HTTP'),
)),
$this->newOption(
'phabricator.csrf-key',
'string',
'0b7ec0592e0a2829d8b71df2fa269b2c6172eca3')
+ ->setMasked(true)
->setSummary(
pht("Hashed with other inputs to generate CSRF tokens."))
->setDescription(
pht(
"This is hashed with other inputs to generate CSRF tokens. If ".
"you want, you can change it to some other string which is ".
"unique to your install. This will make your install more secure ".
"in a vague, mostly theoretical way. But it will take you like 3 ".
"seconds of mashing on your keyboard to set it up so you might ".
"as well.")),
$this->newOption(
'phabricator.mail-key',
'string',
'5ce3e7e8787f6e40dfae861da315a5cdf1018f12')
+ ->setMasked(true)
->setSummary(
pht("Hashed with other inputs to generate mail tokens."))
->setDescription(
pht(
"This is hashed with other inputs to generate mail tokens. If ".
"you want, you can change it to some other string which is ".
"unique to your install. In particular, you will want to do ".
"this if you accidentally send a bunch of mail somewhere you ".
"shouldn't have, to invalidate all old reply-to addresses.")),
- // TODO: This should really be dict<string,bool> but that doesn't exist
- // yet.
- $this->newOption('uri.allowed-protocols', 'wild', null)
+ $this->newOption('uri.allowed-protocols', 'set', null)
->setSummary(
pht("Determines which URI protocols are auto-linked."))
->setDescription(
pht(
"When users write comments which have URIs, they'll be ".
"automatically linked if the protocol appears in this set. This ".
"whitelist is primarily to prevent security issues like ".
"javascript:// URIs."))
->addExample(
'{"http": true, "https": true"}', pht('Valid Setting')),
$this->newOption(
'celerity.resource-hash',
'string',
'd9455ea150622ee044f7931dabfa52aa')
->setSummary(
pht("An input to the hash function when building resource hashes."))
->setDescription(
pht(
"This value is an input to the hash function when building ".
"resource hashes. It has no security value, but if you ".
"accidentally poison user caches (by pushing a bad patch or ".
"having something go wrong with a CDN, e.g.) you can change this ".
"to something else and rebuild the Celerity map to break user ".
"caches. Unless you are doing Celerity development, it is ".
"exceptionally unlikely that you need to modify this.")),
$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 embeded ".
"inline. This has mild security implications (you'll leak ".
"referrers to YouTube) and is pretty silly (but sort of ".
"awesome).")),
);
}
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 ".
"'http://' or 'https://'.",
$key));
}
$domain = $uri->getDomain();
if (strpos($domain, '.') === false) {
throw new PhabricatorConfigValidationException(
pht(
"Config option '%s' is invalid. The URI must contain a dot ('.'), ".
"like 'http://example.com/', not just a bare name like ".
"'http://example/'. Some web browsers will not set cookies on ".
"domains with no TLD.",
$key));
}
$path = $uri->getPath();
if ($path !== '' && $path !== '/') {
throw new PhabricatorConfigValidationException(
pht(
"Config option '%s' is invalid. The URI must NOT have a path, ".
"e.g. 'http://phabricator.example.com/' is OK, but ".
"'http://example.com/phabricator/' is not. Phabricator must be ".
"installed on an entire domain; it can not be installed on a ".
"path.",
$key));
}
}
}
}
diff --git a/src/applications/files/config/PhabricatorFilesConfigOptions.php b/src/applications/files/config/PhabricatorFilesConfigOptions.php
index a561154f6c..b960cd6ee5 100644
--- a/src/applications/files/config/PhabricatorFilesConfigOptions.php
+++ b/src/applications/files/config/PhabricatorFilesConfigOptions.php
@@ -1,125 +1,125 @@
<?php
final class PhabricatorFilesConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht("Files");
}
public function getDescription() {
return pht("Configure files and file storage.");
}
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',
);
$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,
);
return array(
$this->newOption('files.viewable-mime-types', 'wild', $viewable_default)
->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 enormous binary files.'.
"\n\n".
'The keys in this map are vieweable MIME types; the values are '.
'the MIME type sthey are delivered as when they are viewed in '.
'the browser.')),
- $this->newOption('files.image-mime-types', 'wild', $image_default)
+ $this->newOption('files.image-mime-types', 'set', $image_default)
->setSummary(pht('Configure which MIME types are images.'))
->setDescription(
pht(
'List of MIME types which can be used as the `src` for an '.
'`<img />` tag.')),
$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)
->setSummary(pht('Local storage disk path.'))
->setDescription(
pht(
"Phabricator 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(
'storage.engine-selector',
'class',
'PhabricatorDefaultFileStorageEngineSelector')
->setBaseClass('PhabricatorFileStorageEngineSelector')
->setSummary(pht('Storage engine selector.'))
->setDescription(
pht(
"Phabricator uses a storage engine selector to choose which ".
"storage engine to use when writing file data. If you add new ".
"storage engines or want to provide very custom rules (e.g., ".
"write images to one storage engine and other files to a ".
"different one), you can provide an alternate implementation ".
"here. The default engine will use choose MySQL, Local Disk, and ".
"S3, in that order, if they have valid configurations above and ".
"a file fits within configured limits.")),
$this->newOption('storage.upload-size-limit', 'string', null)
->setSummary(
pht('Limit to users in interfaces which allow uploading.'))
->setDescription(
pht(
"Set the size of the largest file a user may upload. This is ".
"used to render text like 'Maximum file size: 10MB' on ".
"interfaces where users can upload files, and files larger than ".
"this size will be rejected. \n\n".
"Specify this limit in bytes, or using a 'K', 'M', or 'G' ".
"suffix.\n\n".
"NOTE: Setting this to a large size is **NOT** sufficient to ".
"allow users to upload large files. You must also configure a ".
"number of other settings. To configure file upload limits, ".
"consult the article 'Configuring File Upload Limits' in the ".
"documentation. Once you've configured some limit across all ".
"levels of the server, you can set this limit to an appropriate ".
"value and the UI will then reflect the actual configured ".
"limit."))
->addExample('10M', pht("Valid setting.")),
);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Dec 2, 4:21 AM (22 h, 32 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
431631
Default Alt Text
(45 KB)

Event Timeline