/**
  * @task config
  */
 protected function establishLiveConnection($mode)
 {
     $namespace = self::getStorageNamespace();
     $database = $namespace . '_' . $this->getApplicationName();
     $is_readonly = PhabricatorEnv::isReadOnly();
     if ($is_readonly && $mode != 'r') {
         $this->raiseImproperWrite($database);
     }
     $is_cluster = (bool) PhabricatorEnv::getEnvConfig('cluster.databases');
     if ($is_cluster) {
         $connection = $this->newClusterConnection($database, $mode);
     } else {
         $connection = $this->newBasicConnection($database, $mode, $namespace);
     }
     // TODO: This should be testing if the mode is "r", but that would proably
     // break a lot of things. Perform a more narrow test for readonly mode
     // until we have greater certainty that this works correctly most of the
     // time.
     if ($is_readonly) {
         $connection->setReadOnly(true);
     }
     // Unless this is a script running from the CLI, prevent any query from
     // running for more than 30 seconds. See T10849 for discussion.
     if (php_sapi_name() != 'cli') {
         $connection->setQueryTimeout(30);
     }
     return $connection;
 }
 /**
  * @task config
  */
 protected function establishLiveConnection($mode)
 {
     $namespace = self::getStorageNamespace();
     $database = $namespace . '_' . $this->getApplicationName();
     $is_readonly = PhabricatorEnv::isReadOnly();
     if ($is_readonly && $mode != 'r') {
         $this->raiseImproperWrite($database);
     }
     $is_cluster = (bool) PhabricatorEnv::getEnvConfig('cluster.databases');
     if ($is_cluster) {
         $connection = $this->newClusterConnection($database, $mode);
     } else {
         $connection = $this->newBasicConnection($database, $mode, $namespace);
     }
     // TODO: This should be testing if the mode is "r", but that would probably
     // break a lot of things. Perform a more narrow test for readonly mode
     // until we have greater certainty that this works correctly most of the
     // time.
     if ($is_readonly) {
         $connection->setReadOnly(true);
     }
     // Unless this is a script running from the CLI:
     //   - (T10849) Prevent any query from running for more than 30 seconds.
     //   - (T11672) Use persistent connections.
     if (php_sapi_name() != 'cli') {
         // TODO: For now, disable this until after T11044: it's better at high
         // load, but causes us to use slightly more connections at low load and
         // is pushing users over limits like MySQL "max_connections".
         $use_persistent = false;
         $connection->setQueryTimeout(30)->setPersistent($use_persistent);
     }
     return $connection;
 }
 public function setKeys(array $keys, $ttl = null)
 {
     if (PhabricatorEnv::isReadOnly()) {
         return;
     }
     if ($keys) {
         $map = $this->digestKeys(array_keys($keys));
         $conn_w = $this->establishConnection('w');
         $sql = array();
         foreach ($map as $key => $hash) {
             $value = $keys[$key];
             list($format, $storage_value) = $this->willWriteValue($key, $value);
             $sql[] = qsprintf($conn_w, '(%s, %s, %s, %B, %d, %nd)', $hash, $key, $format, $storage_value, time(), $ttl ? time() + $ttl : null);
         }
         $guard = AphrontWriteGuard::beginScopedUnguardedWrites();
         foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) {
             queryfx($conn_w, 'INSERT INTO %T
           (cacheKeyHash, cacheKey, cacheFormat, cacheData,
             cacheCreated, cacheExpires) VALUES %Q
           ON DUPLICATE KEY UPDATE
             cacheKey = VALUES(cacheKey),
             cacheFormat = VALUES(cacheFormat),
             cacheData = VALUES(cacheData),
             cacheCreated = VALUES(cacheCreated),
             cacheExpires = VALUES(cacheExpires)', $this->getTableName(), $chunk);
         }
         unset($guard);
     }
     return $this;
 }
 public static function clearCacheForAllUsers($key)
 {
     if (PhabricatorEnv::isReadOnly()) {
         return;
     }
     $table = new self();
     $conn_w = $table->establishConnection('w');
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
     queryfx($conn_w, 'DELETE FROM %T WHERE cacheIndex = %s', $table->getTableName(), PhabricatorHash::digestForIndex($key));
     unset($unguarded);
 }
 public function execute(PhutilArgumentParser $args)
 {
     $this->setDryRun($args->getArg('dryrun'));
     $this->setForce($args->getArg('force'));
     if (!$this->isReadOnlyWorkflow()) {
         if (PhabricatorEnv::isReadOnly()) {
             if ($this->isForce()) {
                 PhabricatorEnv::setReadOnly(false, null);
             } else {
                 throw new PhutilArgumentUsageException(pht('Phabricator is currently in read-only mode. Use --force to ' . 'override this mode.'));
             }
         }
     }
     return $this->didExecute($args);
 }
 public static function updateObjectNotificationViews(PhabricatorUser $user, $object_phid)
 {
     if (PhabricatorEnv::isReadOnly()) {
         return;
     }
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
     $notification_table = new PhabricatorFeedStoryNotification();
     $conn = $notification_table->establishConnection('w');
     queryfx($conn, 'UPDATE %T
    SET hasViewed = 1
    WHERE userPHID = %s
      AND primaryObjectPHID = %s
      AND hasViewed = 0', $notification_table->getTableName(), $user->getPHID(), $object_phid);
     unset($unguarded);
     $count_key = PhabricatorUserNotificationCountCacheType::KEY_COUNT;
     PhabricatorUserCache::clearCache($count_key, $user->getPHID());
     $user->clearCacheData($count_key);
 }
 /**
  * @task config
  */
 protected function establishLiveConnection($mode)
 {
     $namespace = self::getStorageNamespace();
     $database = $namespace . '_' . $this->getApplicationName();
     $is_readonly = PhabricatorEnv::isReadOnly();
     if ($is_readonly && $mode != 'r') {
         $this->raiseImproperWrite($database);
     }
     $connection = $this->newClusterConnection($this->getApplicationName(), $database, $mode);
     // TODO: This should be testing if the mode is "r", but that would probably
     // break a lot of things. Perform a more narrow test for readonly mode
     // until we have greater certainty that this works correctly most of the
     // time.
     if ($is_readonly) {
         $connection->setReadOnly(true);
     }
     return $connection;
 }
 protected function willRenderPage()
 {
     parent::willRenderPage();
     if (!$this->getRequest()) {
         throw new Exception(pht('You must set the %s to render a %s.', 'Request', __CLASS__));
     }
     $console = $this->getConsole();
     require_celerity_resource('phabricator-core-css');
     require_celerity_resource('phabricator-zindex-css');
     require_celerity_resource('phui-button-css');
     require_celerity_resource('phui-spacing-css');
     require_celerity_resource('phui-form-css');
     require_celerity_resource('phabricator-standard-page-view');
     require_celerity_resource('conpherence-durable-column-view');
     require_celerity_resource('font-lato');
     require_celerity_resource('font-aleo');
     Javelin::initBehavior('workflow', array());
     $request = $this->getRequest();
     $user = null;
     if ($request) {
         $user = $request->getUser();
     }
     if ($user) {
         if ($user->isUserActivated()) {
             $offset = $user->getTimeZoneOffset();
             $ignore_key = PhabricatorTimezoneIgnoreOffsetSetting::SETTINGKEY;
             $ignore = $user->getUserSetting($ignore_key);
             Javelin::initBehavior('detect-timezone', array('offset' => $offset, 'uri' => '/settings/timezone/', 'message' => pht('Your browser timezone setting differs from the timezone ' . 'setting in your profile, click to reconcile.'), 'ignoreKey' => $ignore_key, 'ignore' => $ignore));
             if ($user->getIsAdmin()) {
                 $server_https = $request->isHTTPS();
                 $server_protocol = $server_https ? 'HTTPS' : 'HTTP';
                 $client_protocol = $server_https ? 'HTTP' : 'HTTPS';
                 $doc_name = 'Configuring a Preamble Script';
                 $doc_href = PhabricatorEnv::getDoclink($doc_name);
                 Javelin::initBehavior('setup-check-https', array('server_https' => $server_https, 'doc_name' => pht('See Documentation'), 'doc_href' => $doc_href, 'message' => pht('Phabricator thinks you are using %s, but your ' . 'client is conviced that it is using %s. This is a serious ' . 'misconfiguration with subtle, but significant, consequences.', $server_protocol, $client_protocol)));
             }
         }
         $default_img_uri = celerity_get_resource_uri('rsrc/image/icon/fatcow/document_black.png');
         $download_form = phabricator_form($user, array('action' => '#', 'method' => 'POST', 'class' => 'lightbox-download-form', 'sigil' => 'download'), phutil_tag('button', array(), pht('Download')));
         Javelin::initBehavior('lightbox-attachments', array('defaultImageUri' => $default_img_uri, 'downloadForm' => $download_form));
     }
     Javelin::initBehavior('aphront-form-disable-on-submit');
     Javelin::initBehavior('toggle-class', array());
     Javelin::initBehavior('history-install');
     Javelin::initBehavior('phabricator-gesture');
     $current_token = null;
     if ($user) {
         $current_token = $user->getCSRFToken();
     }
     Javelin::initBehavior('refresh-csrf', array('tokenName' => AphrontRequest::getCSRFTokenName(), 'header' => AphrontRequest::getCSRFHeaderName(), 'viaHeader' => AphrontRequest::getViaHeaderName(), 'current' => $current_token));
     Javelin::initBehavior('device');
     Javelin::initBehavior('high-security-warning', $this->getHighSecurityWarningConfig());
     if (PhabricatorEnv::isReadOnly()) {
         Javelin::initBehavior('read-only-warning', array('message' => PhabricatorEnv::getReadOnlyMessage(), 'uri' => PhabricatorEnv::getReadOnlyURI()));
     }
     if ($console) {
         require_celerity_resource('aphront-dark-console-css');
         $headers = array();
         if (DarkConsoleXHProfPluginAPI::isProfilerStarted()) {
             $headers[DarkConsoleXHProfPluginAPI::getProfilerHeader()] = 'page';
         }
         if (DarkConsoleServicesPlugin::isQueryAnalyzerRequested()) {
             $headers[DarkConsoleServicesPlugin::getQueryAnalyzerHeader()] = true;
         }
         Javelin::initBehavior('dark-console', $this->getConsoleConfig());
         // Change this to initBehavior when there is some behavior to initialize
         require_celerity_resource('javelin-behavior-error-log');
     }
     if ($user) {
         $viewer = $user;
     } else {
         $viewer = new PhabricatorUser();
     }
     $menu = id(new PhabricatorMainMenuView())->setUser($viewer);
     if ($this->getController()) {
         $menu->setController($this->getController());
     }
     $application_menu = $this->getApplicationMenu();
     if ($application_menu) {
         if ($application_menu instanceof PHUIApplicationMenuView) {
             $crumbs = $this->getCrumbs();
             if ($crumbs) {
                 $application_menu->setCrumbs($crumbs);
             }
             $application_menu = $application_menu->buildListView();
         }
         $menu->setApplicationMenu($application_menu);
     }
     $this->menuContent = $menu->render();
 }
 private function writeEvents()
 {
     if (PhabricatorEnv::isReadOnly()) {
         return;
     }
     $events = $this->events;
     $random = Filesystem::readRandomBytes(32);
     $request_key = PhabricatorHash::digestForIndex($random);
     $host_id = $this->loadHostID(php_uname('n'));
     $context_id = $this->loadEventContextID($this->eventContext);
     $viewer_id = $this->loadEventViewerID($this->eventViewer);
     $label_map = $this->loadEventLabelIDs(mpull($events, 'getEventLabel'));
     foreach ($events as $event) {
         $event->setRequestKey($request_key)->setSampleRate($this->sampleRate)->setEventHostID($host_id)->setEventContextID($context_id)->setEventViewerID($viewer_id)->setEventLabelID($label_map[$event->getEventLabel()])->save();
     }
 }
 /**
  * Write to the availability cache.
  *
  * @param wild Availability cache data.
  * @param int|null Cache TTL.
  * @return this
  * @task availability
  */
 public function writeAvailabilityCache(array $availability, $ttl)
 {
     if (PhabricatorEnv::isReadOnly()) {
         return $this;
     }
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
     queryfx($this->establishConnection('w'), 'UPDATE %T SET availabilityCache = %s, availabilityCacheTTL = %nd
     WHERE id = %d', $this->getTableName(), json_encode($availability), $ttl, $this->getID());
     unset($unguarded);
     return $this;
 }
 /**
  * @task markup
  */
 private function loadPreprocessorCaches(array $engines, array $objects)
 {
     $blocks = array();
     $use_cache = array();
     foreach ($objects as $key => $info) {
         if ($info['object']->shouldUseMarkupCache($info['field'])) {
             $use_cache[$key] = true;
         }
     }
     if ($use_cache) {
         try {
             $blocks = id(new PhabricatorMarkupCache())->loadAllWhere('cacheKey IN (%Ls)', array_keys($use_cache));
             $blocks = mpull($blocks, null, 'getCacheKey');
         } catch (Exception $ex) {
             phlog($ex);
         }
     }
     $is_readonly = PhabricatorEnv::isReadOnly();
     foreach ($objects as $key => $info) {
         // False check in case MySQL doesn't support unicode characters
         // in the string (T1191), resulting in unserialize returning false.
         if (isset($blocks[$key]) && $blocks[$key]->getCacheData() !== false) {
             // If we already have a preprocessing cache, we don't need to rebuild
             // it.
             continue;
         }
         $text = $info['object']->getMarkupText($info['field']);
         $data = $engines[$key]->preprocessText($text);
         // NOTE: This is just debugging information to help sort out cache issues.
         // If one machine is misconfigured and poisoning caches you can use this
         // field to hunt it down.
         $metadata = array('host' => php_uname('n'));
         $blocks[$key] = id(new PhabricatorMarkupCache())->setCacheKey($key)->setCacheData($data)->setMetadata($metadata);
         if (isset($use_cache[$key]) && !$is_readonly) {
             // This is just filling a cache and always safe, even on a read pathway.
             $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
             $blocks[$key]->replace();
             unset($unguarded);
         }
     }
     return $blocks;
 }
 public function saveCache()
 {
     if (PhabricatorEnv::isReadOnly()) {
         return false;
     }
     if ($this->highlightErrors) {
         return false;
     }
     $render_cache_key = $this->getRenderCacheKey();
     if (!$render_cache_key) {
         return false;
     }
     $cache = array();
     foreach (self::getCacheableProperties() as $cache_key) {
         switch ($cache_key) {
             case 'cacheVersion':
                 $cache[$cache_key] = self::CACHE_VERSION;
                 break;
             case 'cacheHost':
                 $cache[$cache_key] = php_uname('n');
                 break;
             default:
                 $cache[$cache_key] = $this->{$cache_key};
                 break;
         }
     }
     $cache = serialize($cache);
     // We don't want to waste too much space by a single changeset.
     if (strlen($cache) > self::CACHE_MAX_SIZE) {
         return;
     }
     $changeset = new DifferentialChangeset();
     $conn_w = $changeset->establishConnection('w');
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
     try {
         queryfx($conn_w, 'INSERT INTO %T (id, cache, dateCreated) VALUES (%d, %B, %d)
         ON DUPLICATE KEY UPDATE cache = VALUES(cache)', DifferentialChangeset::TABLE_CACHE, $render_cache_key, $cache, time());
     } catch (AphrontQueryException $ex) {
         // Ignore these exceptions. A common cause is that the cache is
         // larger than 'max_allowed_packet', in which case we're better off
         // not writing it.
         // TODO: It would be nice to tailor this more narrowly.
     }
     unset($unguarded);
 }
 protected function executeChecks()
 {
     // TODO: These checks should be executed against every reachable replica?
     // See T10759.
     if (PhabricatorEnv::isReadOnly()) {
         return;
     }
     $max_allowed_packet = self::loadRawConfigValue('max_allowed_packet');
     // 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 small '%s' (%d), " . "which may cause some large writes to fail. Strongly consider raising " . "this to at least %d in your MySQL configuration.", 'max_allowed_packet', $max_allowed_packet, $recommended_minimum);
         $this->newIssue('mysql.max_allowed_packet')->setName(pht('Small MySQL "%s"', '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 %s Mode Not Set', 'STRICT_ALL_TABLES'))->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 %s Mode Set', 'ONLY_FULL_GROUP_BY'))->setSummary($summary)->setMessage($message)->addMySQLConfig('sql_mode');
     }
     $stopword_file = self::loadRawConfigValue('ft_stopword_file');
     if ($this->shouldUseMySQLSearchEngine()) {
         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 %s Not Supported', 'ft_stopword_file'))->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 ($this->shouldUseMySQLSearchEngine()) {
             $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 ($this->shouldUseMySQLSearchEngine()) {
             $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');
     }
     $conn_w = id(new PhabricatorUser())->establishConnection('w');
     $ok = PhabricatorStorageManagementAPI::isCharacterSetAvailableOnConnection('utf8mb4', $conn_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);
     }
     $info = queryfx_one($conn_w, 'SELECT UNIX_TIMESTAMP() epoch');
     $epoch = (int) $info['epoch'];
     $local = PhabricatorTime::getNow();
     $delta = (int) abs($local - $epoch);
     if ($delta > 60) {
         $this->newIssue('mysql.clock')->setName(pht('Major Web/Database Clock Skew'))->setSummary(pht('This host is set to a very different time than the database.'))->setMessage(pht('The database host and this host ("%s") disagree on the current ' . 'time by more than 60 seconds (absolute skew is %s seconds). ' . 'Check that the current time is set correctly everywhere.', php_uname('n'), new PhutilNumber($delta)));
     }
 }
 private function buildClusterStatusPanel()
 {
     $repository = $this->getRepository();
     $viewer = $this->getViewer();
     $service_phid = $repository->getAlmanacServicePHID();
     if ($service_phid) {
         $service = id(new AlmanacServiceQuery())->setViewer($viewer)->withServiceTypes(array(AlmanacClusterRepositoryServiceType::SERVICETYPE))->withPHIDs(array($service_phid))->needBindings(true)->executeOne();
         if (!$service) {
             // TODO: Viewer may not have permission to see the service, or it may
             // be invalid? Raise some more useful error here?
             throw new Exception(pht('Unable to load cluster service.'));
         }
     } else {
         $service = null;
     }
     Javelin::initBehavior('phabricator-tooltips');
     $rows = array();
     if ($service) {
         $bindings = $service->getBindings();
         $bindings = mgroup($bindings, 'getDevicePHID');
         // This is an unusual read which always comes from the master.
         if (PhabricatorEnv::isReadOnly()) {
             $versions = array();
         } else {
             $versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions($repository->getPHID());
         }
         $versions = mpull($versions, null, 'getDevicePHID');
         foreach ($bindings as $binding_group) {
             $all_disabled = true;
             foreach ($binding_group as $binding) {
                 if (!$binding->getIsDisabled()) {
                     $all_disabled = false;
                     break;
                 }
             }
             $any_binding = head($binding_group);
             if ($all_disabled) {
                 $binding_icon = 'fa-times grey';
                 $binding_tip = pht('Disabled');
             } else {
                 $binding_icon = 'fa-folder-open green';
                 $binding_tip = pht('Active');
             }
             $binding_icon = id(new PHUIIconView())->setIcon($binding_icon)->addSigil('has-tooltip')->setMetadata(array('tip' => $binding_tip));
             $device = $any_binding->getDevice();
             $version = idx($versions, $device->getPHID());
             if ($version) {
                 $version_number = $version->getRepositoryVersion();
                 $href = null;
                 if ($repository->isHosted()) {
                     $href = "/diffusion/pushlog/view/{$version_number}/";
                 } else {
                     $commit = id(new DiffusionCommitQuery())->setViewer($viewer)->withIDs(array($version_number))->executeOne();
                     if ($commit) {
                         $href = $commit->getURI();
                     }
                 }
                 if ($href) {
                     $version_number = phutil_tag('a', array('href' => $href), $version_number);
                 }
             } else {
                 $version_number = '-';
             }
             if ($version && $version->getIsWriting()) {
                 $is_writing = id(new PHUIIconView())->setIcon('fa-pencil green');
             } else {
                 $is_writing = id(new PHUIIconView())->setIcon('fa-pencil grey');
             }
             $write_properties = null;
             if ($version) {
                 $write_properties = $version->getWriteProperties();
                 if ($write_properties) {
                     try {
                         $write_properties = phutil_json_decode($write_properties);
                     } catch (Exception $ex) {
                         $write_properties = null;
                     }
                 }
             }
             if ($write_properties) {
                 $writer_phid = idx($write_properties, 'userPHID');
                 $last_writer = $viewer->renderHandle($writer_phid);
                 $writer_epoch = idx($write_properties, 'epoch');
                 $writer_epoch = phabricator_datetime($writer_epoch, $viewer);
             } else {
                 $last_writer = null;
                 $writer_epoch = null;
             }
             $rows[] = array($binding_icon, phutil_tag('a', array('href' => $device->getURI()), $device->getName()), $version_number, $is_writing, $last_writer, $writer_epoch);
         }
     }
     $table = id(new AphrontTableView($rows))->setNoDataString(pht('This is not a cluster repository.'))->setHeaders(array(null, pht('Device'), pht('Version'), pht('Writing'), pht('Last Writer'), pht('Last Write At')))->setColumnClasses(array(null, null, null, 'right wide', null, 'date'));
     $doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories');
     $header = id(new PHUIHeaderView())->setHeader(pht('Cluster Status'))->addActionLink(id(new PHUIButtonView())->setIcon('fa-book')->setHref($doc_href)->setTag('a')->setText(pht('Documentation')));
     return id(new PHUIObjectBoxView())->setHeader($header)->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)->setTable($table);
 }