Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2892468
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
40 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/config/check/PhabricatorSetupCheckTimezone.php b/src/applications/config/check/PhabricatorSetupCheckTimezone.php
index 834afb14b1..ebff55f6f6 100644
--- a/src/applications/config/check/PhabricatorSetupCheckTimezone.php
+++ b/src/applications/config/check/PhabricatorSetupCheckTimezone.php
@@ -1,31 +1,51 @@
<?php
final class PhabricatorSetupCheckTimezone extends PhabricatorSetupCheck {
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 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 'date.timezone' or the Phabricator ".
"configuration value 'phabricator.timezone' to specify one.");
$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/infrastructure/PhabricatorSetup.php b/src/infrastructure/PhabricatorSetup.php
index 7e7edfef15..8645b9c729 100644
--- a/src/infrastructure/PhabricatorSetup.php
+++ b/src/infrastructure/PhabricatorSetup.php
@@ -1,666 +1,632 @@
<?php
final class PhabricatorSetup {
public static function runSetup() {
header("Content-Type: text/plain");
self::write("PHABRICATOR SETUP\n\n");
// Force browser to stop buffering.
self::write(str_repeat(' ', 2048));
usleep(250000);
self::write("This setup mode will guide you through setting up your ".
"Phabricator configuration.\n");
self::writeHeader("CORE CONFIGURATION");
// NOTE: Test this first since other tests depend on the ability to
// execute system commands and will fail if safe_mode is enabled.
$safe_mode = ini_get('safe_mode');
if ($safe_mode) {
self::writeFailure();
self::write(
"Setup failure! You have 'safe_mode' enabled. Phabricator will not ".
"run in safe mode, and it has been deprecated in PHP 5.3 and removed ".
"in PHP 5.4.\n");
return;
} else {
self::write(" okay PHP's deprecated 'safe_mode' is disabled.\n");
}
// NOTE: Also test this early since we can't include files from other
// libraries if this is set strictly.
$open_basedir = ini_get('open_basedir');
if ($open_basedir) {
// 'open_basedir' restricts which files we're allowed to access with
// file operations. This might be okay -- we don't need to write to
// arbitrary places in the filesystem -- but we need to access certain
// resources. This setting is unlikely to be providing any real measure
// of security so warn even if things look OK.
try {
$open_libphutil = class_exists('Future');
} catch (Exception $ex) {
$message = $ex->getMessage();
self::write("Unable to load modules from libphutil: {$message}\n");
$open_libphutil = false;
}
try {
$open_arcanist = class_exists('ArcanistDiffParser');
} catch (Exception $ex) {
$message = $ex->getMessage();
self::write("Unable to load modules from Arcanist: {$message}\n");
$open_arcanist = false;
}
$open_urandom = false;
try {
Filesystem::readRandomBytes(1);
$open_urandom = true;
} catch (FilesystemException $ex) {
self::write($ex->getMessage()."\n");
}
try {
$tmp = new TempFile();
file_put_contents($tmp, '.');
$open_tmp = @fopen((string)$tmp, 'r');
} catch (Exception $ex) {
$message = $ex->getMessage();
$dir = sys_get_temp_dir();
self::write("Unable to open temp files from '{$dir}': {$message}\n");
$open_tmp = false;
}
if (!$open_urandom || !$open_tmp || !$open_libphutil || !$open_arcanist) {
self::writeFailure();
self::write(
"Setup failure! Your server is configured with 'open_basedir' in ".
"php.ini which prevents Phabricator from opening files it needs to ".
"access. Either make the setting more permissive or remove it. It ".
"is unlikely you derive significant security benefits from having ".
"this configured; files outside this directory can still be ".
"accessed through system command execution.");
return;
} else {
self::write(
"[WARN] You have an 'open_basedir' configured in your php.ini. ".
"Although the setting seems permissive enough that Phabricator ".
"will run properly, you may run into problems because of it. It is ".
"unlikely you gain much real security benefit from having it ".
"configured, because the application can still access files outside ".
"the 'open_basedir' by running system commands.\n");
}
} else {
self::write(" okay 'open_basedir' is not set.\n");
}
if (!PhabricatorEnv::getEnvConfig('security.alternate-file-domain')) {
self::write(
"[WARN] You have not configured 'security.alternate-file-domain'. ".
"This makes your installation vulnerable to attack. Make sure you ".
"read the documentation for this parameter and understand the ".
"consequences of leaving it unconfigured.\n");
}
$path = getenv('PATH');
if (empty($path)) {
self::writeFailure();
self::write(
"Setup failure! The environmental \$PATH variable is empty. ".
"Phabricator needs to execute system commands like 'svn', 'git', ".
"'hg', and 'diff'. Set up your webserver so that it passes a valid ".
"\$PATH to the PHP process.\n\n");
if (php_sapi_name() == 'fpm-fcgi') {
self::write(
"You're running php-fpm, so the easiest way to do this is to add ".
"this line to your php-fpm.conf:\n\n".
" env[PATH] = /usr/local/bin:/usr/bin:/bin\n\n".
"Then restart php-fpm.\n");
}
return;
} else {
self::write(" okay \$PATH is nonempty.\n");
}
self::write("[OKAY] Core configuration OKAY.\n");
self::writeHeader("REQUIRED PHP EXTENSIONS");
$extensions = array(
'mysql',
'hash',
'json',
'openssl',
'mbstring',
'iconv',
// There is a chance we might not need this, but some configurations (like
// OAuth or Amazon SES) will require it. Just mark it 'required' since
// it's widely available and relatively core.
'curl',
);
foreach ($extensions as $extension) {
$ok = self::requireExtension($extension);
if (!$ok) {
self::writeFailure();
self::write("Setup failure! Install PHP extension '{$extension}'.");
return;
}
}
list($err, $stdout, $stderr) = exec_manual('which php');
if ($err) {
self::writeFailure();
self::write("Unable to locate 'php' on the command line from the web ".
"server. Verify that 'php' is in the webserver's PATH.\n".
" err: {$err}\n".
"stdout: {$stdout}\n".
"stderr: {$stderr}\n");
return;
} else {
self::write(" okay PHP binary found on the command line.\n");
$php_bin = trim($stdout);
}
// NOTE: In cPanel + suphp installs, 'php' may be the PHP CGI SAPI, not the
// PHP CLI SAPI. proc_open() will pass the environment to the child process,
// which will re-execute the webpage (causing an infinite number of
// processes to spawn). To test that the 'php' binary is safe to execute,
// we call php_sapi_name() using "env -i" to wipe the environment so it
// doesn't execute another reuqest if it's the wrong binary. We can't use
// "-r" because php-cgi doesn't support that flag.
$tmp_file = new TempFile('sapi.php');
Filesystem::writeFile($tmp_file, '<?php echo php_sapi_name();');
list($err, $stdout, $stderr) = exec_manual(
'/usr/bin/env -i %s -f %s',
$php_bin,
$tmp_file);
if ($err) {
self::writeFailure();
self::write("Unable to execute 'php' on the command line from the web ".
"server.\n".
" err: {$err}\n".
"stdout: {$stdout}\n".
"stderr: {$stderr}\n");
return;
} else {
self::write(" okay PHP is available from the command line.\n");
$sapi = trim($stdout);
if ($sapi != 'cli') {
self::writeFailure();
self::write(
"The 'php' binary on this system uses the '{$sapi}' SAPI, but the ".
"'cli' SAPI is expected. Replace 'php' with the php-cli SAPI ".
"binary, or edit your webserver configuration so the first 'php' ".
"in PATH is the 'cli' SAPI.\n\n".
"If you're running cPanel with suphp, the easiest way to fix this ".
"is to add '/usr/local/bin' before '/usr/bin' for 'env_path' in ".
"suconf.php:\n\n".
' env_path="/bin:/usr/local/bin:/usr/bin"'.
"\n\n");
return;
} else {
self::write(" okay 'php' is CLI SAPI.\n");
}
}
$root = dirname(phutil_get_library_root('phabricator'));
// On RHEL6, doing a distro install of pcntl makes it available from the
// CLI binary but not from the Apache module. This isn't entirely
// unreasonable and we don't need it from Apache, so do an explicit test
// for CLI availability.
list($err, $stdout, $stderr) = exec_manual(
'php %s',
"{$root}/scripts/setup/pcntl_available.php");
if ($err) {
self::writeFailure();
self::write("Unable to execute scripts/setup/pcntl_available.php to ".
"test for the availability of pcntl from the CLI.\n".
" err: {$err}\n".
"stdout: {$stdout}\n".
"stderr: {$stderr}\n");
return;
} else {
if (trim($stdout) == 'YES') {
self::write(" okay pcntl is available from the command line.\n");
self::write("[OKAY] All extensions OKAY\n");
} else {
self::write(" warn pcntl is not available!\n");
self::write("[WARN] *** WARNING *** pcntl extension not available. ".
"You will not be able to run daemons.\n");
}
}
self::writeHeader("GIT SUBMODULES");
if (!Filesystem::pathExists($root.'/.git')) {
self::write(" skip Not a git clone.\n\n");
} else {
list($info) = execx(
'(cd %s && git submodule status)',
$root);
foreach (explode("\n", rtrim($info)) as $line) {
$matches = null;
if (!preg_match('/^(.)([0-9a-f]{40}) (\S+)(?: |$)/', $line, $matches)) {
self::writeFailure();
self::write(
"Setup failure! 'git submodule' produced unexpected output:\n".
$line);
return;
}
$status = $matches[1];
$module = $matches[3];
switch ($status) {
case '-':
case '+':
case 'U':
self::writeFailure();
self::write(
"Setup failure! Git submodule '{$module}' is not up to date. ".
"Run:\n\n".
" cd {$root} && git submodule update --init\n\n".
"...to update submodules.");
return;
case ' ':
self::write(" okay Git submodule '{$module}' up to date.\n");
break;
default:
self::writeFailure();
self::write(
"Setup failure! 'git submodule' reported unknown status ".
"'{$status}' for submodule '{$module}'. This is a bug; report ".
"it to the Phabricator maintainers.");
return;
}
}
}
self::write("[OKAY] All submodules OKAY.\n");
self::writeHeader("BASIC CONFIGURATION");
$env = PhabricatorEnv::getEnvConfig('phabricator.env');
if ($env == 'production' || $env == 'default' || $env == 'development') {
self::writeFailure();
self::write(
"Setup failure! Your PHABRICATOR_ENV is set to '{$env}', which is ".
"a Phabricator environmental default. You should create a custom ".
"environmental configuration instead of editing the defaults ".
"directly. See this document for instructions:\n");
self::writeDoc('article/Configuration_Guide.html');
return;
} else {
$host = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
$host_uri = new PhutilURI($host);
$protocol = $host_uri->getProtocol();
$allowed_protocols = array(
'http' => true,
'https' => true,
);
if (empty($allowed_protocols[$protocol])) {
self::writeFailure();
self::write(
"You must specify the protocol over which your host works (e.g.: ".
"\"http:// or https://\")\nin your custom config file.\nRefer to ".
"'default.conf.php' for documentation on configuration options.\n");
return;
}
if (preg_match('/.*\/$/', $host)) {
self::write(" okay phabricator.base-uri protocol\n");
} else {
self::writeFailure();
self::write(
"You must add a trailing slash at the end of the host\n(e.g.: ".
"\"http://phabricator.example.com/ instead of ".
"http://phabricator.example.com\")\nin your custom config file.".
"\nRefer to 'default.conf.php' for documentation on configuration ".
"options.\n");
return;
}
$host_domain = $host_uri->getDomain();
if (strpos($host_domain, '.') !== false) {
self::write(" okay phabricator.base-uri domain\n");
} else {
self::writeFailure();
self::write(
"You must host Phabricator on a domain that contains a dot ('.'). ".
"The current domain, '{$host_domain}', does not have a dot, so some ".
"browsers will not set cookies on it. For instance, ".
"'http://example.com/ is OK, but 'http://example/' won't work. ".
"If you are using localhost, create an entry in the hosts file like ".
"'127.0.0.1 example.com', and access the localhost with ".
"'http://example.com/'.");
return;
}
$host_path = $host_uri->getPath();
if ($host_path == '/') {
self::write(" okay phabricator.base-uri path\n");
} else {
self::writeFailure();
self::write(
"Your 'phabricator.base-uri' setting includes a path, but should ".
"not (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 alongside other applications. Consult the documentation ".
"for more details.");
return;
}
}
- $timezone = nonempty(
- PhabricatorEnv::getEnvConfig('phabricator.timezone'),
- ini_get('date.timezone'));
- if (!$timezone) {
- self::writeFailure();
- self::write(
- "Setup failure! Your configuration fails to specify a server ".
- "timezone. Either set 'date.timezone' in your php.ini or ".
- "'phabricator.timezone' in your Phabricator configuration. See the ".
- "PHP documentation for a list of supported timezones:\n\n".
- "http://www.php.net/manual/en/timezones.php\n");
- return;
- } else {
- self::write(" okay Timezone '{$timezone}' configured.\n");
- }
-
self::write("[OKAY] Basic configuration OKAY\n");
$issue_gd_warning = false;
self::writeHeader('GD LIBRARY');
if (extension_loaded('gd')) {
self::write(" okay Extension 'gd' is loaded.\n");
$image_type_map = array(
'imagepng' => 'PNG',
'imagegif' => 'GIF',
'imagejpeg' => 'JPEG',
);
foreach ($image_type_map as $function => $image_type) {
if (function_exists($function)) {
self::write(" okay Support for '{$image_type}' is available.\n");
} else {
self::write(" warn Support for '{$image_type}' is not available!\n");
$issue_gd_warning = true;
}
}
} else {
self::write(" warn Extension 'gd' is not loaded.\n");
$issue_gd_warning = true;
}
if ($issue_gd_warning) {
self::write(
"[WARN] The 'gd' library is missing or lacks full support. ".
"Phabricator will not be able to generate image thumbnails without ".
"gd.\n");
} else {
self::write("[OKAY] 'gd' loaded and has full image type support.\n");
}
self::writeHeader('FACEBOOK INTEGRATION');
$fb_auth = PhabricatorEnv::getEnvConfig('facebook.auth-enabled');
if (!$fb_auth) {
self::write(" skip 'facebook.auth-enabled' not enabled.\n");
} else {
self::write(" okay 'facebook.auth-enabled' is enabled.\n");
$app_id = PhabricatorEnv::getEnvConfig('facebook.application-id');
$app_secret = PhabricatorEnv::getEnvConfig('facebook.application-secret');
if (!$app_id) {
self::writeFailure();
self::write(
"Setup failure! 'facebook.auth-enabled' is true but there is no ".
"setting for 'facebook.application-id'.\n");
return;
} else {
self::write(" okay 'facebook.application-id' is set.\n");
}
if (!is_string($app_id)) {
self::writeFailure();
self::write(
"Setup failure! 'facebook.application-id' should be a string.");
return;
} else {
self::write(" okay 'facebook.application-id' is string.\n");
}
if (!$app_secret) {
self::writeFailure();
self::write(
"Setup failure! 'facebook.auth-enabled' is true but there is no ".
"setting for 'facebook.application-secret'.");
return;
} else {
self::write(" okay 'facebook.application-secret is set.\n");
}
self::write("[OKAY] Facebook integration OKAY\n");
}
self::writeHeader("MySQL DATABASE & STORAGE CONFIGURATION");
$conf = PhabricatorEnv::newObjectFromConfig('mysql.configuration-provider');
$conn_user = $conf->getUser();
$conn_pass = $conf->getPassword();
$conn_host = $conf->getHost();
$timeout = ini_get('mysql.connect_timeout');
if ($timeout > 5) {
self::writeNote(
"Your MySQL connect timeout is very high ({$timeout} seconds). ".
"Consider reducing it to 5 or below by setting ".
"'mysql.connect_timeout' in your php.ini.");
}
self::write(" okay Trying to connect to MySQL database ".
"{$conn_user}@{$conn_host}...\n");
ini_set('mysql.connect_timeout', 2);
$conn_raw = PhabricatorEnv::newObjectFromConfig(
'mysql.implementation',
array(
array(
'user' => $conn_user,
'pass' => $conn_pass,
'host' => $conn_host,
'database' => null,
),
));
try {
queryfx($conn_raw, 'SELECT 1');
self::write(" okay Connection successful!\n");
} catch (AphrontQueryConnectionException $ex) {
$message = $ex->getMessage();
self::writeFailure();
self::write(
"Setup failure! MySQL exception: {$message} \n".
"Edit Phabricator configuration keys 'mysql.user', ".
"'mysql.host' and 'mysql.pass' to enable Phabricator ".
"to connect.");
return;
}
$engines = queryfx_all($conn_raw, 'SHOW ENGINES');
$engines = ipull($engines, 'Support', 'Engine');
$innodb = idx($engines, 'InnoDB');
if ($innodb != 'YES' && $innodb != 'DEFAULT') {
self::writeFailure();
self::write(
"Setup failure! The 'InnoDB' engine is not available. Enable ".
"InnoDB in your MySQL configuration. If you already created tables, ".
"MySQL incorrectly used some other engine. You need to convert ".
"them or drop and reinitialize them.");
return;
} else {
self::write(" okay InnoDB is available.\n");
}
$namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
$databases = queryfx_all($conn_raw, 'SHOW DATABASES');
$databases = ipull($databases, 'Database', 'Database');
if (empty($databases[$namespace.'_meta_data'])) {
self::writeFailure();
self::write(
"Setup failure! You haven't run 'bin/storage upgrade'. See this ".
"article for instructions:\n");
self::writeDoc('article/Configuration_Guide.html');
return;
} else {
self::write(" okay Databases have been initialized.\n");
}
$index_min_length = queryfx_one(
$conn_raw,
'SHOW VARIABLES LIKE %s',
'ft_min_word_len');
$index_min_length = idx($index_min_length, 'Value', 4);
if ($index_min_length >= 4) {
self::writeNote(
"MySQL is configured with a 'ft_min_word_len' of 4 or greater, which ".
"means you will not be able to search for 3-letter terms. Consider ".
"setting this in your configuration:\n".
"\n".
" [mysqld]\n".
" ft_min_word_len=3\n".
"\n".
"Then optionally run:\n".
"\n".
" REPAIR TABLE {$namespace}_search.search_documentfield QUICK;\n".
"\n".
"...to reindex existing documents.");
}
$max_allowed_packet = queryfx_one(
$conn_raw,
'SHOW VARIABLES LIKE %s',
'max_allowed_packet');
$max_allowed_packet = idx($max_allowed_packet, 'Value', PHP_INT_MAX);
$recommended_minimum = 1024 * 1024;
if ($max_allowed_packet < $recommended_minimum) {
self::writeNote(
"MySQL is configured with a small 'max_allowed_packet' ".
"('{$max_allowed_packet}'), which may cause some large writes to ".
"fail. Consider raising this to at least {$recommended_minimum}.");
} else {
self::write(" okay max_allowed_packet = {$max_allowed_packet}.\n");
}
$local_key = 'storage.local-disk.path';
$local_path = PhabricatorEnv::getEnvConfig($local_key);
if ($local_path) {
if (!Filesystem::pathExists($local_path) ||
!is_readable($local_path) ||
!is_writable($local_path)) {
self::writeFailure();
self::write(
"Setup failure! You have configured local disk storage but the ".
"path you specified ('{$local_path}') does not exist or is not ".
"readable or writable.\n");
if ($open_basedir) {
self::write(
"You have an 'open_basedir' setting -- make sure Phabricator is ".
"allowed to open files in the local storage directory.\n");
}
return;
} else {
self::write(" okay Local disk storage exists and is writable.\n");
}
} else {
self::write(" skip Not configured for local disk storage.\n");
}
- $selector = PhabricatorEnv::getEnvConfig('storage.engine-selector');
-
- try {
- $storage_selector_exists = class_exists($selector);
- } catch (Exception $ex) {
- $storage_selector_exists = false;
- }
-
- if ($storage_selector_exists) {
- self::write(" okay Using '{$selector}' as a storage engine selector.\n");
- } else {
- self::writeFailure();
- self::write(
- "Setup failure! You have configured '{$selector}' as a storage engine ".
- "selector but it does not exist or could not be loaded.\n");
- return;
- }
-
self::write("[OKAY] Database and storage configuration OKAY\n");
self::writeHeader('SUCCESS!');
self::write(
"Congratulations! Your setup seems mostly correct, or at least fairly ".
"reasonable.\n\n".
"*** NEXT STEP ***\n".
"Edit your configuration file (conf/{$env}.conf.php) and remove the ".
"'phabricator.setup' line to finish installation.");
}
public static function requireExtension($extension) {
if (extension_loaded($extension)) {
self::write(" okay Extension '{$extension}' installed.\n");
return true;
} else {
self::write("[FAIL] Extension '{$extension}' is NOT INSTALLED!\n");
return false;
}
}
private static function writeFailure() {
self::write("\n\n<<< *** FAILURE! *** >>>\n");
}
private static function write($str) {
echo $str;
ob_flush();
flush();
// This, uh, makes it look cool. -_-
usleep(20000);
}
private static function writeNote($note) {
$note = "*** NOTE: ".wordwrap($note, 75, "\n", true);
$note = "\n".str_replace("\n", "\n ", $note)."\n\n";
self::write($note);
}
public static function writeHeader($header) {
$template = '>>>'.str_repeat('-', 77);
$template = substr_replace(
$template,
' '.$header.' ',
3,
strlen($header) + 4);
self::write("\n\n{$template}\n\n");
}
public static function writeDoc($doc) {
self::write(
"\n".
' http://www.phabricator.com/docs/phabricator/'.$doc.
"\n\n");
}
}
diff --git a/src/infrastructure/env/PhabricatorEnv.php b/src/infrastructure/env/PhabricatorEnv.php
index 79c6b40c49..627b70bcbc 100644
--- a/src/infrastructure/env/PhabricatorEnv.php
+++ b/src/infrastructure/env/PhabricatorEnv.php
@@ -1,459 +1,465 @@
<?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 {
private static $sourceStack;
private static $repairSource;
/**
* @phutil-external-symbol class PhabricatorStartup
*/
public static function initializeWebEnvironment() {
$env = self::getSelectedEnvironmentName();
if (!$env) {
PhabricatorStartup::didFatal(
"The 'PHABRICATOR_ENV' environmental variable is not defined. Modify ".
"your httpd.conf to include 'SetEnv PHABRICATOR_ENV <env>', where ".
"'<env>' is one of 'development', 'production', or a custom ".
"environment.");
}
self::initializeCommonEnvironment();
}
public static function initializeScriptEnvironment() {
$env = self::getSelectedEnvironmentName();
if (!$env) {
echo phutil_console_wrap(
phutil_console_format(
"**ERROR**: PHABRICATOR_ENV Not Set\n\n".
"Define the __PHABRICATOR_ENV__ environment variable before ".
"running this script. You can do it on the command line like ".
"this:\n\n".
" $ PHABRICATOR_ENV=__custom/myconfig__ %s ...\n\n".
"Replace __custom/myconfig__ with the path to your configuration ".
"file. For more information, see the 'Configuration Guide' in the ".
"Phabricator documentation.\n\n",
$GLOBALS['argv'][0]));
exit(1);
}
self::initializeCommonEnvironment();
// 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() {
PhutilErrorHandler::initialize();
self::buildConfigurationSourceStack();
+ // Force a valid timezone. If both PHP and Phabricator configuration are
+ // invalid, use UTC.
$tz = PhabricatorEnv::getEnvConfig('phabricator.timezone');
if ($tz) {
- date_default_timezone_set($tz);
+ @date_default_timezone_set($tz);
+ }
+ $ok = @date_default_timezone_set(date_default_timezone_get());
+ if (!$ok) {
+ date_default_timezone_set('UTC');
}
// Append any paths to $PATH if we need to.
$paths = PhabricatorEnv::getEnvConfig('environment.append-paths');
if (!empty($paths)) {
$current_env_path = getenv('PATH');
$new_env_paths = implode(PATH_SEPARATOR, $paths);
putenv('PATH='.$current_env_path.PATH_SEPARATOR.$new_env_paths);
}
PhabricatorEventEngine::initialize();
$translation = PhabricatorEnv::newObjectFromConfig('translation.provider');
PhutilTranslator::getInstance()
->setLanguage($translation->getLanguage())
->addTranslations($translation->getTranslations());
}
private static function buildConfigurationSourceStack() {
$stack = new PhabricatorConfigStackSource();
self::$sourceStack = $stack;
$defaultSource = id(new PhabricatorConfigDefaultSource())
->setName(pht('Global Default'));
$stack->pushSource($defaultSource);
$env = self::getSelectedEnvironmentName();
$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 (PhabricatorEnv::getEnvConfig('load-libraries') as $library) {
phutil_load_library($library);
}
// 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.
$defaultSource->loadExternalOptions();
try {
$stack->pushSource(
id(new PhabricatorConfigDatabaseSource('default'))
->setName(pht("Database")));
} catch (AphrontQueryException $exception) {
// If the database is not available, just skip this configuration
// source. This happens during `bin/storage upgrade`, `bin/conf` before
// schema setup, etc.
}
}
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));
}
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.
*
* @task read
*/
public static function getEnvConfig($key, $default = null) {
$result = self::$sourceStack->getKeys(array($key));
return idx($result, $key, $default);
}
/**
* Get the fully-qualified URI for a path.
*
* @task read
*/
public static function getURI($path) {
return rtrim(self::getEnvConfig('phabricator.base-uri'), '/').$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::getEnvConfig('phabricator.base-uri');
}
return rtrim($production_domain, '/').$path;
}
/**
* 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::getEnvConfig('phabricator.base-uri');
}
$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) {
return 'http://www.phabricator.com/docs/phabricator/'.$resource;
}
/**
* 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);
}
/* -( Unit Test Support )-------------------------------------------------- */
/**
* @task test
*/
public static function beginScopedEnv() {
return new PhabricatorScopedEnv(self::pushTestEnvironment());
}
/**
* @task test
*/
private static function pushTestEnvironment() {
$source = new PhabricatorConfigDictionarySource(array());
self::$sourceStack->pushSource($source);
return spl_object_hash($source);
}
/**
* @task test
*/
public static function popTestEnvironment($key) {
$source = self::$sourceStack->popSource();
$stack_key = spl_object_hash($source);
if ($stack_key !== $key) {
self::$sourceStack->pushSource($source);
throw new Exception(
"Scoped environments were destroyed in a diffent order than they ".
"were initialized.");
}
}
/* -( URI Validation )----------------------------------------------------- */
/**
* Detect if a URI satisfies either @{method:isValidLocalWebResource} or
* @{method:isValidRemoteWebResource}, 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 isValidWebResource($uri) {
return self::isValidLocalWebResource($uri) ||
self::isValidRemoteWebResource($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 isValidLocalWebResource($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;
}
// 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 remote resource.
*
* @param string URI to test.
* @return bool True if a URI idenfies a remote resource with an allowed
* protocol.
* @task uri
*/
public static function isValidRemoteWebResource($uri) {
$uri = (string)$uri;
$proto = id(new PhutilURI($uri))->getProtocol();
if (!$proto) {
return false;
}
$allowed = self::getEnvConfig('uri.allowed-protocols');
if (empty($allowed[$proto])) {
return false;
}
return true;
}
/* -( 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);
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 16:52 (2 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1126662
Default Alt Text
(40 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment