diff --git a/src/applications/config/check/PhabricatorAPCSetupCheck.php b/src/applications/config/check/PhabricatorAPCSetupCheck.php
index e6efa7d76d..62925bbf90 100644
--- a/src/applications/config/check/PhabricatorAPCSetupCheck.php
+++ b/src/applications/config/check/PhabricatorAPCSetupCheck.php
@@ -1,84 +1,88 @@
newIssue('extension.apc')
->setShortName(pht('APC'))
->setName(pht("PHP Extension 'APC' Not Installed"))
->setMessage($message)
->addPHPExtension('apc');
return;
}
if (!ini_get('apc.enabled')) {
$summary = pht('Enabling APC will dramatically improve performance.');
$message = pht(
"The PHP extension 'APC' is installed, but not enabled in your PHP ".
"configuration. Enabling it will dramatically improve Phabricator ".
"performance. Edit the 'apc.enabled' setting to enable the extension.");
$this
->newIssue('extension.apc.enabled')
->setShortName(pht('APC Disabled'))
->setName(pht("PHP Extension 'APC' Not Enabled"))
->setSummary($summary)
->setMessage($message)
->addPHPConfig('apc.enabled');
return;
}
$is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode');
$is_stat_enabled = ini_get('apc.stat');
$issue_key = null;
if ($is_stat_enabled && !$is_dev) {
$issue_key = 'extension.apc.stat-enabled';
$short = pht("'apc.stat' Enabled");
$long = pht("'apc.stat' Enabled in Production");
$summary = pht(
"'apc.stat' is currently enabled, but should probably be disabled.");
$message = pht(
"'apc.stat' is currently enabled in your PHP configuration. For most ".
"Phabricator installs, 'apc.stat' should be disabled. This will ".
"slightly improve performance (PHP will do fewer disk reads) and make ".
"updates safer (PHP won't read in the middle of a 'git pull').\n\n".
"(If you are developing for Phabricator, leave 'apc.stat' enabled but ".
"enable 'phabricator.developer-mode'.)");
} else if (!$is_stat_enabled && $is_dev) {
$issue_key = 'extension.apc.stat-disabled';
$short = pht("'apc.stat' Disabled");
$long = pht("'apc.stat' Disabled in Development");
$summary = pht(
"'apc.stat' is currently disabled, but should probably be enabled ".
"in development mode.");
$message = pht(
"'apc.stat' is disabled in your PHP configuration, but Phabricator is ".
"set to developer mode. Normally, you should enable 'apc.stat' for ".
"development installs so you don't need to restart your webserver ".
"after making changes to the code.\n\n".
"You can enable 'apc.stat', or disable 'phabricator.developer-mode', ".
"or safely ignore this warning if you have some reasoning behind ".
"your current configuration.");
}
if ($issue_key !== null) {
$this
->newIssue($issue_key)
->setShortName($short)
->setName($long)
->setSummary($summary)
->setMessage($message)
->addPHPConfig('apc.stat')
->addPhabricatorConfig('phabricator.developer-mode');
}
}
}
diff --git a/src/applications/config/check/PhabricatorAuthSetupCheck.php b/src/applications/config/check/PhabricatorAuthSetupCheck.php
index 82cae4d962..5d4f731867 100644
--- a/src/applications/config/check/PhabricatorAuthSetupCheck.php
+++ b/src/applications/config/check/PhabricatorAuthSetupCheck.php
@@ -1,41 +1,45 @@
setViewer(PhabricatorUser::getOmnipotentUser())
->execute();
if (!$configs) {
$message = pht(
'You have not configured any authentication providers yet. You '.
'should add a provider (like username/password, LDAP, or GitHub '.
'OAuth) so users can register and log in. You can add and configure '.
'providers %s.',
phutil_tag(
'a',
array(
'href' => '/auth/',
),
pht('using the "Auth" application')));
$this
->newIssue('auth.noproviders')
->setShortName(pht('No Auth Providers'))
->setName(pht('No Authentication Providers Configured'))
->setMessage($message);
}
}
}
diff --git a/src/applications/config/check/PhabricatorBaseURISetupCheck.php b/src/applications/config/check/PhabricatorBaseURISetupCheck.php
index ccbadf7bbb..ae22476fdf 100644
--- a/src/applications/config/check/PhabricatorBaseURISetupCheck.php
+++ b/src/applications/config/check/PhabricatorBaseURISetupCheck.php
@@ -1,67 +1,71 @@
newIssue('config.phabricator.domain')
->setShortName(pht('Dotless Domain'))
->setName(pht('No Dot Character in Domain'))
->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/".'.
"\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(
'phabricator/ $ %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 3df43e8546..a63c6b5500 100644
--- a/src/applications/config/check/PhabricatorBinariesSetupCheck.php
+++ b/src/applications/config/check/PhabricatorBinariesSetupCheck.php
@@ -1,280 +1,283 @@
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 'diff', Phabricator will not be able to generate or render ".
"diffs in multiple applications.");
$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 'diff' Behavior"))
->setMessage(
pht(
"The 'diff' 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.",
$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 'diff' binary on this system has unexpected behavior: ".
"it was expected to exit with a nonzero error code when passed ".
"differing files, but did not."));
}
}
$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);
}
$version = null;
switch ($binary) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$minimum_version = null;
$bad_versions = array();
list($err, $stdout, $stderr) = exec_manual('git --version');
$version = trim(substr($stdout, strlen('git version ')));
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$minimum_version = '1.5';
$bad_versions = array(
'1.7.1' => pht('This version of Subversion has a bug where '.
'"svn diff -c N" does not work for files added '.
'in rN (Subversion issue #2873), fixed in 1.7.2.'),);
list($err, $stdout, $stderr) = exec_manual('svn --version --quiet');
$version = trim($stdout);
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$minimum_version = '1.9';
$bad_versions = array(
'2.1' => pht('This version of Mercurial returns a bad exit code '.
'after a successful pull.'),
'2.2' => pht('This version of Mercurial has a significant memory '.
'leak, fixed in 2.2.1. Pushing fails with this '.
'version as well; see T3046#54922.'),);
list($err, $stdout, $stderr) = exec_manual('hg --version --quiet');
// NOTE: At least on OSX, recent versions of Mercurial report this
// string in this format:
//
// Mercurial Distributed SCM (version 3.1.1+20140916)
$matches = null;
$pattern = '/^Mercurial Distributed SCM \(version ([\d.]+)/m';
if (preg_match($pattern, $stdout, $matches)) {
$version = $matches[1];
}
break;
}
if ($version === null) {
$this->raiseUnknownVersionWarning($binary);
} else {
if ($minimum_version &&
version_compare($version, $minimum_version, '<')) {
$this->raiseMinimumVersionWarning(
$binary,
$minimum_version,
$version);
}
foreach ($bad_versions as $bad_version => $details) {
if ($bad_version === $version) {
$this->raiseBadVersionWarning(
$binary,
$bad_version);
}
}
}
}
}
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.",
$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.",
$bin,
'phabricator/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.'.
"\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 '.
'newer version string.'.
"\n\n".
'If updating Phabricator 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 raiseMinimumVersionWarning(
$binary,
$minimum_version,
$version) {
switch ($binary) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$summary = pht(
"The '%s' binary is version %s and Phabricator requires version ".
"%s or higher.",
$binary,
$version,
$minimum_version);
$message = pht(
"Please upgrade the '%s' binary to a more modern version.",
$binary);
$this->newIssue('bin.'.$binary)
->setShortName(pht("Unsupported '%s' Version", $binary))
->setName(pht("Unsupported '%s' Version", $binary))
->setSummary($summary)
->setMessage($summary.' '.$message);
break;
}
}
private function raiseBadVersionWarning($binary, $bad_version) {
switch ($binary) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$summary = pht(
"The '%s' binary is version %s which has bugs that break ".
"Phabricator.",
$binary,
$bad_version);
$message = pht(
"Please upgrade the '%s' binary to a more modern version.",
$binary);
$this->newIssue('bin.'.$binary)
->setShortName(pht("Unsupported '%s' Version", $binary))
->setName(pht("Unsupported '%s' Version", $binary))
->setSummary($summary)
->setMessage($summary.' '.$message);
break;
}
}
}
diff --git a/src/applications/config/check/PhabricatorDaemonsSetupCheck.php b/src/applications/config/check/PhabricatorDaemonsSetupCheck.php
index d7c93dc03b..52f4fe2f37 100644
--- a/src/applications/config/check/PhabricatorDaemonsSetupCheck.php
+++ b/src/applications/config/check/PhabricatorDaemonsSetupCheck.php
@@ -1,190 +1,194 @@
setViewer(PhabricatorUser::getOmnipotentUser())
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
->withDaemonClasses(array('PhabricatorTaskmasterDaemon'))
->setLimit(1)
->execute();
if (!$task_daemon) {
$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.');
$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).'.
"\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'))
->setSummary($summary)
->setMessage($message)
->addCommand('phabricator/ $ ./bin/phd start');
}
$phd_user = PhabricatorEnv::getEnvConfig('phd.user');
$environment_hash = PhabricatorEnv::calculateEnvironmentHash();
$all_daemons = id(new PhabricatorDaemonLogQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
->execute();
foreach ($all_daemons as $daemon) {
if ($phd_user) {
if ($daemon->getRunningAsUser() != $phd_user) {
$doc_href = PhabricatorEnv::getDocLink(
'Managing Daemons with phd');
$summary = pht(
'At least one daemon is currently running as a different '.
'user than configured in the Phabricator phd.user setting');
$message = pht(
'A daemon is running as user %s while the Phabricator config '.
'specifies phd.user to be %s.'.
"\n\n".
'Either adjust phd.user to match %s or start '.
'the daemons as the correct user. '.
"\n\n".
'phd Daemons will try to '.
'use sudo to start as the configured user. '.
'Make sure that the user who starts phd has the correct '.
'sudo permissions to start phd daemons as %s',
phutil_tag('tt', array(), $daemon->getRunningAsUser()),
phutil_tag('tt', array(), $phd_user),
phutil_tag('tt', array(), $daemon->getRunningAsUser()),
phutil_tag('tt', array(), $phd_user));
$this->newIssue('daemons.run-as-different-user')
->setName(pht('Daemons are running as the wrong user'))
->setSummary($summary)
->setMessage($message)
->addCommand('phabricator/ $ ./bin/phd restart');
}
}
if ($daemon->getEnvHash() != $environment_hash) {
$doc_href = PhabricatorEnv::getDocLink(
'Managing Daemons with phd');
$summary = pht(
'At least one daemon is currently running with different '.
'configuration than the Phabricator web application.');
$list_section = null;
$env_info = $daemon->getEnvInfo();
if ($env_info) {
$issues = PhabricatorEnv::compareEnvironmentInfo(
PhabricatorEnv::calculateEnvironmentInfo(),
$env_info);
if ($issues) {
foreach ($issues as $key => $issue) {
$issues[$key] = phutil_tag('li', array(), $issue);
}
$list_section = array(
pht(
'The configurations differ in the following %s way(s):',
new PhutilNumber(count($issues))),
phutil_tag(
'ul',
array(),
$issues),
);
}
}
$message = pht(
'At least one daemon is currently running with a different '.
'configuration (config checksum %s) than the web application '.
'(config checksum %s).'.
"\n\n%s".
'This usually means that you have just made a configuration change '.
'from the web UI, but have not yet restarted the daemons. You '.
'need to restart the daemons after making configuration changes '.
'so they will pick up the new values: until you do, they will '.
'continue operating with the old settings.'.
"\n\n".
'(If you plan to make more changes, you can restart the daemons '.
'once after you finish making all of your changes.)'.
"\n\n".
'Use %s to restart daemons. You can find a list of running daemons '.
'in the %s, which will also help you identify which daemon (or '.
'daemons) have divergent configuration. For more information about '.
'managing the daemons, see %s in the documentation.'.
"\n\n".
'This can also happen if you use the %s environmental variable to '.
'choose a configuration file, but the daemons run with a different '.
'value than the web application. If restarting the daemons does '.
'not resolve this issue and you use %s to select configuration, '.
'check that it is set consistently.'.
"\n\n".
'A third possible cause is that you run several machines, and '.
'the %s configuration file differs between them. This file is '.
'updated when you edit configuration from the CLI with %s. If '.
'restarting the daemons does not resolve this issue and you '.
'run multiple machines, check that all machines have identical '.
'%s configuration files.'.
"\n\n".
'This issue is not severe, but usually indicates that something '.
'is not configured the way you expect, and may cause the daemons '.
'to exhibit different behavior than the web application does.',
phutil_tag('tt', array(), substr($daemon->getEnvHash(), 0, 12)),
phutil_tag('tt', array(), substr($environment_hash, 0, 12)),
$list_section,
phutil_tag('tt', array(), 'bin/phd restart'),
phutil_tag(
'a',
array(
'href' => '/daemon/',
'target' => '_blank',
),
pht('Daemon Console')),
phutil_tag(
'a',
array(
'href' => $doc_href,
'target' => '_blank',
),
pht('Managing Daemons with phd')),
phutil_tag('tt', array(), 'PHABRICATOR_ENV'),
phutil_tag('tt', array(), 'PHABRICATOR_ENV'),
phutil_tag('tt', array(), 'phabricator/conf/local/local.json'),
phutil_tag('tt', array(), 'bin/config'),
phutil_tag('tt', array(), 'phabricator/conf/local/local.json'));
$this->newIssue('daemons.need-restarting')
->setName(pht('Daemons and Web Have Different Config'))
->setSummary($summary)
->setMessage($message)
->addCommand('phabricator/ $ ./bin/phd restart');
break;
}
}
}
}
diff --git a/src/applications/config/check/PhabricatorDatabaseSetupCheck.php b/src/applications/config/check/PhabricatorDatabaseSetupCheck.php
index 16b8764294..3f7af1bdb4 100644
--- a/src/applications/config/check/PhabricatorDatabaseSetupCheck.php
+++ b/src/applications/config/check/PhabricatorDatabaseSetupCheck.php
@@ -1,142 +1,146 @@
getUser();
$conn_pass = $conf->getPassword();
$conn_host = $conf->getHost();
$conn_port = $conf->getPort();
ini_set('mysql.connect_timeout', 2);
$config = array(
'user' => $conn_user,
'pass' => $conn_pass,
'host' => $conn_host,
'port' => $conn_port,
'database' => null,
);
$conn_raw = PhabricatorEnv::newObjectFromConfig(
'mysql.implementation',
array($config));
try {
queryfx($conn_raw, 'SELECT 1');
} catch (AphrontConnectionQueryException $ex) {
$message = pht(
"Unable to connect to MySQL!\n\n".
"%s\n\n".
"Make sure Phabricator and MySQL are correctly configured.",
$ex->getMessage());
$this->newIssue('mysql.connect')
->setName(pht('Can Not Connect to MySQL'))
->setMessage($message)
->setIsFatal(true)
->addRelatedPhabricatorConfig('mysql.host')
->addRelatedPhabricatorConfig('mysql.port')
->addRelatedPhabricatorConfig('mysql.user')
->addRelatedPhabricatorConfig('mysql.pass');
return;
}
$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. 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.)");
$this->newIssue('mysql.innodb')
->setName(pht('MySQL InnoDB Engine Not Available'))
->setMessage($message)
->setIsFatal(true);
return;
}
$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 Phabricator\'s database '.
'schema.');
$this->newIssue('storage.upgrade')
->setName(pht('Setup MySQL Schema'))
->setMessage($message)
->setIsFatal(true)
->addCommand(hsprintf('phabricator/ $ ./bin/storage upgrade'));
} else {
$config['database'] = $namespace.'_meta_data';
$conn_meta = PhabricatorEnv::newObjectFromConfig(
'mysql.implementation',
array($config));
$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) {
$this->newIssue('storage.patch')
->setName(pht('Upgrade MySQL Schema'))
->setMessage(pht(
"Run the storage upgrade script to upgrade Phabricator's database ".
"schema. Missing patches:
%s
",
phutil_implode_html(phutil_tag('br'), array_keys($diff))))
->addCommand(
hsprintf('phabricator/ $ ./bin/storage upgrade'));
}
}
$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 `mysql.host` to `mysql.port` in your '.
'config.'))
->setMessage(
pht(
'Your `mysql.host` configuration contains a port number, but '.
'this usage is deprecated. Instead, put the port number in '.
'`mysql.port`.'))
->addPhabricatorConfig('mysql.host')
->addPhabricatorConfig('mysql.port')
->addCommand(
hsprintf(
'phabricator/ $ ./bin/config set mysql.host %s',
$host))
->addCommand(
hsprintf(
'phabricator/ $ ./bin/config set mysql.port %s',
$port));
}
}
}
diff --git a/src/applications/config/check/PhabricatorElasticSetupCheck.php b/src/applications/config/check/PhabricatorElasticSetupCheck.php
index e1ee4f4dc0..7b60444a88 100644
--- a/src/applications/config/check/PhabricatorElasticSetupCheck.php
+++ b/src/applications/config/check/PhabricatorElasticSetupCheck.php
@@ -1,39 +1,43 @@
newEngine();
if (!$engine->indexExists()) {
$summary = pht(
'You enabled Elasticsearch but the index does not exist.');
$message = pht(
'You likely enabled search.elastic.host without creating the '.
'index. Run `./bin/search init` to correct the index.');
$this
->newIssue('elastic.missing-index')
->setName(pht('Elasticsearch index Not Found'))
->setSummary($summary)
->setMessage($message)
->addRelatedPhabricatorConfig('search.elastic.host');
} else if (!$engine->indexIsSane()) {
$summary = pht(
'Elasticsearch index exists but needs correction.');
$message = pht(
'Either the Phabricator schema for Elasticsearch has changed '.
'or Elasticsearch created the index automatically. Run '.
'`./bin/search init` to correct the index.');
$this
->newIssue('elastic.broken-index')
->setName(pht('Elasticsearch index Incorrect'))
->setSummary($summary)
->setMessage($message);
}
}
}
}
diff --git a/src/applications/config/check/PhabricatorExtensionsSetupCheck.php b/src/applications/config/check/PhabricatorExtensionsSetupCheck.php
index 8d2de44d19..fa723cc7da 100644
--- a/src/applications/config/check/PhabricatorExtensionsSetupCheck.php
+++ b/src/applications/config/check/PhabricatorExtensionsSetupCheck.php
@@ -1,53 +1,57 @@
newIssue('php.extensions')
->setIsFatal(true)
->setName(pht('Missing Required Extensions'))
->setMessage($message);
foreach ($need as $extension) {
$issue->addPHPExtension($extension);
}
}
}
diff --git a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php
index 0b3e427953..c6f5b8b694 100644
--- a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php
+++ b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php
@@ -1,206 +1,210 @@
newIssue('config.unknown.'.$key)
->setShortName($short)
->setName($name)
->setSummary($summary);
$stack = PhabricatorEnv::getConfigSourceStack();
$stack = $stack->getStack();
$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);
$issue->addCommand($command);
}
if ($found_database) {
$issue->addPhabricatorConfig($key);
}
}
}
/**
* 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 '.
'PhabricatorRemarkupCustomInlineRule or '.
'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');
$ancient_config += array(
'phid.external-loaders' =>
pht(
'External loaders have been replaced. Extend `PhabricatorPHIDType` '.
'to implement new PHID and handle types.'),
'maniphest.custom-task-extensions-class' =>
pht(
'Maniphest fields are now loaded automatically. You can configure '.
'them with `maniphest.fields`.'),
'maniphest.custom-fields' =>
pht(
'Maniphest fields are now defined in '.
'`maniphest.custom-field-definitions`. Existing definitions have '.
'been migrated.'),
'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 '.
'`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.'),
'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 `phabricator.show-prototypes` '.
'to emphasize the unfinished nature of many prototype applications. '.
'Your existing setting has been migrated.'),
'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.'),
);
return $ancient_config;
}
}
diff --git a/src/applications/config/check/PhabricatorFileinfoSetupCheck.php b/src/applications/config/check/PhabricatorFileinfoSetupCheck.php
index 4bbfeed4c0..4f5013c033 100644
--- a/src/applications/config/check/PhabricatorFileinfoSetupCheck.php
+++ b/src/applications/config/check/PhabricatorFileinfoSetupCheck.php
@@ -1,17 +1,21 @@
newIssue('extension.fileinfo')
->setName(pht("Missing 'fileinfo' Extension"))
->setMessage($message);
}
}
}
diff --git a/src/applications/config/check/PhabricatorGDSetupCheck.php b/src/applications/config/check/PhabricatorGDSetupCheck.php
index 556c38e2e3..f738715bce 100644
--- a/src/applications/config/check/PhabricatorGDSetupCheck.php
+++ b/src/applications/config/check/PhabricatorGDSetupCheck.php
@@ -1,48 +1,52 @@
newIssue('extension.gd')
->setName(pht("Missing 'gd' Extension"))
->setMessage($message);
} 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 'gd' extension has support for only some image types. ".
"Phabricator will be unable to process images of the missing ".
"types until you build 'gd' with support for them. ".
"Supported types: %s. Missing types: %s.",
$have,
$missing);
$this->newIssue('extension.gd.support')
->setName(pht("Partial 'gd' Support"))
->setMessage($message);
}
}
}
}
diff --git a/src/applications/config/check/PhabricatorImagemagickSetupCheck.php b/src/applications/config/check/PhabricatorImagemagickSetupCheck.php
index 0f0ddfc553..55c538f3a2 100644
--- a/src/applications/config/check/PhabricatorImagemagickSetupCheck.php
+++ b/src/applications/config/check/PhabricatorImagemagickSetupCheck.php
@@ -1,23 +1,27 @@
newIssue('files.enable-imagemagick')
->setName(pht(
"'convert' binary not found or Imagemagick is not installed."))
->setMessage($message)
->addRelatedPhabricatorConfig('files.enable-imagemagick')
->addPhabricatorConfig('environment.append-paths');
}
}
}
}
diff --git a/src/applications/config/check/PhabricatorInvalidConfigSetupCheck.php b/src/applications/config/check/PhabricatorInvalidConfigSetupCheck.php
index 0eb9908b70..576ff21f2e 100644
--- a/src/applications/config/check/PhabricatorInvalidConfigSetupCheck.php
+++ b/src/applications/config/check/PhabricatorInvalidConfigSetupCheck.php
@@ -1,30 +1,34 @@
getOptions();
foreach ($options as $option) {
try {
$group->validateOption(
$option,
PhabricatorEnv::getUnrepairedEnvConfig($option->getKey()));
} catch (PhabricatorConfigValidationException $ex) {
$this
->newIssue('config.invalid.'.$option->getKey())
->setName(pht("Config '%s' Invalid", $option->getKey()))
->setMessage(
pht(
"Configuration option '%s' has invalid value and ".
"was restored to the default: %s",
$option->getKey(),
$ex->getMessage()))
->addPhabricatorConfig($option->getKey());
}
}
}
}
}
diff --git a/src/applications/config/check/PhabricatorMailSetupCheck.php b/src/applications/config/check/PhabricatorMailSetupCheck.php
index cb9fedb367..ff789d4aee 100644
--- a/src/applications/config/check/PhabricatorMailSetupCheck.php
+++ b/src/applications/config/check/PhabricatorMailSetupCheck.php
@@ -1,83 +1,87 @@
newIssue('config.metamta.mail-adapter')
->setShortName(pht('Missing Sendmail'))
->setName(pht('No Sendmail Binary Found'))
->setMessage($message)
->addRelatedPhabricatorConfig('metamta.mail-adapter');
}
break;
case 'PhabricatorMailImplementationAmazonSESAdapter':
if (PhabricatorEnv::getEnvConfig('metamta.can-send-as-user')) {
$message = pht(
'Amazon SES does not support sending email as users. Disable '.
'send as user, or choose a different mail adapter.');
$this->newIssue('config.can-send-as-user')
->setName(pht("SES Can't Send As User"))
->setMessage($message)
->addRelatedPhabricatorConfig('metamta.mail-adapter')
->addPhabricatorConfig('metamta.can-send-as-user');
}
if (!PhabricatorEnv::getEnvConfig('amazon-ses.access-key')) {
$message = pht(
'Amazon SES is selected as the mail adapter, but no SES access '.
'key is configured. Provide an SES access key, or choose a '.
'different mail adapter.');
$this->newIssue('config.amazon-ses.access-key')
->setName(pht('Amazon SES Access Key Not Set'))
->setMessage($message)
->addRelatedPhabricatorConfig('metamta.mail-adapter')
->addPhabricatorConfig('amazon-ses.access-key');
}
if (!PhabricatorEnv::getEnvConfig('amazon-ses.secret-key')) {
$message = pht(
'Amazon SES is selected as the mail adapter, but no SES secret '.
'key is configured. Provide an SES secret key, or choose a '.
'different mail adapter.');
$this->newIssue('config.amazon-ses.secret-key')
->setName(pht('Amazon SES Secret Key Not Set'))
->setMessage($message)
->addRelatedPhabricatorConfig('metamta.mail-adapter')
->addPhabricatorConfig('amazon-ses.secret-key');
}
$address_key = 'metamta.default-address';
$options = PhabricatorApplicationConfigOptions::loadAllOptions();
$default = $options[$address_key]->getDefault();
$value = PhabricatorEnv::getEnvConfig($address_key);
if ($default === $value) {
$message = pht(
'Amazon SES requires verification of the "From" address, but '.
'you have not configured a "From" address. Configure and verify '.
'a "From" address, or choose a different mail adapter.');
$this->newIssue('config.metamta.default-address')
->setName(pht('No SES From Address Configured'))
->setMessage($message)
->addRelatedPhabricatorConfig('metamta.mail-adapter')
->addPhabricatorConfig('metamta.default-address');
}
break;
}
}
}
diff --git a/src/applications/config/check/PhabricatorMySQLSetupCheck.php b/src/applications/config/check/PhabricatorMySQLSetupCheck.php
index b198af1412..053f31394b 100644
--- a/src/applications/config/check/PhabricatorMySQLSetupCheck.php
+++ b/src/applications/config/check/PhabricatorMySQLSetupCheck.php
@@ -1,316 +1,320 @@
establishConnection('w');
try {
$value = queryfx_one($conn_raw, 'SELECT @@%Q', $key);
$value = $value['@@'.$key];
} catch (AphrontQueryException $ex) {
$value = null;
}
return $value;
}
protected function executeChecks() {
$max_allowed_packet = self::loadRawConfigValue('max_allowed_packet');
$recommended_minimum = 1024 * 1024;
if ($max_allowed_packet < $recommended_minimum) {
$message = pht(
"MySQL is configured with a very small 'max_allowed_packet' (%d), ".
"which may cause some large writes to fail. Strongly consider raising ".
"this to at least %d in your MySQL configuration.",
$max_allowed_packet,
$recommended_minimum);
$this->newIssue('mysql.max_allowed_packet')
->setName(pht('Small MySQL "max_allowed_packet"'))
->setMessage($message)
->addMySQLConfig('max_allowed_packet');
}
$modes = self::loadRawConfigValue('sql_mode');
$modes = explode(',', $modes);
if (!in_array('STRICT_ALL_TABLES', $modes)) {
$summary = pht(
'MySQL is not in strict mode, but using strict mode is strongly '.
'encouraged.');
$message = pht(
"On your MySQL instance, the global %s is not set to %s. ".
"It is strongly encouraged that you enable this mode when running ".
"Phabricator.\n\n".
"By default MySQL will silently ignore some types of errors, which ".
"can cause data loss and raise security concerns. Enabling strict ".
"mode makes MySQL raise an explicit error instead, and prevents this ".
"entire class of problems from doing any damage.\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 %s file (in the %s section) and then restart %s:\n\n".
"%s\n".
"(Note that if you run other applications against the same database, ".
"they may not work in strict mode. Be careful about enabling it in ".
"these cases.)",
phutil_tag('tt', array(), 'sql_mode'),
phutil_tag('tt', array(), 'STRICT_ALL_TABLES'),
phutil_tag('tt', array(), 'my.cnf'),
phutil_tag('tt', array(), '[mysqld]'),
phutil_tag('tt', array(), 'mysqld'),
phutil_tag('pre', array(), 'sql_mode=STRICT_ALL_TABLES'));
$this->newIssue('mysql.mode')
->setName(pht('MySQL STRICT_ALL_TABLES Mode Not Set'))
->setSummary($summary)
->setMessage($message)
->addMySQLConfig('sql_mode');
}
if (in_array('ONLY_FULL_GROUP_BY', $modes)) {
$summary = pht(
'MySQL is in ONLY_FULL_GROUP_BY mode, but using this mode is strongly '.
'discouraged.');
$message = pht(
"On your MySQL instance, the global %s is set to %s. ".
"It is strongly encouraged that you disable this mode when running ".
"Phabricator.\n\n".
"With %s enabled, MySQL rejects queries for which the select list ".
"or (as of MySQL 5.0.23) %s list refer to nonaggregated columns ".
"that are not named in the %s clause. More importantly, Phabricator ".
"does not work properly with this mode enabled.\n\n".
"You can find more information about this mode (and how to configure ".
"it) in the MySQL manual. Usually, it is sufficient to change the %s ".
"in your %s file (in the %s section) and then restart %s:\n\n".
"%s\n".
"(Note that if you run other applications against the same database, ".
"they may not work with %s. Be careful about enabling ".
"it in these cases and consider migrating Phabricator to a different ".
"database.)",
phutil_tag('tt', array(), 'sql_mode'),
phutil_tag('tt', array(), 'ONLY_FULL_GROUP_BY'),
phutil_tag('tt', array(), 'ONLY_FULL_GROUP_BY'),
phutil_tag('tt', array(), 'HAVING'),
phutil_tag('tt', array(), 'GROUP BY'),
phutil_tag('tt', array(), 'sql_mode'),
phutil_tag('tt', array(), 'my.cnf'),
phutil_tag('tt', array(), '[mysqld]'),
phutil_tag('tt', array(), 'mysqld'),
phutil_tag('pre', array(), 'sql_mode=STRICT_ALL_TABLES'),
phutil_tag('tt', array(), 'ONLY_FULL_GROUP_BY'));
$this->newIssue('mysql.mode')
->setName(pht('MySQL ONLY_FULL_GROUP_BY Mode Set'))
->setSummary($summary)
->setMessage($message)
->addMySQLConfig('sql_mode');
}
$stopword_file = self::loadRawConfigValue('ft_stopword_file');
if (!PhabricatorDefaultSearchEngineSelector::shouldUseElasticSearch()) {
if ($stopword_file === null) {
$summary = pht(
'Your version of MySQL does not support configuration of a '.
'stopword file. You will not be able to find search results for '.
'common words.');
$message = pht(
"Your MySQL instance 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.",
phutil_tag('tt', array(), 'ft_stopword_file'));
$this->newIssue('mysql.ft_stopword_file')
->setName(pht('MySQL ft_stopword_file Not Supported'))
->setSummary($summary)
->setMessage($message)
->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 is using a default stopword file, which will prevent '.
'searching for many common words.');
$message = pht(
"Your MySQL instance is using the builtin stopword file for ".
"building search indexes. This can make Phabricator's 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",
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)
->addMySQLConfig('ft_stopword_file');
}
}
$min_len = self::loadRawConfigValue('ft_min_word_len');
if ($min_len >= 4) {
if (!PhabricatorDefaultSearchEngineSelector::shouldUseElasticSearch()) {
$namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
$summary = pht(
'MySQL is configured to only index words with at least %d '.
'characters.',
$min_len);
$message = pht(
"Your MySQL instance 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",
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)
->addMySQLConfig('ft_min_word_len');
}
}
$bool_syntax = self::loadRawConfigValue('ft_boolean_syntax');
if ($bool_syntax != ' |-><()~*:""&^') {
if (!PhabricatorDefaultSearchEngineSelector::shouldUseElasticSearch()) {
$summary = pht(
'MySQL is configured to search on fulltext indexes using "OR" by '.
'default. Using "AND" is usually the desired behaviour.');
$message = pht(
"Your MySQL instance is configured to use the default Boolean ".
"search syntax when using fulltext indexes. This means searching ".
"for 'search words' will yield the query 'search OR words' ".
"instead of the desired 'search AND words'.\n\n".
"This might produce unexpected search results. \n\n".
"You can change this setting to a more sensible default. ".
"Alternatively, you can ignore this warning if ".
"using 'OR' is the desired behaviour. If you later plan ".
"to configure ElasticSearch, you can also ignore this warning: ".
"only MySQL fulltext search is affected.\n\n".
"To change this setting, add this to your %s file ".
"(in the %s section) and then restart %s:\n\n".
"%s\n",
phutil_tag('tt', array(), 'my.cnf'),
phutil_tag('tt', array(), '[mysqld]'),
phutil_tag('tt', array(), 'mysqld'),
phutil_tag('pre', array(), 'ft_boolean_syntax=\' |-><()~*:""&^\''));
$this->newIssue('mysql.ft_boolean_syntax')
->setName(pht('MySQL is Using the Default Boolean Syntax'))
->setSummary($summary)
->setMessage($message)
->addMySQLConfig('ft_boolean_syntax');
}
}
$innodb_pool = self::loadRawConfigValue('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 is configured with a very small innodb_buffer_pool_size, '.
'which may impact performance.');
$message = pht(
"Your MySQL instance 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, ".
"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 ".
"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.",
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)
->addMySQLConfig('innodb_buffer_pool_size');
}
}
}
diff --git a/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php b/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php
index 50da517f31..57d97226a6 100644
--- a/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php
+++ b/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php
@@ -1,181 +1,185 @@
newIssue('php.safe_mode')
->setIsFatal(true)
->setName(pht('Disable PHP 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 ".
"'%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 ".
"'%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 ($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.
$failures = array();
try {
$open_libphutil = class_exists('Future');
} catch (Exception $ex) {
$failures[] = $ex->getMessage();
}
try {
$open_arcanist = class_exists('ArcanistDiffParser');
} catch (Exception $ex) {
$failures[] = $ex->getMessage();
}
$open_urandom = false;
try {
Filesystem::readRandomBytes(1);
$open_urandom = true;
} catch (FilesystemException $ex) {
$failures[] = $ex->getMessage();
}
try {
$tmp = new TempFile();
file_put_contents($tmp, '.');
$open_tmp = @fopen((string)$tmp, 'r');
if (!$open_tmp) {
$failures[] = pht(
"Unable to read temporary file '%s'.",
(string)$tmp);
}
} catch (Exception $ex) {
$message = $ex->getMessage();
$dir = sys_get_temp_dir();
$failures[] = pht(
"Unable to open temp files from '%s': %s",
$dir,
$message);
}
$issue = $this->newIssue('php.open_basedir')
->setName(pht('Disable PHP open_basedir'))
->addPHPConfig('open_basedir');
if ($failures) {
$message = pht(
"Your server is configured with 'open_basedir', which prevents ".
"Phabricator from opening files it requires access to.".
"\n\n".
"Disable this setting to continue.".
"\n\n".
"Failures:\n\n%s",
implode("\n\n", $failures));
$issue
->setIsFatal(true)
->setMessage($message);
return;
} else {
$summary = pht(
"You have 'open_basedir' configured in your PHP settings, which ".
"may cause some features to fail.");
$message = pht(
"You have 'open_basedir' configured in your PHP settings. Although ".
"this setting appears permissive enough that Phabricator will ".
"work properly, you may still run into problems because of it.".
"\n\n".
"Consider disabling 'open_basedir'.");
$issue
->setSummary($summary)
->setMessage($message);
}
}
}
}
diff --git a/src/applications/config/check/PhabricatorPathSetupCheck.php b/src/applications/config/check/PhabricatorPathSetupCheck.php
index 52cddd95d2..4833e4861a 100644
--- a/src/applications/config/check/PhabricatorPathSetupCheck.php
+++ b/src/applications/config/check/PhabricatorPathSetupCheck.php
@@ -1,125 +1,129 @@
newIssue('config.environment.append-paths')
->setName(pht('$PATH Not Set'))
->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 '+x' ".
"permission bit is not set).",
$path_part,
Filesystem::resolvePath($path_part));
}
}
if ($message !== null) {
$bad_paths[$path_part] = $message;
}
}
if ($bad_paths) {
foreach ($bad_paths as $path_part => $message) {
$digest = substr(PhabricatorHash::digest($path_part), 0, 8);
$this
->newIssue('config.PATH.'.$digest)
->setName(pht('$PATH Component Unusable'))
->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 ".
"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 bfb5f7e39e..26f1f46332 100644
--- a/src/applications/config/check/PhabricatorPygmentSetupCheck.php
+++ b/src/applications/config/check/PhabricatorPygmentSetupCheck.php
@@ -1,69 +1,73 @@
newIssue('pygments.enabled')
->setName(pht('pygmentize Not Found'))
->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 pygmentize script is '.
'available, but does not seem to work.');
$message = pht(
'Phabricator has %s available in $PATH, 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(), 'pygmentize -h'));
$this
->newIssue('pygments.failed')
->setName(pht('pygmentize Not Working'))
->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, '.
'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 de78709a9a..89967f7eaa 100644
--- a/src/applications/config/check/PhabricatorRepositoriesSetupCheck.php
+++ b/src/applications/config/check/PhabricatorRepositoriesSetupCheck.php
@@ -1,62 +1,66 @@
setViewer(PhabricatorUser::getOmnipotentUser())
->withServiceClasses(
array(
'AlmanacClusterRepositoryServiceType',
))
->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".
"%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 654f0d5f44..d7ae7db53a 100644
--- a/src/applications/config/check/PhabricatorSecuritySetupCheck.php
+++ b/src/applications/config/check/PhabricatorSecuritySetupCheck.php
@@ -1,74 +1,78 @@
'() { :;} ; 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.',
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(
'Increase security (and improve performance) by configuring '.
'a CDN or alternate file domain.'))
->setMessage(
pht(
'Phabricator 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/PhabricatorSetupCheck.php b/src/applications/config/check/PhabricatorSetupCheck.php
index 90e599e883..85a9152274 100644
--- a/src/applications/config/check/PhabricatorSetupCheck.php
+++ b/src/applications/config/check/PhabricatorSetupCheck.php
@@ -1,152 +1,158 @@
setIssueKey($key);
$this->issues[$key] = $issue;
+ $issue->setGroup($this->getDefaultGroup());
return $issue;
}
final public function getIssues() {
return $this->issues;
}
final public function runSetupChecks() {
$this->issues = array();
$this->executeChecks();
}
final public static function getOpenSetupIssueCount() {
$cache = PhabricatorCaches::getSetupCache();
return $cache->getKey('phabricator.setup.issues');
}
final public static function setOpenSetupIssueCount($count) {
$cache = PhabricatorCaches::getSetupCache();
$cache->setKey('phabricator.setup.issues', $count);
}
final public static function countUnignoredIssues(array $all_issues) {
assert_instances_of($all_issues, 'PhabricatorSetupIssue');
$count = 0;
foreach ($all_issues as $issue) {
if (!$issue->getIsIgnored()) {
$count++;
}
}
return $count;
}
final public static function getConfigNeedsRepair() {
$cache = PhabricatorCaches::getSetupCache();
return $cache->getKey('phabricator.setup.needs-repair');
}
final public static function setConfigNeedsRepair($needs_repair) {
$cache = PhabricatorCaches::getSetupCache();
$cache->setKey('phabricator.setup.needs-repair', $needs_repair);
}
final public static function deleteSetupCheckCache() {
$cache = PhabricatorCaches::getSetupCache();
$cache->deleteKeys(
array(
'phabricator.setup.needs-repair',
'phabricator.setup.issues',
));
}
final public static function willProcessRequest() {
$issue_count = self::getOpenSetupIssueCount();
if ($issue_count === null) {
$issues = self::runAllChecks();
foreach ($issues as $issue) {
if ($issue->getIsFatal()) {
$view = id(new PhabricatorSetupIssueView())
->setIssue($issue);
return id(new PhabricatorConfigResponse())
->setView($view);
}
}
self::setOpenSetupIssueCount(self::countUnignoredIssues($issues));
}
// Try to repair configuration unless we have a clean bill of health on it.
// We need to keep doing this on every page load until all the problems
// are fixed, which is why it's separate from setup checks (which run
// once per restart).
$needs_repair = self::getConfigNeedsRepair();
if ($needs_repair !== false) {
$needs_repair = self::repairConfig();
self::setConfigNeedsRepair($needs_repair);
}
}
final public static function runAllChecks() {
$symbols = id(new PhutilSymbolLoader())
->setAncestorClass('PhabricatorSetupCheck')
->setConcreteOnly(true)
->selectAndLoadSymbols();
$checks = array();
foreach ($symbols as $symbol) {
$checks[] = newv($symbol['name'], array());
}
$checks = msort($checks, 'getExecutionOrder');
$issues = array();
foreach ($checks as $check) {
$check->runSetupChecks();
foreach ($check->getIssues() as $key => $issue) {
if (isset($issues[$key])) {
throw new Exception(
"Two setup checks raised an issue with key '{$key}'!");
}
$issues[$key] = $issue;
if ($issue->getIsFatal()) {
break 2;
}
}
}
foreach (PhabricatorEnv::getEnvConfig('config.ignore-issues')
as $ignorable => $derp) {
if (isset($issues[$ignorable])) {
$issues[$ignorable]->setIsIgnored(true);
}
}
return $issues;
}
final public static function repairConfig() {
$needs_repair = false;
$options = PhabricatorApplicationConfigOptions::loadAllOptions();
foreach ($options as $option) {
try {
$option->getGroup()->validateOption(
$option,
PhabricatorEnv::getEnvConfig($option->getKey()));
} catch (PhabricatorConfigValidationException $ex) {
PhabricatorEnv::repairConfig($option->getKey(), $option->getDefault());
$needs_repair = true;
}
}
return $needs_repair;
}
}
diff --git a/src/applications/config/check/PhabricatorStorageSetupCheck.php b/src/applications/config/check/PhabricatorStorageSetupCheck.php
index 377729c364..12296f03a9 100644
--- a/src/applications/config/check/PhabricatorStorageSetupCheck.php
+++ b/src/applications/config/check/PhabricatorStorageSetupCheck.php
@@ -1,103 +1,107 @@
newIssue('config.storage.upload-size-limit')
->setShortName(pht('Upload Limit'))
->setName(pht('Upload Limit Not Yet Configured'))
->setMessage($message)
->addPhabricatorConfig('storage.upload-size-limit');
} else {
$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();
$upload_limit_bytes = phutil_parse_bytes($upload_limit);
$available_bytes = ($memory_limit_bytes - $memory_usage_bytes);
if ($upload_limit_bytes > $available_bytes) {
$summary = pht(
'Your PHP memory limit is configured in a way that may prevent '.
'you from uploading large files.');
$message = pht(
'When you upload a file via drag-and-drop or the API, the entire '.
'file is buffered into memory before being written to permanent '.
'storage. Phabricator needs memory available to store these '.
'files while they are uploaded, but PHP is currently configured '.
'to limit the available memory.'.
"\n\n".
'Your Phabricator %s is currently set to a larger value (%s) than '.
'the amount of available memory (%s) that a PHP process has '.
'available to use, so uploads via drag-and-drop and the API will '.
'hit the memory limit before they hit other limits.'.
"\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 buffering file uploads.)'.
"\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.".
"\n\n".
"You can also increase the limit, or decrease %s, or ignore this ".
"issue and accept that these upload mechanisms will be limited ".
"in the size of files they can handle.",
phutil_tag('tt', array(), 'storage.upload-size-limit'),
phutil_format_bytes($upload_limit_bytes),
phutil_format_bytes($available_bytes),
phutil_tag('tt', array(), 'memory_limit'),
phutil_tag('tt', array(), '-1'),
phutil_tag('tt', array(), 'storage.upload-size-limit'));
$this
->newIssue('php.memory_limit.upload')
->setName(pht('Memory Limit Restricts File Uploads'))
->setSummary($summary)
->setMessage($message)
->addPHPConfig('memory_limit')
->addPHPConfigOriginalValue('memory_limit', $memory_limit)
->addPhabricatorConfig('storage.upload-size-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');
}
}
}
diff --git a/src/applications/config/check/PhabricatorTimezoneSetupCheck.php b/src/applications/config/check/PhabricatorTimezoneSetupCheck.php
index 7f304a3f7e..5fcf74a294 100644
--- a/src/applications/config/check/PhabricatorTimezoneSetupCheck.php
+++ b/src/applications/config/check/PhabricatorTimezoneSetupCheck.php
@@ -1,51 +1,55 @@
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/applications/config/controller/PhabricatorConfigIssueListController.php b/src/applications/config/controller/PhabricatorConfigIssueListController.php
index 99bb4d8ff7..d1ebf85134 100644
--- a/src/applications/config/controller/PhabricatorConfigIssueListController.php
+++ b/src/applications/config/controller/PhabricatorConfigIssueListController.php
@@ -1,74 +1,114 @@
getRequest();
$user = $request->getUser();
$nav = $this->buildSideNavView();
$nav->selectFilter('issue/');
$issues = PhabricatorSetupCheck::runAllChecks();
PhabricatorSetupCheck::setOpenSetupIssueCount(
PhabricatorSetupCheck::countUnignoredIssues($issues));
- $list = $this->buildIssueList($issues);
- $list->setNoDataString(pht('There are no open setup issues.'));
- $list->setStackable(true);
+ $important = $this->buildIssueList(
+ $issues, PhabricatorSetupCheck::GROUP_IMPORTANT);
+ $php = $this->buildIssueList(
+ $issues, PhabricatorSetupCheck::GROUP_PHP);
+ $mysql = $this->buildIssueList(
+ $issues, PhabricatorSetupCheck::GROUP_MYSQL);
+ $other = $this->buildIssueList(
+ $issues, PhabricatorSetupCheck::GROUP_OTHER);
+
+ $setup_issues = array();
+ if ($important) {
+ $setup_issues[] = id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('Important Setup Issues'))
+ ->appendChild($important);
+ }
- $box = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Open Phabricator Setup Issues'))
- ->appendChild($list);
+ if ($php) {
+ $setup_issues[] = id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('PHP Setup Issues'))
+ ->appendChild($php);
+ }
- $nav->appendChild(
- array(
- $box,
- ));
+ if ($mysql) {
+ $setup_issues[] = id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('MySQL Setup Issues'))
+ ->appendChild($mysql);
+ }
+
+ if ($other) {
+ $setup_issues[] = id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('Other Setup Issues'))
+ ->appendChild($other);
+ }
+
+ if (empty($setup_issues)) {
+ $setup_issues[] = id(new PHUIErrorView())
+ ->setTitle(pht('No Issues'))
+ ->appendChild(
+ pht('Your install has no current setup issues to resolve.'))
+ ->setSeverity(PHUIErrorView::SEVERITY_NOTICE);
+ }
+
+ $nav->appendChild($setup_issues);
$title = pht('Setup Issues');
$crumbs = $this
->buildApplicationCrumbs($nav)
->addTextCrumb(pht('Setup'), $this->getApplicationURI('issue/'));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
));
}
- private function buildIssueList(array $issues) {
+ private function buildIssueList(array $issues, $group) {
assert_instances_of($issues, 'PhabricatorSetupIssue');
$list = new PHUIObjectItemListView();
+ $list->setStackable(true);
$ignored_items = array();
+ $items = 0;
foreach ($issues as $issue) {
+ if ($issue->getGroup() == $group) {
+ $items++;
$href = $this->getApplicationURI('/issue/'.$issue->getIssueKey().'/');
$item = id(new PHUIObjectItemView())
->setHeader($issue->getName())
->setHref($href)
->addAttribute($issue->getSummary());
- if (!$issue->getIsIgnored()) {
- $item->setBarColor('yellow');
- $list->addItem($item);
- } else {
- $item->addIcon('fa-eye-slash', pht('Ignored'));
- $item->setDisabled(true);
- $item->setBarColor('none');
- $ignored_items[] = $item;
+ if (!$issue->getIsIgnored()) {
+ $item->setBarColor('yellow');
+ $list->addItem($item);
+ } else {
+ $item->addIcon('fa-eye-slash', pht('Ignored'));
+ $item->setDisabled(true);
+ $item->setBarColor('none');
+ $ignored_items[] = $item;
+ }
}
}
foreach ($ignored_items as $item) {
$list->addItem($item);
}
- return $list;
+ if ($items == 0) {
+ return null;
+ } else {
+ return $list;
+ }
}
}
diff --git a/src/applications/config/issue/PhabricatorSetupIssue.php b/src/applications/config/issue/PhabricatorSetupIssue.php
index 133f55e017..f1415fec05 100644
--- a/src/applications/config/issue/PhabricatorSetupIssue.php
+++ b/src/applications/config/issue/PhabricatorSetupIssue.php
@@ -1,179 +1,193 @@
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 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;
}
}