old_cacti_version = get_cacti_db_version_raw();
$this->setRuntime($install_params['Runtime'] ?? 'unknown');
$step = read_config_option('install_step', true);
log_install_high('step', 'Initial: ' . clean_up_lines(var_export($step, true)));
if (empty($step)) {
$step = $this->getStepDefault();
}
$this->stepError = false;
if ($step == Installer::STEP_INSTALL) {
$install_version = read_config_option('install_version', true);
log_install_high('step', 'Previously complete: ' . clean_up_lines(var_export($install_version, true)));
if (empty($install_version)) {
$install_version = $this->old_cacti_version;
}
$install_params = [];
$install_error = read_config_option('install_error', true);
if (!empty($install_error)) {
$step = Installer::STEP_ERROR;
} elseif (!is_install_needed($install_version)) {
log_install_debug('step', 'Does match: ' . clean_up_lines(var_export($this->old_cacti_version, true)));
}
} elseif ($step >= Installer::STEP_COMPLETE) {
$install_version = read_config_option('install_version', true);
log_install_high('step', 'Previously complete: ' . clean_up_lines(var_export($install_version, true)));
if (is_install_needed($install_version)) {
log_install_debug('step', 'Does not match: ' . clean_up_lines(var_export(CACTI_VERSION_FULL, true)));
$this->stepError = Installer::STEP_WELCOME;
db_execute('DELETE FROM settings WHERE name LIKE \'install_%\'');
} else {
$install_params = [];
}
}
log_install_high('step', 'After: ' . clean_up_lines(var_export($step, true)));
$this->setStep($step);
$this->iconClass = [
DB_STATUS_ERROR => 'ti ti-thumb-down',
DB_STATUS_WARNING => 'ti ti-alert-triangle-filled',
DB_STATUS_RESTART => 'ti ti-alert-triangle-filled',
DB_STATUS_SUCCESS => 'ti ti-thumb-up',
DB_STATUS_SKIPPED => 'fa-solid fa-circle-check'
];
$this->defaultAutomation = [
[
'name' => 'Net-SNMP Device',
'hash' => '07d3fe6a52915f99e642d22e27d967a4',
'sysDescrMatch' => 'Linux',
'sysNameMatch' => '',
'sysOidMatch' => '',
'availMethod' => 2,
'sequence' => 1
],
[
'name' => 'Windows Device',
'hash' => '5b8300be607dce4f030b026a381b91cd',
'sysDescrMatch' => 'Windows',
'sysNameMatch' => '',
'sysOidMatch' => '',
'availMethod' => 2,
'sequence' => 2
],
[
'name' => 'Cisco Router',
'hash' => 'cae6a879f86edacb2471055783bec6d0',
'sysDescrMatch' => '(Cisco Internetwork Operating System Software|IOS)',
'sysNameMatch' => '',
'sysOidMatch' => '',
'availMethod' => 2,
'sequence' => 3
]
];
$this->errors = [];
$this->templates = [];
$this->eula = intval(read_config_option('install_eula', true));
$this->cronInterval = intval(read_config_option('cron_interval', true));
$this->locales = get_installed_locales();
$this->stepData = null;
$this->setLanguage($this->getLanguage());
$this->setTheme($this->getTheme());
if (empty($this->theme)) {
$this->setTheme('modern');
}
if ($step < Installer::STEP_INSTALL) {
$this->setDefaults($install_params);
}
log_install_debug('step', 'Error: ' . clean_up_lines(var_export($this->stepError, true)));
log_install_debug('step', 'Done: ' . clean_up_lines(var_export($this->stepCurrent, true)));
}
/**
* jsonSerialize() - provides JSON object of return data with optional
* values output dependant on Runtime mode.
*
* @return array - An array of options data
*/
public function jsonSerialize(): array {
if (empty($this->output)) {
$this->output = $this->processCurrentStep();
}
$basics = [
'Mode' => $this->mode,
'Step' => $this->stepCurrent,
'Errors' => $this->errors
];
$webdata = [];
// Are we running in either Web or Json mode?
if ($this->runtime == 'Web' || $this->runtime == 'Json') {
$webdata += [
'Eula' => $this->eula,
'Prev' => $this->buttonPrevious,
'Next' => $this->buttonNext,
'Test' => $this->buttonTest,
'Theme' => $this->theme,
'StepData' => $this->stepData,
];
}
// Are we running in only Web mode?
if ($this->runtime == 'Web') {
$webdata += [
'Html' => $this->output,
];
}
return array_merge($basics, $webdata);
}
/**
* getData() - alias for jsonSerialize()
*
* @return array - A serialized json string
*/
public function getData() : array {
return $this->jsonSerialize();
}
/**
* getErrors() - retrieve an array of all recorded errors
*
* @return array - An array of errors
*/
public function getErrors() : array {
return $this->errors;
}
/**
* processParameters - process array of parameters to override defaults
* matches XXX from setXXX/getXXX functions
*
* @param array $params_install - array of parameters to process where key
*
* @return void
*/
protected function processParameters($params_install = []) {
// Always set the current step first
if (isset($params_install['Step'])) {
$this->setStep($params_install['Step']);
unset($params_install['Step']);
}
foreach ($params_install as $key => $value) {
switch ($key) {
case 'Mode':
$this->setMode($value);
break;
case 'Eula':
$this->setEula($value);
break;
case 'Prev':
$this->buttonPrevious = new InstallerButton($value);
break;
case 'Next':
$this->buttonNext = new InstallerButton($value);
break;
case 'Test':
$this->buttonTest = new InstallerButton($value);
break;
case 'Language':
$this->setLanguage($value);
break;
case 'Profile':
$this->setProfile($value);
break;
case 'CronInterval':
$this->setCronInterval($value);
break;
case 'DefaultTemplate':
$this->setDefaultTemplate($value);
break;
case 'AutomationMode':
$this->setAutomationMode(intval($value));
break;
case 'AutomationOverride':
$this->setAutomationOverride($value);
break;
case 'AutomationRange':
$this->setAutomationRange($value);
break;
case 'Templates':
$this->setTemplates($value);
break;
case 'Tables':
$this->setTables($value);
break;
case 'Paths':
$this->setPaths($value);
break;
case 'RRDVersion':
$this->setRRDVersion($value);
break;
case 'Theme':
$this->setTheme($value);
break;
case 'SnmpOptions':
$this->setSnmpOptions($value);
break;
case 'AdminEmailAddress':
$this->setAdminEmailAddress($value);
break;
case 'Runtime':
break;
default:
log_install_always('badkey', "$key => $value");
}
}
}
/**
* setDefaults - apply default values from array object
*
* @param array $install_params - optional key/value array where key matches
*
* @return void
*/
private function setDefaults($install_params = []) : void {
$this->tables = $this->getTables();
// You have to set the paths before you start executing commands
$this->paths = install_file_paths();
$this->setPaths($this->getPaths());
$this->permissions = $this->getPermissions();
$this->modules = $this->getModules();
$this->setDefaultTemplate($this->getDefaultTemplate());
$this->setProfile($this->getProfile());
$this->setAutomationMode($this->getAutomationMode());
$this->setAutomationOverride($this->getAutomationOverride());
$this->setAutomationRange($this->getAutomationRange());
$this->setRRDVersion($this->getRRDVersion(), 'default ');
$this->snmpOptions = $this->getSnmpOptions();
$this->setAdminEmailAddress($this->getAdminEmailAddress());
$this->setMode($this->getMode());
log_install_high('', 'Installer::processParameters(' . clean_up_lines(json_encode($install_params)) . ')');
if (!empty($install_params)) {
$this->processParameters($install_params);
}
if ($this->stepError !== false && $this->stepCurrent > $this->stepError) {
$this->setStep($this->stepError);
}
}
/**
* setTrueFalse() - determine whether param can be mapped to either
* True or False and if so, assign result to $field
*
* @param mixed $param - value to be set if it can be mapped to True or False
* @param mixed $field - variable reference to be set
* @param string $option - name of the option
* @param bool $save - Indication to save state
*
* @return bool - The result of the function
*/
private function setTrueFalse(mixed $param, mixed &$field, string $option = '', bool $save = true) : bool {
$value = null;
if ($param === true || $param === 'true' || $param === 'on' || $param === 1 || $param === '1') {
$value = true;
} elseif ($param === false || $param === 'false' || $param === '' || $param === 0 || $param === '0') {
$value = false;
}
if ($value !== null) {
$field = $value;
if ($save) {
set_install_config_option('install_' . $option, $param);
}
}
$result = $value !== null;
log_install_high('', "setTrueFalse($option, " . var_export($param, true) . " sets $value, returns $result");
return $result;
}
/**
* addError() - adds a new error to the array or updates an existing one
* @param int $step Which step of the installer reports the error and
* should be Installer::STEP_ constant
* @param string $section Title of section causing a problem
* @param string $item Individual item that caused the problem
* @param mixed $text Descriptive text of the error
*
* @return void
*/
public function addError(int $step, string $section, string $item, mixed $text = false) : void {
if (!isset($this->errors[$section])) {
$this->errors[$section] = [];
}
if ($text === false) {
$this->errors[$section][] = $item;
log_install_medium('errors', "addError($section, $item)");
} else {
$this->errors[$section][$item] = $text;
log_install_medium('errors', "addError($section, $item, $text)");
}
log_install_debug('errors', 'stepError = ' . $step . ' -> ' . clean_up_lines(var_export($this->stepError, true)));
if ($this->stepError === false || $this->stepError > $step) {
$this->stepError = $step;
}
log_install_debug('errors-json', clean_up_lines(var_export($this->errors, true)));
}
/**
* setProgress() - set the progress point of Installer::STEP_INSTALL
*
* @param int $param_progress One of Installer::PROGRESS_ constants
*
* @return void
*/
private function setProgress(int $param_progress) : void {
log_install_medium('', "Progress: $param_progress");
set_install_config_option('install_progress', $param_progress);
set_install_config_option('install_updated', microtime(true));
}
/**
* sanitizeRRDVersion() - ensure version number is valid
*
* @param string $param_rrdver The version to be sanitized
* @param string $default_version The default version if any
*
* @return string The rrdtool version
*/
private function sanitizeRRDVersion(string $param_rrdver = '', string $default_version = '') : string {
$rrdver = $default_version;
if ($param_rrdver != '') {
log_install_debug('rrdversion', 'sanitizeRRDVersion() - Checking for version string');
if (preg_match("/(?:version|v)*\s*((?:[0-9]+\.?)+)/i", $param_rrdver, $matches)) {
log_install_debug('rrdversion', 'sanitizeRRDVersion() - Checking for version string - ' . (cacti_sizeof($matches) - 1) . ' matches found');
if (cacti_sizeof($matches) > 1) {
log_install_debug('rrdversion', 'sanitizeRRDVersion() - Comparing ' . $param_rrdver . ' <= 1.3');
if (cacti_version_compare('1.3', $matches[1], '<=')) {
$rrdver = $matches[1];
log_install_debug('rrdversion', 'sanitizeRRDVersion() - Valid version');
}
}
}
} elseif (!empty($default_version)) {
$rrdver = $this->sanitizeRRDVersion($default_version, '1.3');
}
log_install_medium('rrdversion', 'sanitizeRRDVersion() - returning ' . $rrdver);
return $rrdver;
}
/*
* *
* Setters/Getters *
* --------------- *
* *
* This section contains various setXXX and getXXX functions *
* to prime the installer. Most of these functions are private *
* to prevent external usage which must come via the installData *
* parameter when creating a new Installer class object *
* *
*/
/**
* getPermissions() - gets the permissions for folders that we require
* to be available for writing during install or
* always (after install)
*
* @return array An array of permissions
*/
private function getPermissions() : array {
global $config;
$permissions = ['install' => [], 'always' => []];
$install_paths = [
'snmp_queries' => CACTI_PATH_RESOURCE . '/snmp_queries',
'script_server' => CACTI_PATH_RESOURCE . '/script_server',
'script_queries' => CACTI_PATH_RESOURCE . '/script_queries',
'scripts' => CACTI_PATH_SCRIPTS . '',
];
$always_paths = [
'sys_temp' => sys_get_temp_dir(),
'log' => CACTI_PATH_LOG . '',
'boost' => CACTI_PATH_CACHE . '/boost',
'mibcache' => CACTI_PATH_CACHE . '/mibcache',
'realtime' => CACTI_PATH_CACHE . '/realtime',
'spikekill' => CACTI_PATH_CACHE . '/spikekill'
];
$csrf_path = CACTI_CSRF_SECRET;
if (is_dir($csrf_path)) {
$csrf_path = rtrim($csrf_path ?? '', '/') . '/csrf-secret.php';
}
$install_paths['csrf'] = $csrf_path;
$install_key = 'always';
if ($this->mode != Installer::MODE_POLLER) {
$install_key = 'install';
}
foreach ($install_paths as $name => $path) {
if (is_dir($path)) {
$valid = (is_resource_writable($path . '/'));
$permissions[$install_key][$path . '/'] = $valid;
} else {
$valid = false;
// Lets see if this is the CSRF secret
if ($name == 'csrf') {
// Does it exist?
if (file_exists($path)) {
// Lets get the contents and make sure we have something at least
$csrf_secret = file_get_contents($path);
$valid = !empty($csrf_secret);
}
}
// If we aren't valid at this point, we aren't CSRF
// or the CSRF was empty
if (!$valid) {
// Lets me sure this path is writable
$valid = (is_resource_writable($path));
}
$permissions[$install_key][$path] = $valid;
}
log_install_debug('permission',"($name) $path = $valid ($install_key)");
if (!$valid) {
$this->addError(Installer::STEP_PERMISSION_CHECK, 'Permission', $name . ':' . $path, __('Path was not writable'));
}
}
foreach ($always_paths as $name => $path) {
$path = rtrim($path, '/') . '/';
$valid = (is_resource_writable($path));
$permissions['always'][$path] = $valid;
log_install_debug('permission',"($name) $path = $valid ($install_key)");
if (!$valid) {
$this->addError(Installer::STEP_PERMISSION_CHECK, 'Permission', $name . ':' . $path, __('Path was not writable'));
}
}
return $permissions;
}
/**
* setRuntime() - sets the runtime mode of the Installer
*
* @param string $param_runtime Default is 'unknown', acceptable modes are 'Cli'
* and 'Json'.
*
* @return void
*/
public function setRuntime(string $param_runtime = 'unknown') : void {
if ($param_runtime == 'Web' || $param_runtime == 'Cli' || $param_runtime == 'Json') {
$this->runtime = $param_runtime;
} else {
$this->addError(Installer::STEP_WELCOME, '', 'Failed to apply specified runtime');
}
}
private function getLanguage() : string {
$language = read_config_option('install_language');
$section = 'install';
if (empty($language)) {
$language = read_config_option('i18n_default_language');
$section = 'i18n';
if (empty($language) && isset($_SESSION[SESS_USER_ID])) {
$language = read_user_setting('user_language', get_new_user_default_language(), true);
$section = 'user';
}
}
log_install_medium('language', 'getLanguage(): ' . $language . ' [' . $section . ']');
return $language;
}
/**
* setLanguage() - sets the language of the Installer
*
* @param string $param_language Must be a valid language which is returned from
* apply_locale() function located in Core
*
* @return void
*
* Errors: will add an error at STEP_WELCOME if invalid language.
*/
private function setLanguage(string $param_language = '') : void {
if ($param_language != '') {
$language = apply_locale($param_language);
if (empty($language)) {
$this->addError(Installer::STEP_WELCOME, 'Language', 'Failed to apply specified language');
} else {
log_install_debug('language', 'setLanguage(): ' . $param_language);
$this->language = $param_language;
set_install_config_option('i18n_default_language', $param_language);
set_install_config_option('install_language', $param_language);
if (isset($_SESSION[SESS_USER_ID])) {
$_SESSION[SESS_USER_LANGUAGE] = $param_language;
set_user_setting('user_language', $param_language);
}
$test_i18n = read_config_option('i18n_default_language');
$test_install = read_config_option('install_language');
log_install_debug('language','setLanguage(): ' . $test_i18n . ' / ' . $test_install);
}
}
}
/**
* setEula() - sets whether the Eula was accepted or not
*
* @param $string param_eula Valid values are 'Accepted', 'True'
*
* @return void
*
* Errors: will add an error at STEP_WELCOME if not accepted.
*/
private function setEula(string $param_eula = '') : void {
if ($param_eula == 'Accepted' || $param_eula === 'true') {
$param_eula = 1;
} elseif (!is_numeric($param_eula)) {
$param_eula = 0;
}
$this->eula = $param_eula;
if (!$this->eula) {
$this->addError(Installer::STEP_WELCOME, 'Eula', 'Eula not accepted');
}
set_install_config_option('install_eula', $this->eula);
}
/**
* getRRDVersion() - gets the RRDVersion from the system or if overridden
* during the installer, from the installer option
*
* @return string
*/
private function getRRDVersion() : string {
$rrdver = read_config_option('install_rrdtool_version', true);
if (empty($rrdver)) {
log_install_high('rrdversion', 'getRRDVersion(): Getting installed tool version');
$rrdver = get_installed_rrdtool_version();
}
log_install_medium('rrdversion', 'getRRDVersion(): ' . $rrdver);
return $rrdver;
}
/**
* setCSRFSecret() - Initializes the csrf secret file for
* csrf protection.
*
* @return void
*/
private function setCSRFSecret() : void {
$this->setProgress(Installer::PROGRESS_CSRF_BEGIN);
if (!CACTI_CSRF_SECRET != '') {
log_install_debug('csrf', 'setCSRFSecret(): secret ' . CACTI_CSRF_SECRET);
$secret = @file_exists(CACTI_CSRF_SECRET) ? file_get_contents(CACTI_CSRF_SECRET) : '';
log_install_debug('csrf', 'setCSRFSecret(): secret ' . (empty($secret) ? 'not ' : '') . 'empty');
if (empty($secret)) {
if (is_resource_writable(CACTI_CSRF_SECRET)) {
log_install_medium('csrf', 'setCSRFSecret(): Updated CSRF secret - "' . CACTI_CSRF_SECRET . '"');
install_create_csrf_secret(CACTI_CSRF_SECRET);
} else {
log_install_high('csrf', 'setCSRFSecret(): Unable to create file - "' . CACTI_CSRF_SECRET . '"');
}
} else {
log_install_debug('csrf', 'setCSRFSecret(): Secret already exists - "' . CACTI_CSRF_SECRET . '"');
}
}
$this->setProgress(Installer::PROGRESS_CSRF_END);
}
/**
* setRRDVersion() - sets the RRDVersion installer option, overrides
* the system default.
*
* @param string $param_rrdver A valid version number.
* @param string $prefix A display prefix, not used in values
*
* @return void
*
* Errors: will add an error at STEP_BINARY_LOCATIONS if invalid version
* was detected.
*/
private function setRRDVersion(string $param_rrdver = '', string $prefix = '') {
if ($param_rrdver != '') {
$rrdver = $this->sanitizeRRDVersion($param_rrdver, '');
if (empty($rrdver)) {
$this->addError(Installer::STEP_BINARY_LOCATIONS, 'RRDVersion', 'setRRDVersion()', __('Failed to set specified %sRRDTool version: %s', $prefix, $param_rrdver));
} else {
$this->paths['rrdtool_version']['default'] = $param_rrdver;
set_install_config_option('install_rrdtool_version', $param_rrdver);
set_install_config_option('rrdtool_version', $param_rrdver);
}
}
}
/**
* getTheme() - gets the current theme
*
* @return string The current theme
*/
private function getTheme() : string {
$theme = read_config_option('install_theme', true);
if (empty($theme)) {
if (isset($_SESSION[SESS_USER_ID])) {
$theme = read_user_setting('selected_theme');
}
if (empty($theme)) {
$theme = read_config_option('selected_theme', true);
}
}
$theme = empty($theme) ? 'modern' : $theme;
log_install_medium('theme', 'getTheme(): ' . $theme);
return $theme;
}
/**
* setTheme() - sets the Theme installer option, override the system default.
*
* @param string $param_theme A valid theme which must exist in /include/themes/
*
* @return void
*
* Errors: will add an error at STEP_BINARY_WELCOME if invalid theme
* was detected.
*/
private function setTheme(string $param_theme = '') : void {
if ($param_theme != '') {
log_install_medium('theme', 'Checking theme: ' . $param_theme);
$themePath = CACTI_PATH_INCLUDE . '/themes/' . $param_theme . '/main.css';
if (file_exists($themePath)) {
log_install_debug('theme', 'setTheme(): ' . $param_theme);
$this->theme = $param_theme;
set_install_config_option('install_theme', $this->theme);
set_install_config_option('selected_theme', $this->theme);
if (isset($_SESSION[SESS_USER_ID])) {
set_user_setting('selected_theme', $this->theme);
$_SESSION['selected_theme'] = $this->theme;
}
} else {
$this->addError(Installer::STEP_WELCOME, 'Theme', __('Invalid Theme Specified'));
}
}
}
/**
* getPaths() - gets the various programs/paths to that are defined in
* $this->paths (setup in constructor) where they are
* either prefixed with path_ or specifically the sendmail
* path. If a default exists, that is usd, otherwise the
* system configuration option is read and set back into
* $this->paths array.
*
* @return array An array of paths
*/
public function getPaths() : array {
$paths = [];
foreach ($this->paths as $name => $array) {
if (str_starts_with($name, 'path_') || $name == 'settings_sendmail_path') {
if (array_key_exists('default', $array)) {
$paths[$name] = $array['default'];
} else {
$paths[$name] = read_config_option($name, true);
}
}
}
return $paths;
}
/**
* setPaths() - sets paths set in the array, checking for a number of
* issues. The array should be key->path which is compared
* against $this->paths
*
* @param array $param_paths A list of paths
*
* @return void
*
* Errors: will add an error to STEP_BINARY_LOCATIONS if a problem is
* found with the value.
*/
private function setPaths(array $param_paths = []) : void {
log_install_debug('paths', 'setPaths(' . $this->stepCurrent . ', ' . cacti_count($param_paths) . ')');
// get all items on the form and write values for them
foreach ($param_paths as $name => $path) {
$key_exists = array_key_exists($name, $this->paths);
$check = $this->paths[$name]['install_check'] ?? 'file_exists';
$optional = $this->paths[$name]['install_optional'] ?? false;
$blank = $this->paths[$name]['install_blank'] ?? false;
log_install_high('paths', sprintf('setPaths(): name: %-25s, key_exists: %-5s, optional: %-5s, check: %s, path: %s', $name, $key_exists, $optional, $check, $path));
if ($key_exists) {
$should_set = true;
if ($blank && empty($path)) {
// allow this
} elseif ($check == 'writable') {
$should_set = is_resource_writable(dirname($path) . '/') || $optional;
if ($should_set) {
$should_set = is_resource_writable($path) || $optional;
}
if ($should_set === false && $optional === false) {
$this->addError(Installer::STEP_BINARY_LOCATIONS, 'Paths', $name, __('Resource is not writable'));
}
} elseif ($check == 'file_exists') {
$should_set = (!empty($path) && file_exists($path)) || $optional;
if (!$should_set) {
$this->addError(Installer::STEP_BINARY_LOCATIONS, 'Paths', $name, __('File not found'));
}
}
if ($should_set && $name == 'path_php_binary') {
$input = mt_rand(2, 64);
$output = shell_exec(
cacti_escapeshellcmd($path) . ' -q ' .
cacti_escapeshellarg(CACTI_PATH_INSTALL . '/cli_test.php') .
' ' . $input
);
if ($output != $input * $input) {
$this->addError(Installer::STEP_BINARY_LOCATIONS, 'Paths', $name, __('PHP did not return expected result'));
$should_set = false;
}
}
if ($should_set) {
unset($this->errors['Paths'][$name]);
set_install_config_option($name, empty($path) ? '' : $path);
}
$this->paths[$name]['default'] = $path;
log_install_debug('paths', sprintf('setPaths(): name: %-25s, key_exists: %-5s, optional: %-5s, should_set: %3s, check: %s', $name, $key_exists, $optional, $should_set ? 'Yes' : 'No', $check));
log_install_debug('paths', sprintf('setPaths(): name: %-25s, data: %s', $name, clean_up_lines(var_export($this->paths[$name], true))));
} else {
$this->addError(Installer::STEP_BINARY_LOCATIONS, 'Paths', $name, __('Unexpected path parameter'));
}
}
}
/**
* getProfile() - gets the data source profile to be used as the system
* default once installation has been completed. It is
* also used by the package installation to attribute
* the installed packages to this collector
*
* @return int
*/
private function getProfile() : int {
$db_profile = read_config_option('install_profile', true);
if (empty($db_profile) && db_table_exists('data_source_profiles')) {
$db_profile = db_fetch_cell('SELECT id FROM data_source_profiles WHERE `default` = \'on\' LIMIT 1');
if ($db_profile === false) {
$db_profile = db_fetch_cell('SELECT id FROM data_source_profiles ORDER BY id LIMIT 1');
}
}
log_install_medium('automation', 'getProfile() returns with ' . $db_profile);
return intval($db_profile);
}
/**
* setProfile() - sets the data source profile as the default one to be
* be used by the system.
*
* @param int $param_profile Must be an existing data_source_profile id
*
* @return void
*
* Error: will add an error to STEP_PROFILE_AND_AUTOMATION when an
* invalid id is passed.
*/
private function setProfile(int $param_profile = 0) {
if (db_table_exists('data_source_profiles')) {
if (!empty($param_profile)) {
$valid = db_fetch_cell_prepared('SELECT id FROM data_source_profiles WHERE id = ?', [$param_profile]);
if ($valid === false || $valid != $param_profile) {
$this->addError(Installer::STEP_PROFILE_AND_AUTOMATION, 'Profile', __('Failed to apply specified profile %s != %s', $valid, $param_profile));
} else {
$this->profile = $valid;
set_install_config_option('install_profile', $valid);
}
}
log_install_medium('automation', "setProfile($param_profile) returns with $this->profile");
} else {
$this->profile = 1;
}
}
/**
* getAutomationMode() - Gets the automation mode option, if not found
* uses the system default
*
* @return int
*/
public function getAutomationMode() : int {
$enabled = read_config_option('install_automation_mode', true);
log_install_debug('automation', 'automation_mode: ' . clean_up_lines($enabled));
if ($enabled == null && db_table_exists('automation_networks')) {
$row = db_fetch_row('SELECT id, enabled FROM automation_networks LIMIT 1');
log_install_debug('automation', 'Network data: ' . clean_up_lines(var_export($row, true)));
$enabled = 0;
if (!empty($row)) {
if ($row['enabled'] == 'on') {
$enabled = 1;
}
}
}
log_install_medium('automation', "getAutomationMode() returns '$enabled'");
return $enabled;
}
/**
* setAutomationMode() - sets whether the automation system should
* be enabled or disabled by default.
*
* @param int $param_mode Must be a valid true or false value
*
* @return void
*
* Errors: will add an error to STEP_PROFILE_AND_AUTOMATION if an
* invalid value is passed.
*/
private function setAutomationMode(int $param_mode = -1) : void {
if ($param_mode != -1) {
if (!$this->setTrueFalse($param_mode, $this->automationMode, 'automation_mode')) {
$this->addError(Installer::STEP_PROFILE_AND_AUTOMATION, 'Automation', 'Mode', __('Failed to apply specified mode: %s', $param_mode));
}
}
log_install_medium('automation', "setAutomationMode($param_mode) returns with $this->automationMode");
}
/**
* getAutomationOverride() - gets whether the automation snmp options
* are being overridden.
*
* @return string
*/
public function getAutomationOverride() : string {
return (string) read_config_option('install_automation_override', true);
}
/**
* setAutomationOverride() - sets whether the extra snmp options are to
* be overwritten by the SnmpOptions provided.
*
* @param string $param_override Must be a valid true or false value
*
* @return void
*
* Errors: will add an error to STEP_PROFILE_AND_AUTOMATION if an
* invalid value is passed.
*/
private function setAutomationOverride(string $param_override = '') : void {
if ($param_override != '') {
if (!$this->setTrueFalse($param_override, $this->automationOverride, 'automation_override')) {
$this->addError(Installer::STEP_PROFILE_AND_AUTOMATION, 'Automation', 'Override', __('Failed to apply specified automation override: %s', $param_override));
}
}
log_install_medium('automation', "setAutomationOverride($param_override) returns with $this->automationOverride");
}
/**
* setCronInterval() - sets the expected system cron interval but does
* not actually affect the system cron.
*
* @param string $param_interval A value that must exist in the system global
* variables $cron_intervals.
*
* @return void
*
* Errors: will set an error in STEP_PROFILE_AND_AUTOMATION when an
* invalid value is passed.
*/
private function setCronInterval(mixed $param_interval = null) : void {
global $cron_intervals;
if ($param_interval != null) {
if (array_key_exists($param_interval, $cron_intervals)) {
$this->cronInterval = intval($param_interval);
set_install_config_option('cron_interval', $param_interval);
} else {
$this->addError(Installer::STEP_PROFILE_AND_AUTOMATION, 'Poller', 'Cron', __('Failed to apply specified cron interval'));
}
}
log_install_medium('automation', "setCronInterval($param_interval) returns with $this->cronInterval");
}
/**
* getAutomationRange() - get the default network range to be used by
* Automation for scanning the network. If no previous value is found,
* defaults to 192.168.1.0/24
*
* @return string The specified network range
*/
public function getAutomationRange() : string {
$range = read_config_option('install_automation_range', true);
if (empty($range) && db_table_exists('automation_networks')) {
$row = db_fetch_row('SELECT id, subnet_range
FROM automation_networks
LIMIT 1');
$enabled = 0;
$network = '';
log_install_debug('automation', "getAutomationRange(): found '" . clean_up_lines(var_export($row, true)));
if (!empty($row)) {
$range = $row['subnet_range'];
}
}
$result = empty($range) ? '192.168.0.1/24' : $range;
log_install_medium('automation', "getAutomationRange() returns '$result'");
return $result;
}
/**
* setAutomationRange() - sets the network range to be used by
* Automation when scanning the network.
*
* @param string $param_range A valid network range which is converted and returned
* by cacti_pton(). If the return value is false, it is
* considered invalid
*
* @return void
*
* Errors: will add an error to STEP_PROFILE_AND_AUTOMATION if an invalid value is passed
*/
private function setAutomationRange(string $param_range = '') : void {
if ($param_range != '') {
$param_array = explode(',', $param_range);
if (cacti_sizeof($param_array)) {
foreach ($param_array as $param_network) {
$ip_details = automation_get_network_info($param_network);
if ($ip_details === false) {
$this->addError(Installer::STEP_PROFILE_AND_AUTOMATION, 'Automation', 'Range', __('Failed to apply \'%s\' as Automation Range', $param_network));
}
}
$this->automationRange = $param_range;
set_install_config_option('install_automation_range', $param_range);
}
} else {
$param_range = '';
$this->automationRange = $param_range;
}
log_install_medium('automation', "setAutomationRange($param_range) returns with $this->automationRange");
}
/**
* getSnmpOptions() - gets an array of all the extra SNMP options to be
* set which Automation will use when scanning the
* network.
*
* @return array The snmp option strings
*/
private function getSnmpOptions() : array {
global $fields_snmp_item_with_retry;
$db_snmp_options = db_fetch_assoc('SELECT name, value FROM settings where name like \'install_snmp_option_%\'');
$options = [];
foreach ($db_snmp_options as $row) {
$key = str_replace('install_snmp_option_', '', $row['name']);
$options[$key] = $row['value'];
}
log_install_debug('snmp_options', 'Option array: ' . clean_up_lines(var_export($options, true)));
return $options;
}
/**
* setSnmpOptions() - sets the extra SNMP options to be used by
* Automation when scanning the network.
*
* @param array $param_snmp_options An array of snmp options
*
* Errors: will add an error to STEP_PROFILE_AND_AUTOMATION if an
* invalid value is passed
*
* @return void
*/
private function setSnmpOptions($param_snmp_options = []) : void {
global $fields_snmp_item_with_retry;
$known_snmp_options = $fields_snmp_item_with_retry;
db_execute('DELETE FROM settings WHERE name like \'install_snmp_option_%\'');
log_install_medium('snmp_options', 'Updating snmp_options');
log_install_debug('snmp_options', 'Parameter data:' . clean_up_lines(var_export($param_snmp_options, true)));
$known_map = [
'SnmpVersion' => 'snmp_version',
'SnmpCommunity' => 'snmp_community',
'SnmpSecurityLevel' => 'snmp_security_level',
'SnmpUsername' => 'snmp_username',
'SnmpAuthProtocol' => 'snmp_auth_protocol',
'SnmpPassword' => 'snmp_password',
'SnmpPrivProtocol' => 'snmp_priv_protocol',
'SnmpPrivPassphrase' => 'snmp_priv_passphrase',
'SnmpContext' => 'snmp_context',
'SnmpEngineId' => 'snmp_engine_id',
'SnmpPort' => 'snmp_port',
'SnmpTimeout' => 'snmp_timeout',
'SnmpMaxOids' => 'max_oids',
'SnmpRetries' => 'snmp_retries',
];
foreach ($param_snmp_options as $option_name => $option_value) {
log_install_high('snmp_options', 'snmp_option: ' . clean_up_lines(var_export($option_name, true)));
$bad_option = true;
if (array_key_exists($option_name, $known_map)) {
$key = $known_map[$option_name];
log_install_debug('snmp_options', 'snmp_option found:' . clean_up_lines(var_export($key, true)));
if (array_key_exists($key, $known_snmp_options)) {
$bad_option = false;
log_install_high('snmp_options', 'snmp_option set:' . clean_up_lines(var_export($option_value, true)));
log_install_debug('snmp_options', "Set snmp_option: install_snmp_option_$key = $option_value");
set_install_config_option("install_snmp_option_$key", $option_value);
}
}
if ($bad_option) {
$this->addError(Installer::STEP_TEMPLATE_INSTALL, 'SnmpOptions', $option_name, __('No matching snmp option exists'));
}
}
}
/**
* getModules() - returns a list of required modules and their
* installation status.
*
* @return array An array of modules
*/
private function getModules() : array {
if (isset($this->extensions) || empty($this->extensions)) {
$extensions = utility_php_extensions();
foreach ($extensions as $name => $e) {
if (!$e['installed']) {
$this->addError(Installer::STEP_CHECK_DEPENDENCIES, 'Modules', $name . ' is missing');
}
}
$this->extensions = $extensions;
}
return $this->extensions;
}
private function getDefaultTemplate(bool $force = false) : int {
$default_template = intval(read_config_option('default_template', true));
if (read_config_option('install_get_template_message_logged', true) == '') {
log_install_always('templates', 'getDefaultTemplate(): Default Device Template is \'' . $default_template . '\'');
set_config_option('install_get_template_message_logged', 'on');
}
$sysDescrMatch = CACTI_SERVER_OS == 'win32' ? 'Windows' : 'Linux';
foreach ($this->defaultAutomation as $item) {
if ($item['sysDescrMatch'] == $sysDescrMatch || !empty($default_template)) {
$host_template_id = db_fetch_cell_prepared('SELECT id
FROM host_template
WHERE hash = ?',
[$item['hash']]
);
if (!empty($host_template_id)) {
if ($host_template_id != $default_template) {
log_install_always('templates', 'getDefaultTemplate(): Changing Device Template to \'' . $item['name'] . '\'');
set_install_config_option('default_template', $host_template_id);
}
break;
} else {
log_install_debug('templates', 'getDefaultTemplate(): Unable to find Device Template for \'' . $item['name'] . '\'');
}
}
}
if (empty($default_template) && !$force) {
set_default_action('default_template');
$default_template = $this->getDefaultTemplate(true);
}
return $default_template;
}
private function setDefaultTemplate(int $param_default_template = 0) : void {
$default_template = null;
if (!empty($param_default_template)) {
foreach ($this->defaultAutomation as $item) {
$id = db_fetch_cell_prepared('SELECT id
FROM host_template
WHERE hash = ?',
[$item['hash']]);
if ($id == $param_default_template) {
$default_template = $param_default_template;
break;
}
}
}
if (empty($default_template)) {
$default_template = $this->getDefaultTemplate();
}
set_install_config_option('default_template', $default_template);
$default_template = $this->getDefaultTemplate();
if (read_config_option('install_set_template_message_logged', true) == '') {
log_install_always('templates', 'setDefaultTemplate(): Default Device Template is \'' . $default_template . '\'');
set_config_option('install_set_template_message_logged', 'on');
}
}
/**
* getTemplates() - returns a list of expected templates and whether
* they have been selected for installation.
*
* @return mixed
*/
private function getTemplates() : mixed { // @phpstan-ignore-line
$known_templates = install_setup_get_templates();
if ($known_templates === false) {
return false;
}
$db_templates = array_rekey(
db_fetch_assoc('SELECT name, value FROM settings WHERE name LIKE \'install_template_%\''),
'name',
'value'
);
$hasTemplates = read_config_option('install_has_templates', true);
$selected = [];
$select_count = 0;
log_install_debug('templates', 'getTemplates(): First: ' . (empty($hasTemplates) ? 'Yes' : 'No') . ', Templates - ' . clean_up_lines(var_export($known_templates, true)));
log_install_debug('templates', 'getTemplates(): DB: ' . clean_up_lines(var_export($db_templates, true)));
foreach ($known_templates as $known) {
$filename = $known['filename'];
$key_base = str_replace('.', '_', $filename);
$key_install = 'install_template_' . $key_base;
$key_check = 'chk_template_' . $key_base;
log_install_high('templates', 'getTemplates(): Checking template ' . $known['name'] . ' using base: ' . $key_base);
log_install_debug('templates', 'getTemplates(): Checking template ' . $known['name'] . ' using key.: ' . $key_install);
log_install_debug('templates', 'getTemplates(): Checking template ' . $known['name'] . ' filename..: ' . $filename);
$value = '';
if (array_key_exists($key_install, $db_templates)) {
$value = $db_templates[$key_install];
}
log_install_debug('templates', 'getTemplates(): Checking template ' . $known['name'] . ' against...: ' . $value);
$isSelected = !empty($hasTemplates) && ($value == $filename);
$selected[$key_check] = $isSelected;
if ($isSelected) {
$select_count++;
}
}
$selected['all'] = ($select_count == cacti_count($selected) || empty($hasTemplates));
log_install_high('templates', 'getTemplates(): Returning with ' . clean_up_lines(var_export($selected, true)));
return $selected;
}
/**
* setTemplates() - sets a list of templates that should be installed
* during the installServer() phase.
*
* @param array $param_templates An array of templates to install in the form of
* 'template'=>(true|false)
*
* Errors: will add an error to STEP_TEMPLATE_INSTALL if a template is
* passed that is not expected
*/
private function setTemplates(array $param_templates = []) : void {
db_execute("DELETE FROM settings WHERE name LIKE 'install_tp_%'");
$known_templates = install_setup_get_templates();
log_install_medium('templates', 'setTemplates(): Updating templates');
log_install_debug('templates', 'setTemplates(): Parameter data:' . clean_up_lines(var_export($param_templates, true)));
log_install_debug('templates', 'setTemplates(): Template data:' . clean_up_lines(var_export($known_templates, true)));
$param_all = false;
if (array_key_exists('all', $param_templates)) {
$this->setTrueFalse($param_templates['all'], $param_all, 'param_all', false);
unset($param_templates['all']);
log_install_debug('templates', 'setTemplates(): All flag:' . clean_up_lines(var_export($param_all, true)));
}
$count = 0;
foreach ($param_templates as $name => $enabled) {
$template = false;
log_install_high('templates', 'setTemplates(): ' . $name . ' => ' . ($enabled ? 'true' : 'false'));
foreach ($known_templates as $known) {
$filename = $known['filename'];
$key = 'chk_template_' . str_replace('.', '_', $filename);
if ($name == $key || $name == $filename) {
$template = $known;
break;
}
}
if ($template === false) {
$this->addError(Installer::STEP_TEMPLATE_INSTALL, 'Templates', $name, __('No matching template exists'));
} else {
$set = false;
$key = str_replace('.', '_', $template['filename']);
$this->setTrueFalse($enabled, $set, $key, false);
$use = ($set) || ($param_all);
$value = ($use) ? $template['filename'] : '';
log_install_high('templates', "setTemplates(): Use: $use, Set: $set, All: $param_all, key: install_template_$key = " . $value);
// Don't default install templates if upgrade
if ($this->getMode() == Installer::MODE_DOWNGRADE) {
$value = '';
$use = false;
}
set_install_config_option("install_tp_$key", $value);
$this->templates[$name] = $use;
}
}
$all = true;
if (array_key_exists('all', $this->templates)) {
unset($this->templates['all']);
}
foreach ($this->templates as $use) {
if (!$use) {
$all = false;
}
}
$this->templates['all'] = $all;
set_install_config_option('install_has_templates', true);
}
/**
* getTables() - gets a list of tables that require conversion to
* mb4_unicode_utf8.
*
* @return array
*/
private function getTables() : array {
$known_tables = install_setup_get_tables();
$db_tables = array_rekey(
db_fetch_assoc('SELECT name, value FROM settings where name like \'install_table_%\''),
'name',
'value'
);
$hasTables = read_config_option('install_has_tables', true);
$selected = [];
$select_count = 0;
foreach ($known_tables as $known) {
$table = $known['Name'];
$key = $known['Name'];
$option = '';
if (array_key_exists('install_table_' . $key, $db_tables)) {
$option = $db_tables['install_table_' . $key];
}
$isSelected = !empty($hasTables) && (!empty($option));
$selected['chk_table_' . $key] = $isSelected;
if ($isSelected) {
$select_count++;
}
}
$selected['all'] = ($select_count == cacti_count($selected) || empty($hasTables));
return $selected;
}
/**
* setTables - sets a list of tables to be converted to the latest
* default coalition
*
* @param array $param_tables Array of table names
*
* Errors: does not add errors as a table may not be present in the
*
* @return void
*/
private function setTables(array $param_tables = []) : void {
db_execute('DELETE FROM settings WHERE name like \'install_table_%\'');
$known_tables = install_setup_get_tables();
log_install_medium('tables', 'setTables(): Updating Tables');
log_install_debug('tables', 'setTables(): Parameter data:' . clean_up_lines(var_export($param_tables, true)));
$param_all = false;
if (array_key_exists('all', $param_tables)) {
$this->setTrueFalse($param_tables['all'], $param_all, 'allTables', false);
unset($param_tables['all']);
}
foreach ($known_tables as $known) {
$name = $known['Name'];
$key = 'chk_table_' . $name;
$set = false;
log_install_high('tables', "setTables(): Checking table '$name' against key $key ...");
log_install_debug('tables', 'setTables(): Table: ' . clean_up_lines(var_export($known, true)));
if (!array_key_exists($key, $param_tables)) {
$param_tables[$key] = null;
}
$this->setTrueFalse($param_tables[$key], $set, 'table_' . $name, false);
$use = ($set || $param_all);
$value = $use ? $name : '';
log_install_high('tables', "setTables(): Use: $use, Set: $set, All: $param_all, key: install_table_$name = " . $value);
set_install_config_option("install_table_$name", $value);
$this->tables[$key] = $use;
}
$all = true;
if (array_key_exists('all', $this->tables)) {
unset($this->tables['all']);
}
foreach ($this->tables as $use) {
if (!$use) {
$all = false;
}
}
$this->tables['all'] = $all;
set_install_config_option('install_has_tables', true);
}
// getMode - gets the current mode
public function getMode() : int {
if (isset($this->mode)) {
$mode = $this->mode;
} else {
$mode = Installer::MODE_INSTALL;
if ($this->old_cacti_version != 'new_install') {
if (cacti_version_compare($this->old_cacti_version, CACTI_VERSION, '=')) {
$equal = '=';
$mode = Installer::MODE_NONE;
} elseif (cacti_version_compare($this->old_cacti_version, CACTI_VERSION, '<')) {
$equal = '<';
$mode = Installer::MODE_UPGRADE;
} else {
$equal = '>=';
$mode = Installer::MODE_DOWNGRADE;
}
log_install_high('mode', 'getMode(): Version Mode - ' . clean_up_lines(var_export($this->old_cacti_version, true))
. $equal . clean_up_lines(var_export(CACTI_VERSION, true)));
} elseif ($this->hasRemoteDatabaseInfo()) {
$mode = Installer::MODE_POLLER;
}
if ($mode == Installer::MODE_POLLER || $mode == Installer::MODE_INSTALL) {
$db_mode = read_config_option('install_mode', true);
log_install_high('mode', 'getMode(): DB Install mode ' . clean_up_lines(var_export($db_mode, true)));
if ($db_mode !== false && $db_mode !== null) {
$mode = $db_mode;
}
}
$this->mode = $mode;
}
log_install_high('mode', 'getMode(): ' . clean_up_lines(var_export($mode, true)));
return $mode;
}
/**
* setMode() - sets the current mode of operation
*
* @param int $param_mode The mode to set, must be one of the MODE_ constants
*
* Errors: will add an error when an invalid value is passed
*
* @return void
*/
private function setMode(int $param_mode = 0) : void {
if (intval($param_mode) > Installer::MODE_NONE && intval($param_mode) <= Installer::MODE_DOWNGRADE) {
log_install_high('mode', 'setMode(' . $param_mode . ')');
set_install_config_option('install_mode', $param_mode);
$this->mode = $param_mode;
$this->updateButtons();
} elseif ($param_mode != 0) {
$this->addError(Installer::STEP_INSTALL_TYPE, 'mode', 'Failed to apply mode: ' . $param_mode);
}
}
// getSetDefault() - returns the default step
private function getStepDefault() : int {
$mode = $this->getMode();
$step = $mode == Installer::MODE_NONE ? Installer::STEP_COMPLETE : Installer::STEP_WELCOME;
log_install_medium('step', "getStepDefault(): Resetting to $step as not found ($mode)");
return $step;
}
// getStep() - returns the current step
public function getStep() : int {
return $this->stepCurrent;
}
/**
* setStep() - sets the current step
*
* @param int $param_step Must be a valid value as defined by STEP_ constants
*
* @return void
*/
private function setStep(int $param_step = -1) : void {
$step = Installer::STEP_WELCOME;
if (empty($param_step)) {
$param_step = Installer::STEP_WELCOME;
$step = $param_step;
} elseif ($param_step > Installer::STEP_NONE && $param_step <= Installer::STEP_ERROR) {
if ($param_step == Installer::STEP_WELCOME) {
$step = $param_step;
} else {
$step = $param_step;
}
}
if ($step === Installer::STEP_NONE) {
$step = Installer::STEP_WELCOME;
}
log_install_debug('step', 'setStep(): ' . var_export($step, true));
// Make current step the first if it is unknown
log_install_high('step', 'setStep(): stepError ' . clean_up_lines(var_export($this->stepError, true)) . ' < ' . clean_up_lines(var_export($step, true)));
if ($this->stepError !== false && $this->stepError < $step) {
$step = $this->stepError;
}
$this->stepCurrent = ($step == Installer::STEP_NONE ? Installer::STEP_WELCOME : $step);
$this->stepPrevious = Installer::STEP_NONE;
$this->stepNext = Installer::STEP_NONE;
if ($step <= Installer::STEP_COMPLETE) {
$this->stepNext = ($step >= Installer::STEP_COMPLETE ? Installer::STEP_NONE : $step + 1);
if ($step >= Installer::STEP_WELCOME) {
$this->stepPrevious = ($step <= Installer::STEP_WELCOME ? Installer::STEP_NONE : $step - 1);
}
}
set_install_config_option('install_step', $this->stepCurrent);
$this->updateButtons();
set_install_config_option('install_prev', $this->stepPrevious);
set_install_config_option('install_next', $this->stepNext);
}
private function setAdminEmailAddress(string $admin_email_address = '') : void {
if ($admin_email_address != '') {
if (filter_var($admin_email_address, FILTER_VALIDATE_EMAIL)) {
db_execute("UPDATE user_auth SET email_address = '" . $admin_email_address . "'");
log_install_always('email', 'Admin email address set to ' . $admin_email_address);
} else {
$this->addError(Installer::STEP_PROFILE_AND_AUTOMATION, 'Email', __('Incorrect email address'));
}
} else {
log_install_always('email', 'Admin email address not set.');
}
$this->adminEmailAddress = $admin_email_address;
}
private function getAdminEmailAddress() : mixed {
$admin_email_address = db_fetch_cell("SELECT email_address FROM user_auth WHERE username = 'admin' LIMIT 1");
return $admin_email_address;
}
// Some utility functions
public function shouldRedirectToHome() : bool {
return (cacti_version_compare($this->old_cacti_version, CACTI_VERSION, '='));
}
public function shouldExitWithReason() : mixed {
if ($this->isDatabaseEmpty()) {
return Installer::EXIT_DB_EMPTY;
}
if ($this->isDatabaseTooOld()) {
return Installer::EXIT_DB_OLD;
}
return false;
}
public function isValidCollation(array $collation_vars, array $charset_vars, string $type) : bool {
$collation_value = '';
$charset_value = '';
if (
cacti_sizeof($collation_vars) &&
array_key_exists('collation_' . $type, $collation_vars)
) {
$collation_value = $collation_vars['collation_' . $type];
}
if (
cacti_sizeof($charset_vars) &&
array_key_exists('charset_' . $type, $charset_vars)
) {
$charset_value = $charset_vars['charset_' . $type];
}
return ($collation_value == 'utf8mb4_unicode_ci' ||
$charset_value == 'utf8mb4');
}
public function isDatabaseEmpty() : bool {
return empty($this->old_cacti_version);
}
public function isDatabaseTooOld() : bool {
return preg_match('/^0\.6/', $this->old_cacti_version);
}
public function isNewInstall() : bool {
return ($this->old_cacti_version == 'new_install');
}
public function isPre_v0_8_UpgradeNeeded() : bool {
return version_compare($this->old_cacti_version, '0.8.5a', '<=');
}
public function hasRemoteDatabaseInfo() : bool {
global $rdatabase_default;
return !empty($rdatabase_default);
}
public function isConfigurationWritable() : bool {
return is_resource_writable(CACTI_PATH_INCLUDE . '/config.php');
}
public function isRemoteDatabaseGood() : bool {
global $rdatabase_default, $rdatabase_username, $rdatabase_hostname, $rdatabase_port;
return (isset($rdatabase_default) &&
isset($rdatabase_username) &&
isset($rdatabase_hostname) &&
isset($rdatabase_port)) ? true : false;
}
public function exitWithReason(mixed $reason) : string {
switch ($reason) {
case Installer::EXIT_DB_EMPTY:
return $this->exitSqlNeeded();
case Installer::EXIT_DB_OLD:
return $this->exitDbTooOld();
default:
return $this->exitWithUnknownReason($reason);
}
}
private function exitWithUnknownReason(string $reason) : string {
$output = Installer::sectionTitleError();
$output .= Installer::sectionNormal(__('The Installer could not proceed due to an unexpected error.'));
$output .= Installer::sectionNormal(__('Please report this to the Cacti Group.'));
$output .= Installer::sectionCode(__('Unknown Reason: %s', $reason));
return $output;
}
private function exitDbTooOld() : string {
global $database_username, $database_default;
$output = Installer::sectionTitleError();
$output .= Installer::sectionNormal(__('You are attempting to install Cacti %s onto a 0.6.x database. Unfortunately, this can not be performed.', CACTI_VERSION));
$output .= Installer::sectionNormal(__('To be able continue, you MUST create a new database, import "cacti.sql" into it:', CACTI_VERSION));
$output .= Installer::sectionCode(sprintf('mysql -u %s -p [new_database] %s < cacti.sql', $database_username, $database_default));
$output .= Installer::sectionNormal(__('You MUST then update "include/config.php" to point to the new database.'));
$output .= Installer::sectionNormal(__('NOTE: Your existing data will not be modified, nor will it or any history be available to the new install'));
return $output;
}
private function exitSqlNeeded() : string {
global $database_username, $database_default, $database_password;
$output = Installer::sectionTitleError();
$output .= Installer::sectionNormal(__("You have created a new database, but have not yet imported the 'cacti.sql' file. At the command line, execute the following to continue:"));
$output .= Installer::sectionCode(sprintf('mysql -u %s -p %s < cacti.sql', $database_username, $database_default));
$output .= Installer::sectionNormal(__('This error may also be generated if the cacti database user does not have correct permissions on the Cacti database. Please ensure that the Cacti database user has the ability to SELECT, INSERT, DELETE, UPDATE, CREATE, ALTER, DROP, INDEX on the Cacti database.'));
$output .= Installer::sectionNormal(__('You MUST also import MySQL TimeZone information into MySQL and grant the Cacti user SELECT access to the mysql.time_zone_name table'));
if (CACTI_SERVER_OS == 'unix') {
$output .= Installer::sectionNormal(__("On Linux/UNIX, run the following as 'root' in a shell:"));
$output .= Installer::sectionCode(sprintf('mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql'));
} else {
$output .= Installer::sectionNormal(__("On Windows, you must follow the instructions here Time zone description table. Once that is complete, you can issue the following command to grant the Cacti user access to the tables:"));
}
$output .= Installer::sectionNormal(__('Then run the following within MySQL as an administrator:'));
$output .= Installer::sectionCode(sprintf("mysql > GRANT SELECT ON mysql.time_zone_name to '%s'@'localhost' IDENTIFIED BY '%s'", $database_username, $database_password));
return $output;
}
// updateButtons() - update the buttons used by the installer
private function updateButtons() : void {
if (empty($this->buttonNext)) {
$this->buttonNext = new InstallerButton();
}
if (empty($this->buttonPrevious)) {
$this->buttonPrevious = new InstallerButton();
}
if (empty($this->buttonTest)) {
$this->buttonTest = new InstallerButton();
$this->buttonTest->Enabled = false;
$this->buttonTest->Text = __('Test Connection');
}
$this->buttonNext->Text = __('Next');
$this->buttonPrevious->Text = __('Previous');
$this->buttonTest->Visible = false;
$this->buttonTest->setStep(Installer::STEP_TEST_REMOTE);
switch ($this->stepCurrent) {
case Installer::STEP_WELCOME:
$this->buttonNext->Text = __('Begin');
break;
case Installer::STEP_INSTALL_CONFIRM:
// checkdependencies - send to install/upgrade
if ($this->mode == Installer::MODE_UPGRADE) {
$this->buttonNext->Text = __('Upgrade');
} elseif ($this->mode == Installer::MODE_DOWNGRADE) {
$this->buttonNext->Text = __('Downgrade');
}
break;
case Installer::STEP_PERMISSION_CHECK:
switch ($this->mode) {
case Installer::MODE_UPGRADE:
case Installer::MODE_DOWNGRADE:
$this->stepNext = Installer::STEP_TEMPLATE_INSTALL;
break;
}
break;
case Installer::STEP_BINARY_LOCATIONS:
switch ($this->mode) {
case Installer::MODE_POLLER:
$this->stepNext = Installer::STEP_CHECK_TABLES;
break;
}
break;
case Installer::STEP_CHECK_TABLES:
switch ($this->mode) {
case Installer::MODE_POLLER:
$this->stepPrevious = Installer::STEP_BINARY_LOCATIONS;
break;
case Installer::MODE_UPGRADE:
case Installer::MODE_DOWNGRADE:
$this->stepPrevious = Installer::STEP_PERMISSION_CHECK;
break;
}
break;
case Installer::STEP_INSTALL_CONFIRM:
// upgrade - if user upgrades send to settings check
if ($this->isPre_v0_8_UpgradeNeeded()) {
// upgrade - if user runs old version send to upgrade-oldversion
$this->stepNext = Installer::STEP_INSTALL_OLDVERSION;
} else {
$this->stepNext = Installer::STEP_INSTALL;
}
break;
case Installer::STEP_COMPLETE:
$this->stepPrevious = Installer::STEP_NONE;
break;
}
$this->buttonNext->setStep($this->stepNext);
$this->buttonPrevious->setStep($this->stepPrevious);
}
/*
* The following sections of code are all related
* to the installation process of the current step
*/
public function processCurrentStep() : string {
$exitReason = $this->shouldExitWithReason();
if ($exitReason !== false) {
$this->buttonNext->Enabled = false;
$this->buttonPrevious->Enabled = false;
$this->buttonTest->Enabled = false;
return $this->exitWithReason($exitReason);
}
switch ($this->stepCurrent) {
case Installer::STEP_WELCOME:
return $this->processStepWelcome();
case Installer::STEP_CHECK_DEPENDENCIES:
return $this->processStepCheckDependencies();
case Installer::STEP_INSTALL_TYPE:
return $this->processStepMode();
case Installer::STEP_BINARY_LOCATIONS:
return $this->processStepBinaryLocations();
case Installer::STEP_PERMISSION_CHECK:
return $this->processStepPermissionCheck();
case Installer::STEP_INPUT_VALIDATION:
return $this->processStepNoticesRecommendations();
case Installer::STEP_PROFILE_AND_AUTOMATION:
return $this->processStepProfileAndAutomation();
case Installer::STEP_TEMPLATE_INSTALL:
return $this->processStepTemplateInstall();
case Installer::STEP_CHECK_TABLES:
return $this->processStepCheckTables();
case Installer::STEP_INSTALL_CONFIRM:
return $this->processStepInstallConfirm();
case Installer::STEP_INSTALL:
return $this->processStepInstall();
case Installer::STEP_ERROR:
return $this->processStepComplete();
case Installer::STEP_COMPLETE:
return $this->processStepComplete();
default:
return $this->exitWithReason(($this->stepCurrent));
}
}
public function processStepWelcome() : string {
global $cacti_version_codes;
$output = Installer::sectionTitle(__('Cacti Version') . ' ' . CACTI_VERSION_BRIEF . ' - ' . __('License Agreement'));
if (!array_key_exists(CACTI_VERSION, $cacti_version_codes)) {
$output .= Installer::sectionError(__('This version of Cacti (%s) does not appear to have a valid version code, please contact the Cacti Development Team to ensure this is corrected. If you are seeing this error in a release, please raise a report immediately on GitHub', CACTI_VERSION));
}
$output .= Installer::sectionNormal(__('Thanks for taking the time to download and install Cacti, the complete graphing solution for your network. Before you can start making cool graphs, there are a few pieces of data that Cacti needs to know.'));
$output .= Installer::sectionNormal(__('Make sure you have read and followed the required steps needed to install Cacti before continuing. Install information can be found for Unix and Win32-based operating systems.', '../docs/html/install_unix.html', '../docs/html/install_windows.html'));
if ($this->mode == Installer::MODE_UPGRADE) {
$output .= Installer::sectionNote(__('This process will guide you through the steps for upgrading from version \'%s\'. ', $this->old_cacti_version));
$output .= Installer::sectionNormal(__('Also, if this is an upgrade, be sure to read the Upgrade information file.', '../docs/html/upgrade.html'));
}
if ($this->mode == Installer::MODE_DOWNGRADE) {
$output .= Installer::sectionNote(__('It is NOT recommended to downgrade as the database structure may be inconsistent'));
}
$output .= Installer::sectionNormal(__('Cacti is licensed under the GNU General Public License, you must agree to its provisions before continuing:'));
$output .= Installer::sectionCode(
__('This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.') . '
' .
__('This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.')
);
$langOutput = '';
$themePath = CACTI_PATH_INCLUDE . '/themes/';
$themes = glob($themePath . '*', GLOB_ONLYDIR);
$themeOutput = '';
$eula = ($this->eula) ? ' checked' : '';
$output .= Installer::sectionNormal('' . __('Select default theme: ') . $themeOutput . '' . $langOutput . '');
$this->stepData = ['Eula' => $this->eula, 'Theme' => $this->theme, 'Language' => $this->language];
$this->buttonNext->Enabled = ($this->eula == 1);
return $output;
}
public function processStepCheckDependencies() : string {
global $local_db_cnn_id, $remote_db_cnn_id;
global $database_default, $database_username, $database_port;
global $rdatabase_default, $rdatabase_username, $rdatabase_port;
cacti_system_zone_set();
$enabled = [
'location' => DB_STATUS_SUCCESS,
'php_modules' => DB_STATUS_SUCCESS,
'php_optional' => DB_STATUS_SUCCESS,
'mysql_timezone' => DB_STATUS_SUCCESS,
'mysql_performance' => DB_STATUS_SUCCESS
];
$output = Installer::sectionTitle(__('Pre-installation Checks'));
$output .= Installer::sectionSubTitle(__('Location checks'), 'location');
// Get request URI and break into parts
if ($this->runtime == 'Cli' || $this->runtime == 'Json') {
$test_request_uri = 'index.php';
} else {
$test_request_uri = $_SERVER['REQUEST_URI'];
}
$test_request_parts = parse_url($test_request_uri);
$test_request_path = $test_request_parts['path'];
$test_request_len = strlen($test_request_parts['path']);
// Get current script name (filename only)
$test_script_name = get_current_script_name();
$test_script_len = strlen($test_script_name);
// Get end of the path of URI and see if it's our script
$test_script_part = substr($test_request_path, $test_request_len - $test_script_len);
$test_script_result = strcmp($test_script_part, $test_script_name);
// Assume desired path is the URI
$test_final_path = $test_request_parts['path'];
// Script was found in path, so remove it
if ($test_script_result === 0) {
$test_final_path = substr($test_final_path, 0, strlen($test_final_path) - $test_script_len);
}
// Add the install subfolder to defined path location
// and check if it matches, if not there will likely be problems
$test_config_path = CACTI_PATH_URL . 'install/';
$test_compare_result = strcmp($test_config_path, $test_final_path);
// The path was not what we expected so print an error
if ($test_compare_result !== 0) {
$output .= Installer::sectionNormal('' . __('ERROR:') . ' ' . __('Please update config.php with the correct relative URI location of Cacti (url_path).') . '');
$enabled['location'] = DB_STATUS_ERROR;
} else {
$output .= Installer::sectionNormal(__('Your Cacti configuration has the relative correct path (url_path) in config.php.'));
}
$output .= Installer::sectionSubTitleEnd();
$recs = utility_php_recommends();
foreach ($recs as $rec_title => $recommends) {
if (!empty($recommends)) {
$output .= Installer::sectionSubTitle(__('PHP - Recommendations (%s)', $rec_title), 'php_recommends_' . $rec_title);
ob_start();
html_start_box(__('PHP Recommendations' . ' (' . $recommends[0]['value'] . ')'), '100%', false, 3, '', false);
html_header([__('Name'), __('Current'), __('Recommended'), __('Status'), __('Description')]);
$status = DB_STATUS_SUCCESS;
if (!cacti_sizeof($recommends)) {
$recommends = [
[
'status' => DB_STATUS_ERROR,
'name' => __('PHP Binary'),
'current' => read_config_option('path_php_binary', true),
'value' => '',
'description' => __('The PHP binary location is not valid and must be updated.'),
],
[
'status' => DB_STATUS_WARNING,
'name' => '',
'current' => '',
'value' => '',
'description' => __('Update the path_php_binary value in the settings table.'),
]
];
}
foreach ($recommends as $recommend) {
if ($recommend['name'] == 'location') {
continue;
}
if ($recommend['status'] == DB_STATUS_SUCCESS) {
$status_font = 'green';
$status_text = __('Passed');
} elseif ($recommend['status'] == DB_STATUS_WARNING) {
$status_font = 'orange';
$status_text = __('Warning');
if ($status > DB_STATUS_WARNING) {
$status = DB_STATUS_WARNING;
}
} elseif ($recommend['status'] == DB_STATUS_RESTART) {
$status_font = 'orange';
$status_text = '' . __('Restart Required') . '';
if ($status > DB_STATUS_RESTART) {
$status = DB_STATUS_RESTART;
}
} else {
$status_font = 'red';
$status_text = __('Error');
if ($status > DB_STATUS_ERROR) {
$status = DB_STATUS_ERROR;
}
}
form_alternate_row('php_' . $recommend['name'], true);
form_selectable_ecell($recommend['name'], '');
form_selectable_ecell($recommend['current'], '');
form_selectable_ecell('>= ' . $recommend['value'], '');
form_selectable_cell("$status_text", '');
form_selectable_ecell($recommend['description'], '');
form_end_row();
}
html_end_box(false);
$output .= Installer::sectionNormal(ob_get_contents());
ob_clean();
$output .= Installer::sectionSubTitleEnd();
$enabled['php_recommends_' . $rec_title] = $status;
}
}
$output .= Installer::sectionSubTitle(__('PHP - Module Support (Required)'), 'php_modules');
$output .= Installer::sectionNormal(__('Cacti requires several PHP Modules to be installed to work properly. If any of these are not installed, you will be unable to continue the installation until corrected. In addition, for optimal system performance Cacti should be run with certain MySQL system variables set. Please follow the MySQL recommendations at your discretion. Always seek the MySQL documentation if you have any questions.'));
$output .= Installer::sectionNormal(__('The following PHP extensions are mandatory, and MUST be installed before continuing your Cacti install.'));
ob_clean();
html_start_box(__('Required PHP Modules'), '100%', false, 3, 'center', false);
html_header([__('Name'), __('Required'), __('Installed')]);
$enabled['php_modules'] = DB_STATUS_SUCCESS;
foreach ($this->modules as $id => $e) {
form_alternate_row('line' . $id);
form_selectable_cell($id, '');
form_selectable_cell('' . __('Yes') . '', '');
form_selectable_cell(Installer::formatModuleStatus($this->modules[$id], $id), '');
form_end_row();
if (!$e['installed']) {
$enabled['php_modules'] = DB_STATUS_ERROR;
}
}
html_end_box(false);
$output .= Installer::sectionNormal(ob_get_contents());
ob_clean();
$output .= Installer::sectionSubTitleEnd();
$output .= Installer::sectionSubTitle(__('PHP - Module Support (Optional)'), 'php_optional');
$output .= Installer::sectionNormal(__('The following PHP extensions are recommended, and should be installed before continuing your Cacti install. NOTE: If you are planning on supporting SNMPv3 with IPv6 or SNMPv3 with stronger ciphers, you should not install the php-snmp module at this time.'));
$ext = utility_php_optionals();
html_start_box(__('Optional Modules'), '100%', false, 3, 'center', false);
html_header([__('Name'), __('Optional'), __('Installed')]);
foreach ($ext as $id => $e) {
form_alternate_row('line' . $id, true);
form_selectable_cell($id, '');
form_selectable_cell('' . __('Yes') . '', '');
form_selectable_cell(Installer::formatModuleStatus($ext[$id], $id, 'orange'), '');
form_end_row();
if (!$e['installed']) {
$enabled['php_optional'] = DB_STATUS_WARNING;
}
}
html_end_box();
$output .= Installer::sectionNormal(ob_get_contents());
ob_clean();
$output .= Installer::sectionSubTitleEnd();
$output .= Installer::sectionSubTitle(__('MySQL - TimeZone Support'), 'mysql_timezone');
if (POLLER_ID == 1) {
$mysql_timezone_access = db_fetch_assoc('SHOW COLUMNS FROM mysql.time_zone_name', false);
} else {
$mysql_timezone_access = db_fetch_assoc('SHOW COLUMNS FROM mysql.time_zone_name', false, $local_db_cnn_id);
}
if (cacti_sizeof($mysql_timezone_access)) {
$timezone_populated = db_fetch_cell('SELECT COUNT(*) FROM mysql.time_zone_name');
if (!$timezone_populated) {
$output .= Installer::sectionNormal('' . __('ERROR:') . ' ' . __('Your MySQL TimeZone database is not populated. Please populate this database before proceeding.') . '');
$enabled['mysql_timezone'] = DB_STATUS_ERROR;
}
} else {
$output .= Installer::sectionNormal('' . __('ERROR:') . ' ' . __('Your Cacti database login account does not have access to the MySQL TimeZone database. Please provide the Cacti database account "select" access to the "time_zone_name" table in the "mysql" database, and populate MySQL\'s TimeZone information before proceeding.') . '');
$enabled['mysql_timezone'] = DB_STATUS_ERROR;
}
if ($enabled['mysql_timezone'] == DB_STATUS_SUCCESS) {
$output .= Installer::sectionNormal(__('Your Cacti database account has access to the MySQL TimeZone database and that database is populated with global TimeZone information.'));
}
$output .= Installer::sectionSubTitleEnd();
$output .= Installer::sectionSubTitle(__('MySQL - Settings'), 'mysql_performance');
$output .= Installer::sectionNormal(__('These MySQL performance tuning settings will help your Cacti system perform better without issues for a longer time.'));
html_start_box(__('Recommended MySQL System Variable Settings'), '100%', false, 3, 'center', false);
$output_temp = ob_get_contents();
ob_clean();
$enabled['mysql_performance'] = utilities_get_mysql_recommendations();
$output_util = ob_get_contents();
ob_clean();
html_end_box(false);
$output .= Installer::sectionNormal($output_temp . $output_util . ob_get_contents());
ob_end_clean();
$output .= Installer::sectionSubTitleEnd();
$this->stepData = ['Sections' => $enabled];
$this->buttonNext->Enabled = ($enabled['php_modules'] != DB_STATUS_ERROR) &&
($enabled['php_recommends_cli'] != DB_STATUS_ERROR) &&
($enabled['php_recommends_web'] != DB_STATUS_ERROR) &&
($enabled['mysql_timezone'] != DB_STATUS_ERROR);
return $output;
}
public function processStepMode() : string {
global $database_default, $database_username, $database_hostname, $database_port;
global $rdatabase_default, $rdatabase_username, $rdatabase_hostname, $rdatabase_port;
// install/upgrade
$output = Installer::sectionTitle(__('Installation Type'));
switch ($this->mode) {
case Installer::MODE_UPGRADE:
// upgrade detected
$output .= Installer::sectionSubTitle(__('Upgrade'));
$output .= Installer::sectionNormal(__('Upgrade from %s to %s', $this->old_cacti_version, CACTI_VERSION_FULL));
$output .= Installer::sectionWarning(__('In the event of issues, It is highly recommended that you clear your browser cache, closing then reopening your browser (not just the tab Cacti is on) and retrying, before raising an issue with The Cacti Group'));
$output .= Installer::sectionNormal(__('On rare occasions, we have had reports from users who experience some minor issues due to changes in the code. These issues are caused by the browser retaining pre-upgrade code and whilst we have taken steps to minimise the chances of this, it may still occur. If you need instructions on how to clear your browser cache, https://www.refreshyourcache.com/ is a good starting point.'));
$output .= Installer::sectionNormal(__('If after clearing your cache and restarting your browser, you still experience issues, please raise the issue with us and we will try to identify the cause of it.'));
$output .= Installer::sectionSubTitleEnd();
break;
case Installer::MODE_DOWNGRADE:
$output .= Installer::sectionSubTitle(__('Upgrade'));
$output .= Installer::sectionNormal(__('Downgrade from %s to %s', $this->old_cacti_version, CACTI_VERSION_FULL));
$output .= Installer::sectionWarning(__('You appear to be downgrading to a previous version. Database changes made for the newer version will not be reversed and could cause issues.'));
$output .= Installer::sectionSubTitleEnd();
break;
default:
// new install
$output .= Installer::sectionSubTitle(__('Please select the type of installation'));
$output .= Installer::sectionNormal(__('Installation options:'));
$output .= Installer::sectionNormal(
'