diff --git a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php index 4ec61cf503..139684adbe 100644 --- a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php +++ b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php @@ -1,221 +1,224 @@ 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.'), 'translation.provider' => pht( 'The translation implementation has changed and providers are no '. 'longer used or supported.'), 'config.mask' => pht( 'Use `config.hide` instead of this option.'), 'phd.start-taskmasters' => pht( 'Taskmasters now use an autoscaling pool. You can configure the '. 'pool size with `phd.taskmasters`.'), 'storage.engine-selector' => pht( 'Phabricator now automatically discovers available storage engines '. 'at runtime.'), + 'storage.upload-size-limit' => pht( + 'Phabricator now supports arbitrarily large files. Consult the '. + 'documentation for configuration details.'), ); return $ancient_config; } } diff --git a/src/applications/config/check/PhabricatorMySQLSetupCheck.php b/src/applications/config/check/PhabricatorMySQLSetupCheck.php index 44d9cb3cd4..6747158b85 100644 --- a/src/applications/config/check/PhabricatorMySQLSetupCheck.php +++ b/src/applications/config/check/PhabricatorMySQLSetupCheck.php @@ -1,340 +1,343 @@ 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; + + // This primarily supports setting the filesize limit for MySQL to 8MB, + // which may produce a >16MB packet after escaping. + $recommended_minimum = (32 * 1024 * 1024); if ($max_allowed_packet < $recommended_minimum) { $message = pht( - "MySQL is configured with a very small 'max_allowed_packet' (%d), ". + "MySQL is configured with a 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'); } $ok = PhabricatorStorageManagementAPI::isCharacterSetAvailableOnConnection( 'utf8mb4', id(new PhabricatorUser())->establishConnection('w')); if (!$ok) { $summary = pht( 'You are using an old version of MySQL, and should upgrade.'); $message = pht( 'You are using an old version of MySQL which has poor unicode '. 'support (it does not support the "utf8mb4" collation set). You will '. 'encounter limitations when working with some unicode data.'. "\n\n". 'We strongly recommend you upgrade to MySQL 5.5 or newer.'); $this->newIssue('mysql.utf8mb4') ->setName(pht('Old MySQL Version')) ->setSummary($summary) ->setMessage($message); } } } diff --git a/src/applications/config/check/PhabricatorStorageSetupCheck.php b/src/applications/config/check/PhabricatorStorageSetupCheck.php index 12296f03a9..01844ce0f5 100644 --- a/src/applications/config/check/PhabricatorStorageSetupCheck.php +++ b/src/applications/config/check/PhabricatorStorageSetupCheck.php @@ -1,107 +1,150 @@ isChunkEngine()) { + $chunk_engine_active = true; + break; + } + } + + if (!$chunk_engine_active) { + $doc_href = PhabricatorEnv::getDocLink('Configuring File Storage'); + $message = pht( - 'The Phabricator file upload limit is not configured. You may only '. - 'be able to upload very small files until you configure it, because '. - 'some PHP default limits are very low (as low as 2MB).'); + 'Large file storage has not been configured, which will limit '. + 'the maximum size of file uploads. See %s for '. + 'instructions on configuring uploads and storage.', + phutil_tag( + 'a', + array( + 'href' => $doc_href, + 'target' => '_blank', + ), + pht('Configuring File Storage'))); $this - ->newIssue('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'); - } + ->newIssue('large-files') + ->setShortName(pht('Large Files')) + ->setName(pht('Large File Storage Not Configured')) + ->setMessage($message); + } + + $post_max_size = ini_get('post_max_size'); + if ($post_max_size && ((int)$post_max_size > 0)) { + $post_max_bytes = phutil_parse_bytes($post_max_size); + $post_max_need = (32 * 1024 * 1024) * 100; + if ($post_max_need > $post_max_bytes) { + $summary = pht( + 'Set %s in your PHP configuration to at least 32MB '. + 'to support large file uploads.', + phutil_tag('tt', array(), 'post_max_size')); + + $message = pht( + 'Adjust %s in your PHP configuration to at least 32MB. When '. + 'set to smaller value, large file uploads may not work properly.', + phutil_tag('tt', array(), 'post_max_size')); + + $this + ->newIssue('php.post_max_size') + ->setName(pht('PHP post_max_size Not Configured')) + ->setSummary($summary) + ->setMessage($message) + ->setGroup(self::GROUP_PHP) + ->addPHPConfig('post_max_size'); + } + } + + // This is somewhat arbitrary, but make sure we have enough headroom to + // upload a default file at the chunk threshold (8MB), which may be + // base64 encoded, then JSON encoded in the request, and may need to be + // held in memory in the raw and as a query string. + $need_bytes = (64 * 1024 * 1024); + + $memory_limit = PhabricatorStartup::getOldMemoryLimit(); + if ($memory_limit && ((int)$memory_limit > 0)) { + $memory_limit_bytes = phutil_parse_bytes($memory_limit); + $memory_usage_bytes = memory_get_usage(); + + $available_bytes = ($memory_limit_bytes - $memory_usage_bytes); + + if ($need_bytes > $available_bytes) { + $summary = pht( + 'Your PHP memory limit is configured in a way that may prevent '. + 'you from uploading large files or handling large requests.'); + + $message = pht( + 'When you upload a file via drag-and-drop or the API, chunks must '. + 'be buffered into memory before being written to permanent '. + 'storage. Phabricator needs memory available to store these '. + 'chunks while they are uploaded, but PHP is currently configured '. + 'to severly limit the available memory.'. + "\n\n". + 'PHP processes currently have very little free memory available '. + '(%s). To work well, processes should have at least %s.'. + "\n\n". + '(Note that the application itself must also fit in available '. + 'memory, so not all of the memory under the memory limit is '. + 'available for running workloads.)'. + "\n\n". + "The easiest way to resolve this issue is to set %s to %s in your ". + "PHP configuration, to disable the memory limit. There is ". + "usually little or no value to using this option to limit ". + "Phabricator process memory.". + "\n\n". + "You can also increase the limit or ignore this issue and accept ". + "that you may encounter problems uploading large files and ". + "processing large requests.", + phutil_format_bytes($available_bytes), + phutil_format_bytes($need_bytes), + phutil_tag('tt', array(), 'memory_limit'), + phutil_tag('tt', array(), '-1')); + + $this + ->newIssue('php.memory_limit.upload') + ->setName(pht('Memory Limit Restricts File Uploads')) + ->setSummary($summary) + ->setMessage($message) + ->setGroup(self::GROUP_PHP) + ->addPHPConfig('memory_limit') + ->addPHPConfigOriginalValue('memory_limit', $memory_limit); } } $local_path = PhabricatorEnv::getEnvConfig('storage.local-disk.path'); if (!$local_path) { return; } if (!Filesystem::pathExists($local_path) || !is_readable($local_path) || !is_writable($local_path)) { $message = pht( 'Configured location for storing uploaded files on disk ("%s") does '. 'not exist, or is not readable or writable. Verify the directory '. 'exists and is readable and writable by the webserver.', $local_path); $this ->newIssue('config.storage.local-disk.path') ->setShortName(pht('Local Disk Storage')) ->setName(pht('Local Disk Storage Not Readable/Writable')) ->setMessage($message) ->addPhabricatorConfig('storage.local-disk.path'); } } } diff --git a/src/applications/files/config/PhabricatorFilesConfigOptions.php b/src/applications/files/config/PhabricatorFilesConfigOptions.php index 36add0f08b..b448b0b5ff 100644 --- a/src/applications/files/config/PhabricatorFilesConfigOptions.php +++ b/src/applications/files/config/PhabricatorFilesConfigOptions.php @@ -1,205 +1,185 @@ 'image/jpeg', 'image/jpg' => 'image/jpg', 'image/png' => 'image/png', 'image/gif' => 'image/gif', 'text/plain' => 'text/plain; charset=utf-8', 'text/x-diff' => 'text/plain; charset=utf-8', // ".ico" favicon files, which have mime type diversity. See: // http://en.wikipedia.org/wiki/ICO_(file_format)#MIME_type 'image/x-ico' => 'image/x-icon', 'image/x-icon' => 'image/x-icon', 'image/vnd.microsoft.icon' => 'image/x-icon', 'audio/x-wav' => 'audio/x-wav', 'application/ogg' => 'application/ogg', 'audio/mpeg' => 'audio/mpeg', ); $image_default = array( 'image/jpeg' => true, 'image/jpg' => true, 'image/png' => true, 'image/gif' => true, 'image/x-ico' => true, 'image/x-icon' => true, 'image/vnd.microsoft.icon' => true, ); $audio_default = array( 'audio/x-wav' => true, 'application/ogg' => true, 'audio/mpeg' => true, ); // largely lifted from http://en.wikipedia.org/wiki/Internet_media_type $icon_default = array( // audio file icon 'audio/basic' => 'fa-file-audio-o', 'audio/L24' => 'fa-file-audio-o', 'audio/mp4' => 'fa-file-audio-o', 'audio/mpeg' => 'fa-file-audio-o', 'audio/ogg' => 'fa-file-audio-o', 'audio/vorbis' => 'fa-file-audio-o', 'audio/vnd.rn-realaudio' => 'fa-file-audio-o', 'audio/vnd.wave' => 'fa-file-audio-o', 'audio/webm' => 'fa-file-audio-o', // movie file icon 'video/mpeg' => 'fa-file-movie-o', 'video/mp4' => 'fa-file-movie-o', 'video/ogg' => 'fa-file-movie-o', 'video/quicktime' => 'fa-file-movie-o', 'video/webm' => 'fa-file-movie-o', 'video/x-matroska' => 'fa-file-movie-o', 'video/x-ms-wmv' => 'fa-file-movie-o', 'video/x-flv' => 'fa-file-movie-o', // pdf file icon 'application/pdf' => 'fa-file-pdf-o', // zip file icon 'application/zip' => 'fa-file-zip-o', // msword icon 'application/msword' => 'fa-file-word-o', // msexcel 'application/vnd.ms-excel' => 'fa-file-excel-o', // mspowerpoint 'application/vnd.ms-powerpoint' => 'fa-file-powerpoint-o', ) + array_fill_keys(array_keys($image_default), 'fa-file-image-o'); return array( $this->newOption('files.viewable-mime-types', 'wild', $viewable_default) ->setSummary( pht('Configure which MIME types are viewable in the browser.')) ->setDescription( pht( 'Configure which uploaded file types may be viewed directly '. 'in the browser. Other file types will be downloaded instead '. 'of displayed. This is mainly a usability consideration, since '. 'browsers tend to freak out when viewing enormous binary files.'. "\n\n". 'The keys in this map are vieweable MIME types; the values are '. 'the MIME types they are delivered as when they are viewed in '. 'the browser.')), $this->newOption('files.image-mime-types', 'set', $image_default) ->setSummary(pht('Configure which MIME types are images.')) ->setDescription( pht( 'List of MIME types which can be used as the `src` for an '. '`` tag.')), $this->newOption('files.audio-mime-types', 'set', $audio_default) ->setSummary(pht('Configure which MIME types are audio.')) ->setDescription( pht( 'List of MIME types which can be used to render an '. '`