From 02067ace54a400d652d0614bcaa946305e072aa0 Mon Sep 17 00:00:00 2001
From: xisi
Date: Fri, 19 Jun 2015 07:01:41 -0400
Subject: [PATCH] See issue #2427
---
include/admin_checks.php | 155 ------------------
.../pages/admin/checks/check_daemon.inc.php | 38 +++++
include/pages/admin/checks/check_fees.inc.php | 13 ++
.../pages/admin/checks/check_memcache.inc.php | 59 +++++++
.../admin/checks/check_permissions.inc.php | 47 ++++++
.../pages/admin/checks/check_security.inc.php | 29 ++++
.../pages/admin/checks/check_stratum.inc.php | 36 ++++
include/pages/admin/setup.inc.php | 42 +++++
public/index.php | 4 +-
templates/bootstrap/admin/setup/default.tpl | 21 +++
templates/bootstrap/global/navigation.tpl | 1 +
11 files changed, 288 insertions(+), 157 deletions(-)
delete mode 100644 include/admin_checks.php
create mode 100644 include/pages/admin/checks/check_daemon.inc.php
create mode 100644 include/pages/admin/checks/check_fees.inc.php
create mode 100644 include/pages/admin/checks/check_memcache.inc.php
create mode 100644 include/pages/admin/checks/check_permissions.inc.php
create mode 100644 include/pages/admin/checks/check_security.inc.php
create mode 100644 include/pages/admin/checks/check_stratum.inc.php
create mode 100644 include/pages/admin/setup.inc.php
create mode 100644 templates/bootstrap/admin/setup/default.tpl
diff --git a/include/admin_checks.php b/include/admin_checks.php
deleted file mode 100644
index 83d62895..00000000
--- a/include/admin_checks.php
+++ /dev/null
@@ -1,155 +0,0 @@
-isAdmin(@$_SESSION['USERDATA']['id'])) {
- if (!include_once(INCLUDE_DIR . '/lib/jsonRPCClient.php')) die('Unable to load libs');
- $notice = array();
- $enotice = array();
- $error = array();
-
- // setup some basic stuff for checking - getuid/getpwuid not available on mac/windows
- $apache_user = 'unknown';
- if (substr_count(strtolower(PHP_OS), 'nix') > 0 || substr_count(strtolower(PHP_OS), 'linux') > 0) {
- $apache_user = (function_exists('posix_getuid')) ? posix_getuid() : 'unknown';
- $apache_user = (function_exists('posix_getpwuid')) ? posix_getpwuid($apache_user) : $apache_user;
- }
-
- // setup checks
- // logging
- if ($config['logging']['enabled']) {
- if (!is_writable($config['logging']['path'])) {
- $error[] = "Logging is enabled but we can't write in the logfile path";
- }
- }
-
- // check if fees are 0 and ap/mp tx fees are also set to 0 -> issue #2424
- if ($config['fees'] == 0 && ($config['txfee_auto'] == 0 || $config['txfee_manual'] == 0)) {
- $notice[] = "Having your pool fees set to 0 and tx fees also set to 0 can cause a problem where the wallet cannot payout, consider setting the txfee to a very low amount, ie. 0.0001 to avoid this.";
- }
-
- // check if memcache isn't available but enabled in config -> error
- if (!class_exists('Memcached') && $config['memcache']['enabled']) {
- $error[] = "You have memcached enabled in your config and it's not available as a PHP module. Install the package on your system.";
- }
-
- // if it's not enabled, test it if it exists, if it works -> error tell them to enable, -> otherwise notice it's disabled
- if (!$config['memcache']['enabled']) {
- if (PHP_OS == 'WINNT') {
- require_once(CLASS_DIR . 'memcached.class.php');
- }
- if (class_exists('Memcached')) {
- $memcache_test = @new Memcached();
- if ($config['memcache']['sasl'] === true) {
- $memcache_test->setOption(Memcached::OPT_BINARY_PROTOCOL, true);
- $memcache_test->setSaslAuthData($config['memcache']['sasl']['username'], $config['memcache']['sasl']['password']);
- }
- $memcache_test_add = @$memcache_test->addServer($config['memcache']['host'], $config['memcache']['port']);
- $randmctv = rand(5,10);
- $memcache_test_set = @$memcache_test->set('test_mpos_setval', $randmctv);
- $memcache_test_get = @$memcache_test->get('test_mpos_setval');
- }
- if (class_exists('Memcached') && $memcache_test_get == $randmctv) {
- $error[] = "You have memcache disabled in the config but it's available and works! Enable it for best performance.";
- } else {
- $notice[] = "Memcache is disabled; Almost every linux distro has packages for it, you should be using it if you can.";
- }
- }
-
- // check if htaccess exists
- if (!file_exists(BASEPATH.".htaccess")) {
- $htaccess_link = ".htaccess";
- $notice[] = "You don't seem to have a .htaccess in your public folder, if you're using Apache set it up: $htaccess_link";
- }
-
- // check if we can write templates/cache and templates/compile -> error
- if (!is_writable(TEMPLATE_DIR . '/cache')) {
- $error[] = "templates/cache folder is not writable for uid {$apache_user['name']}";
- }
- if (!is_writable(TEMPLATE_DIR . '/compile')) {
- $error[] = "templates/compile folder is not writable for uid {$apache_user['name']}";
- }
-
- // check if we can write the config files, we should NOT be able to -> error
- if (is_writable(INCLUDE_DIR.'/config/global.inc.php') || is_writable(INCLUDE_DIR.'/config/global.inc.dist.php') ||
- is_writable(INCLUDE_DIR.'/config/security.inc.php') || is_writable(INCLUDE_DIR.'/config/security.inc.dist.php')) {
- $error[] = "Your config files SHOULD NOT be writable to this user!";
- }
-
- // check if daemon can connect -> error
- try {
- if ($bitcoin->can_connect() !== true) {
- $error[] = "Unable to connect to coin daemon using provided credentials";
- }
- else {
- // validate that the wallet service is not in test mode
- if ($bitcoin->is_testnet() == true) {
- $error[] = "The coin daemon service is running as a testnet. Check the TESTNET setting in your coin daemon config and make sure the correct port is set in the MPOS config.";
- }
-
- // if coldwallet is not empty, check if the address is valid -> error
- if (!empty($config['coldwallet']['address'])) {
- if (!$bitcoin->validateaddress($config['coldwallet']['address']))
- $error[] = "Your cold wallet address is SET and INVALID";
- }
-
- // check if there is more than one account set on wallet
- $accounts = $bitcoin->listaccounts();
- if (count($accounts) > 1 && $accounts[''] <= 0) {
- $error[] = "There are " . count($accounts) . " Accounts set in local Wallet and Default Account has no liquid funds to pay your miners!";
- }
- }
- } catch (Exception $e) {
- }
- // check anti DOS protection, we need memcache for that
- if ($config['mc_antidos'] && !$config['memcache']['enabled']) {
- $error[] = "mc_antidos is enabled and memcache is not, memcache is required to use this";
- }
-
- // poke stratum using gettingstarted details -> enotice
- if (function_exists('socket_create')) {
- $host = @gethostbyname($config['gettingstarted']['stratumurl']);
- $port = $config['gettingstarted']['stratumport'];
-
- if (isset($host) and
- isset($port) and
- ($socket=socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) and
- (socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, array('sec' => 3, 'usec' => 0))) and
- (@socket_connect($socket, $host, $port)))
- {
- socket_close($socket);
- } else {
- $enotice[] = 'We tried to poke your Stratum server using your $config[\'gettingstarted\'] settings but it didn\'t respond - ' . socket_strerror(socket_last_error());
- }
- } else {
- // Connect via fsockopen as fallback
- if (! $fp = @fsockopen($config['gettingstarted']['stratumurl'], $config['gettingstarted']['stratumport'], $errCode, $errStr, 1)) {
- $enotice[] = 'We tried to poke your Stratum server using your $config[\'gettingstarted\'] settings but it didn\'t respond';
- }
- @fclose($fp);
- }
-
- // security checks
- // salts too short -> notice, salts default -> error
- if ((strlen($config['SALT']) < 24) || (strlen($config['SALTY']) < 24) || $config['SALT'] == 'PLEASEMAKEMESOMETHINGRANDOM' || $config['SALTY'] == 'THISSHOULDALSOBERRAANNDDOOM') {
- if ($config['SALT'] == 'PLEASEMAKEMESOMETHINGRANDOM' || $config['SALTY'] == 'THISSHOULDALSOBERRAANNDDOOM') {
- $error[] = "You absolutely SHOULD NOT leave your SALT or SALTY default changing them will require registering again";
- } else {
- $notice[] = "SALT or SALTY is too short, they should be more than 24 characters and changing them will require registering again";
- }
- }
-
- // display the errors
- foreach ($enotice as $en) {
- $_SESSION['POPUP'][] = array('CONTENT' => $en, 'TYPE' => 'alert alert-info');
- }
- if (!count($notice) && !count($error)) {
- $_SESSION['POPUP'][] = array('CONTENT' => 'The config options we checked seem OK', 'TYPE' => 'alert alert-success');
- } else {
- foreach ($notice as $n) {
- $_SESSION['POPUP'][] = array('CONTENT' => $n, 'TYPE' => 'alert alert-warning');
- }
- foreach ($error as $e) {
- $_SESSION['POPUP'][] = array('CONTENT' => $e, 'TYPE' => 'alert alert-danger');
- }
- }
-}
diff --git a/include/pages/admin/checks/check_daemon.inc.php b/include/pages/admin/checks/check_daemon.inc.php
new file mode 100644
index 00000000..a87b2cf7
--- /dev/null
+++ b/include/pages/admin/checks/check_daemon.inc.php
@@ -0,0 +1,38 @@
+ error
+try {
+ if ($bitcoin->can_connect() !== true) {
+ $newerror = array();
+ $newerror['name'] = "Coin daemon";
+ $newerror['description'] = "Unable to connect to coin daemon using provided credentials.";
+ $newerror['configvalue'] = "wallet.*";
+ $newerror['helplink'] = "https://github.com/MPOS/php-mpos/wiki/Config-Setup#wiki-local-wallet-rpc";
+ $error[] = $newerror;
+ $newerror = null;
+ }
+ else {
+ // validate that the wallet service is not in test mode
+ if ($bitcoin->is_testnet() == true) {
+ $newerror = array();
+ $newerror['name'] = "Coin daemon";
+ $newerror['description'] = "The coin daemon service is running as a testnet. Check the TESTNET setting in your coin daemon config and make sure the correct port is set in the MPOS config.";
+ $newerror['configvalue'] = "wallet.host";
+ $newerror['helplink'] = "https://github.com/MPOS/php-mpos/wiki/Config-Setup#wiki-local-wallet-rpc";
+ $error[] = $newerror;
+ $newerror = null;
+ }
+ // check if there is more than one account set on wallet
+ $accounts = $bitcoin->listaccounts();
+ if (count($accounts) > 1 && $accounts[''] <= 0) {
+ $newerror = array();
+ $newerror['name'] = "Coin daemon";
+ $newerror['description'] = "There are " . count($accounts) . " Accounts set in local Wallet and Default Account has no liquid funds to pay your miners!";
+ $newerror['configvalue'] = "wallet.host";
+ $newerror['helplink'] = "https://github.com/MPOS/php-mpos/wiki/Config-Setup#wiki-local-wallet-rpc";
+ $error[] = $newerror;
+ $newerror = null;
+ }
+ }
+} catch (Exception $e) {}
diff --git a/include/pages/admin/checks/check_fees.inc.php b/include/pages/admin/checks/check_fees.inc.php
new file mode 100644
index 00000000..8f9f006f
--- /dev/null
+++ b/include/pages/admin/checks/check_fees.inc.php
@@ -0,0 +1,13 @@
+ issue #2424
+if ($config['fees'] == 0 && ($config['txfee_auto'] == 0 || $config['txfee_manual'] == 0)) {
+ $newerror = array();
+ $newerror['name'] = "Fees and TX Fees 0";
+ $newerror['description'] = "Having your pool fees set to 0 and tx fees also set to 0 can cause a problem where the wallet cannot payout, consider setting the txfee to a very low amount, ie. 0.0001 to avoid this.";
+ $newerror['configvalue'] = "fees";
+ $newerror['helplink'] = "https://github.com/MPOS/php-mpos/issues/2424";
+ $error[] = $newerror;
+ $newerror = null;
+}
diff --git a/include/pages/admin/checks/check_memcache.inc.php b/include/pages/admin/checks/check_memcache.inc.php
new file mode 100644
index 00000000..aa300673
--- /dev/null
+++ b/include/pages/admin/checks/check_memcache.inc.php
@@ -0,0 +1,59 @@
+ error
+if (!class_exists('Memcached') && $config['memcache']['enabled']) {
+ $newerror = array();
+ $newerror['name'] = "Memcache Config";
+ $newerror['description'] = "You have memcached enabled in your config and it's not available as a PHP module. Install the package on your system.";
+ $newerror['configvalue'] = "memcache.enabled";
+ $newerror['helplink'] = "https://github.com/MPOS/php-mpos/wiki/Config-Setup#wiki-memcache";
+ $error[] = $newerror;
+ $newerror = null;
+}
+
+// if it's not enabled, test it if it exists, if it works -> error tell them to enable, -> otherwise notice it's disabled
+if (!$config['memcache']['enabled']) {
+ if (PHP_OS == 'WINNT') {
+ require_once(CLASS_DIR . 'memcached.class.php');
+ }
+ if (class_exists('Memcached')) {
+ $memcache_test = @new Memcached();
+ if ($config['memcache']['sasl'] === true) {
+ $memcache_test->setOption(Memcached::OPT_BINARY_PROTOCOL, true);
+ $memcache_test->setSaslAuthData($config['memcache']['sasl']['username'], $config['memcache']['sasl']['password']);
+ }
+ $memcache_test_add = @$memcache_test->addServer($config['memcache']['host'], $config['memcache']['port']);
+ $randmctv = rand(5,10);
+ $memcache_test_set = @$memcache_test->set('test_mpos_setval', $randmctv);
+ $memcache_test_get = @$memcache_test->get('test_mpos_setval');
+ }
+ if (class_exists('Memcached') && $memcache_test_get == $randmctv) {
+ $newerror = array();
+ $newerror['name'] = "Memcache Config";
+ $newerror['description'] = "You have memcache disabled in the config but it's available and works! Enable it for best performance.";
+ $newerror['configvalue'] = "memcache.enabled";
+ $newerror['helplink'] = "https://github.com/MPOS/php-mpos/wiki/Config-Setup#wiki-memcache";
+ $error[] = $newerror;
+ $newerror = null;
+ } else {
+ $newerror = array();
+ $newerror['name'] = "Memcache Config";
+ $newerror['description'] = "Memcache is disabled; Almost every linux distro has packages for it, you should be using it if you can.";
+ $newerror['configvalue'] = "memcache.enabled";
+ $newerror['helplink'] = "https://github.com/MPOS/php-mpos/wiki/Config-Setup#wiki-memcache";
+ $error[] = $newerror;
+ $newerror = null;
+ }
+}
+
+// check anti DOS protection, we need memcache for that
+if ($config['mc_antidos'] && !$config['memcache']['enabled']) {
+ $newerror = array();
+ $newerror['name'] = "Memcache Config";
+ $newerror['description'] = "mc_antidos is enabled and memcache is not, memcache is required to use this.";
+ $newerror['configvalue'] = "memcache.enabled";
+ $newerror['helplink'] = "https://github.com/MPOS/php-mpos/wiki/Config-Setup#memcache-rate-limiting";
+ $error[] = $newerror;
+ $newerror = null;
+}
diff --git a/include/pages/admin/checks/check_permissions.inc.php b/include/pages/admin/checks/check_permissions.inc.php
new file mode 100644
index 00000000..c0cf31bd
--- /dev/null
+++ b/include/pages/admin/checks/check_permissions.inc.php
@@ -0,0 +1,47 @@
+ error
+if (!is_writable(TEMPLATE_DIR . '/cache')) {
+ $newerror = array();
+ $newerror['name'] = "templates/cache permissions";
+ $newerror['description'] = "templates/cache folder is not writable for uid {$apache_user['name']}";
+ $newerror['configvalue'] = "templates/cache folder";
+ $newerror['helplink'] = "https://github.com/MPOS/php-mpos/wiki/Quick-Start-Guide#configuration-1";
+ $error[] = $newerror;
+ $newerror = null;
+}
+if (!is_writable(TEMPLATE_DIR . '/compile')) {
+ $newerror = array();
+ $newerror['name'] = "templates/compile permissions";
+ $newerror['description'] = "templates/compile folder is not writable for uid {$apache_user['name']}";
+ $newerror['configvalue'] = "templates/compile folder";
+ $newerror['helplink'] = "https://github.com/MPOS/php-mpos/wiki/Quick-Start-Guide#configuration-1";
+ $error[] = $newerror;
+ $newerror = null;
+}
+
+// check if we can write the config files, we should NOT be able to -> error
+if (is_writable(INCLUDE_DIR.'/config/global.inc.php') || is_writable(INCLUDE_DIR.'/config/global.inc.dist.php') ||
+ is_writable(INCLUDE_DIR.'/config/security.inc.php') || is_writable(INCLUDE_DIR.'/config/security.inc.dist.php')) {
+ $newerror = array();
+ $newerror['name'] = "Config permissions";
+ $newerror['description'] = "Your config files SHOULD NOT be writable to this user!";
+ $newerror['configvalue'] = "global.inc.php and security.inc.php";
+ $newerror['helplink'] = "https://github.com/MPOS/php-mpos/wiki/Quick-Start-Guide#configuration-1";
+ $error[] = $newerror;
+ $newerror = null;
+}
diff --git a/include/pages/admin/checks/check_security.inc.php b/include/pages/admin/checks/check_security.inc.php
new file mode 100644
index 00000000..569f2209
--- /dev/null
+++ b/include/pages/admin/checks/check_security.inc.php
@@ -0,0 +1,29 @@
+SHOULD NOT leave your SALT or SALTY default changing them will require registering again.";
+ } else {
+ $newerror['description'] = "SALT or SALTY is too short, they should be more than 24 characters and changing them will require registering again.
";
+ }
+ $newerror['configvalue'] = "SALT";
+ $newerror['helplink'] = "https://github.com/MPOS/php-mpos/wiki/Config-Setup#wiki-defines--salts";
+ $error[] = $newerror;
+ $newerror = null;
+}
+
+// check if htaccess exists
+if (!file_exists(BASEPATH.".htaccess")) {
+ $newerror = array();
+ $newerror['name'] = ".htaccess";
+ $htaccess_link = ".htaccess";
+ $newerror['description'] = "You don't seem to have a .htaccess in your public folder, if you're using Apache set it up: $htaccess_link";
+ $newerror['configvalue'] = ".htaccess";
+ $newerror['helplink'] = "https://github.com/MPOS/php-mpos/wiki";
+ $error[] = $newerror;
+ $newerror = null;
+}
diff --git a/include/pages/admin/checks/check_stratum.inc.php b/include/pages/admin/checks/check_stratum.inc.php
new file mode 100644
index 00000000..25b7b7af
--- /dev/null
+++ b/include/pages/admin/checks/check_stratum.inc.php
@@ -0,0 +1,36 @@
+ enotice
+if (function_exists('socket_create')) {
+ $host = @gethostbyname($config['gettingstarted']['stratumurl']);
+ $port = $config['gettingstarted']['stratumport'];
+ if (isset($host) and
+ isset($port) and
+ ($socket=socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) and
+ (socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, array('sec' => 3, 'usec' => 0))) and
+ (@socket_connect($socket, $host, $port)))
+ {
+ socket_close($socket);
+ } else {
+ $newerror = array();
+ $newerror['name'] = "Stratum information";
+ $newerror['description'] = "We tried to poke your Stratum server using your \$config['gettingstarted'] settings but it didn't respond - " . socket_strerror(socket_last_error()) . ".";
+ $newerror['configvalue'] = "gettingstarted";
+ $newerror['helplink'] = "https://github.com/MPOS/php-mpos/wiki/Config-Setup#wiki-getting-started";
+ $error[] = $newerror;
+ $newerror = null;
+ }
+} else {
+ // Connect via fsockopen as fallback
+ if (! $fp = @fsockopen($config['gettingstarted']['stratumurl'], $config['gettingstarted']['stratumport'], $errCode, $errStr, 1)) {
+ $newerror = array();
+ $newerror['name'] = "Stratum information";
+ $newerror['description'] = "We tried to poke your Stratum server using your \$config['gettingstarted'] settings but it didn't respond.";
+ $newerror['configvalue'] = "gettingstarted";
+ $newerror['helplink'] = "https://github.com/MPOS/php-mpos/wiki/Config-Setup#wiki-getting-started";
+ $error[] = $newerror;
+ $newerror = null;
+ }
+ @fclose($fp);
+}
diff --git a/include/pages/admin/setup.inc.php b/include/pages/admin/setup.inc.php
new file mode 100644
index 00000000..cef64d14
--- /dev/null
+++ b/include/pages/admin/setup.inc.php
@@ -0,0 +1,42 @@
+isAuthenticated() || !$user->isAdmin($_SESSION['USERDATA']['id'])) {
+ header("HTTP/1.1 404 Page not found");
+ die("404 Page not found");
+}
+
+if (@$_SESSION['USERDATA']['is_admin'] && $user->isAdmin(@$_SESSION['USERDATA']['id'])) {
+ if (!include_once(INCLUDE_DIR . '/lib/jsonRPCClient.php')) die('Unable to load libs');
+ $error = array();
+
+ if ($config['skip_config_tests']) {
+ $newerror = array();
+ $newerror['name'] = "Config tests skipped";
+ $newerror['description'] = "Config tests are disabled. Enable them in the global config to run them again.";
+ $newerror['configvalue'] = "skip_config_tests";
+ $newerror['helplink'] = "https://github.com/MPOS/php-mpos/wiki/Config-Setup#config-check";
+ $error[] = $newerror;
+ $newerror = null;
+ } else {
+ // setup some basic stuff for checking - getuid/getpwuid not available on mac/windows
+ $apache_user = 'unknown';
+ if (substr_count(strtolower(PHP_OS), 'nix') > 0 || substr_count(strtolower(PHP_OS), 'linux') > 0) {
+ $apache_user = (function_exists('posix_getuid')) ? posix_getuid() : 'unknown';
+ $apache_user = (function_exists('posix_getpwuid')) ? posix_getpwuid($apache_user) : $apache_user;
+ }
+
+ // we want to load anything in checks/ that is check_*.inc.php
+ $potentialfiles = scandir(__DIR__."/checks/");
+ foreach ($potentialfiles as $pf) {
+ if ($pf == '.' || $pf == '..') continue;
+ if (preg_match("/check_+.+\.inc\.php/", $pf)) {
+ include_once("checks/".$pf);
+ }
+ }
+ }
+ $smarty->assign("ERRORS", $error);
+}
+
+$smarty->assign("CONTENT", "default.tpl");
diff --git a/public/index.php b/public/index.php
index 1725eea2..1604b945 100644
--- a/public/index.php
+++ b/public/index.php
@@ -101,8 +101,8 @@ if (count(@$_SESSION['last_ip_pop']) == 2) {
// version check and config check if not disabled
if (@$_SESSION['USERDATA']['is_admin'] && $user->isAdmin(@$_SESSION['USERDATA']['id'])) {
- if (!@$config['skip_config_tests']) {
- require_once(INCLUDE_DIR . '/admin_checks.php');
+ if (!@$config['skip_config_tests'] && @$_GET['action'] != 'setup') {
+ $_SESSION['POPUP'][] = array('CONTENT' => "You haven't turned off config checks, visit the setup page for further information.", 'DISMISS' => 'yes', 'ID' => 'lastlogin', 'TYPE' => 'alert alert-info');
}
}
diff --git a/templates/bootstrap/admin/setup/default.tpl b/templates/bootstrap/admin/setup/default.tpl
new file mode 100644
index 00000000..346d19e8
--- /dev/null
+++ b/templates/bootstrap/admin/setup/default.tpl
@@ -0,0 +1,21 @@
+
+
+
+
+ Setup Checks
+ To disable these checks, set skip_config_tests to true in global.inc.php
+
+
+ {if $ERRORS|@count > 0}
+ {section errors $ERRORS}
+
{$ERRORS[errors].name}
+
{$ERRORS[errors].description}
+
See: {$ERRORS[errors].configvalue}
+
+ {/section}
+ {/if}
+
+
+
+
+
diff --git a/templates/bootstrap/global/navigation.tpl b/templates/bootstrap/global/navigation.tpl
index a2f0dd41..2e57aac8 100644
--- a/templates/bootstrap/global/navigation.tpl
+++ b/templates/bootstrap/global/navigation.tpl
@@ -30,6 +30,7 @@
System