0) { $can_show_restore = true; $should_skip_to_user = true; } else { // If DB exists but doesn't have user table yet, maybe still allow restore $all_tables = mysqli_query($mysqli, "SHOW TABLES"); if ($all_tables && mysqli_num_rows($all_tables) > 0) { $can_show_restore = true; } } } include_once "../includes/settings_localization_array.php"; $errorLog = ini_get('error_log') ?: "Debian/Ubuntu default is usually /var/log/apache2/error.log"; // Get a list of all available timezones $timezones = DateTimeZone::listIdentifiers(); if (isset($_POST['add_database'])) { // Check if database has been set up already. If it has, direct user to edit directly instead. if (file_exists('../config.php')) { $_SESSION['alert_message'] = "Database already configured. Any further changes should be made by editing the config.php file."; header("Location: ?user"); exit; } $host = filter_var(trim($_POST['host']), FILTER_SANITIZE_STRING); $database = filter_var(trim($_POST['database']), FILTER_SANITIZE_STRING); $username = filter_var(trim($_POST['username']), FILTER_SANITIZE_STRING); $password = filter_var(trim($_POST['password']), FILTER_SANITIZE_STRING); $config_base_url = $_SERVER['HTTP_HOST'] . dirname($_SERVER['REQUEST_URI']); $config_base_url = rtrim($config_base_url, '/'); $installation_id = randomString(32); // Ensure variables meet specific criteria (very basic examples) if (!preg_match('/^[a-zA-Z0-9.-]+$/', $host)) { die('Invalid host format.'); } // Test database connection before writing it to config.php $conn = mysqli_connect($host, $username, $password, $database); if (!$conn) { exit("Database connection failed - please check and try again

" . mysqli_connect_error()); } $new_config = "\s*$/', $cfg)) { $cfg = preg_replace('/\?>\s*$/', "\n$line\n?>\n", $cfg, 1); } else { if ($cfg !== '' && substr($cfg, -1) !== "\n") $cfg .= "\n"; $cfg .= $line . "\n"; } } $dir = dirname($file); $tmp = tempnam($dir, 'cfg_'); if ($tmp === false) throw new RuntimeException("Failed to create temp file in $dir"); if (file_put_contents($tmp, $cfg, LOCK_EX) === false) { @unlink($tmp); throw new RuntimeException("Failed to write temp config"); } $perms = @fileperms($file); if ($perms !== false) @chmod($tmp, $perms & 0777); if (!@rename($tmp, $file)) { @unlink($tmp); throw new RuntimeException("Failed to atomically replace config.php"); } if (function_exists('opcache_invalidate')) { @opcache_invalidate($file, true); } } } if (!function_exists('setConfigFlag')) { function setConfigFlag(string $file, string $key, $value): void { $cfg = @file_get_contents($file); if ($cfg === false) throw new RuntimeException("Cannot read $file"); $cfg = str_replace("\r\n", "\n", $cfg); $pattern = '/^\s*\$' . preg_quote($key, '/') . '\s*=\s*.*?;\s*$/m'; $line = '$' . $key . ' = ' . (is_bool($value) ? ($value ? 'true' : 'false') : var_export($value, true)) . ';'; if (preg_match($pattern, $cfg)) { $cfg = preg_replace($pattern, $line, $cfg, 1); } else { if (preg_match('/\?>\s*$/', $cfg)) { $cfg = preg_replace('/\?>\s*$/', "\n$line\n?>\n", $cfg, 1); } else { if ($cfg !== '' && substr($cfg, -1) !== "\n") $cfg .= "\n"; $cfg .= $line . "\n"; } } if (file_put_contents($file, $cfg, LOCK_EX) === false) { throw new RuntimeException("Failed to update $file"); } if (function_exists('opcache_invalidate')) { @opcache_invalidate($file, true); } } } // ---------- Environment guards ---------- @set_time_limit(0); if (function_exists('ini_set')) { @ini_set('memory_limit', '1024M'); } // ---------- 1) Validate uploaded file ---------- if (!isset($_FILES['backup_zip']) || $_FILES['backup_zip']['error'] !== UPLOAD_ERR_OK) { die("No backup file uploaded or upload failed."); } $file = $_FILES['backup_zip']; if ($file['size'] > 4 * 1024 * 1024 * 1024) { die("Backup archive is too large."); } $fi = new finfo(FILEINFO_MIME_TYPE); $mime = $fi->file($file['tmp_name']); if ($mime !== 'application/zip' && $mime !== 'application/x-zip-compressed') { die("Invalid archive type; only .zip is supported."); } $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if ($ext !== 'zip') { die("Only .zip files are allowed."); } // ---------- 2) Save outer zip to temp ---------- $timestamp = date('YmdHis'); $tempZip = tempnam(sys_get_temp_dir(), "restore_{$timestamp}_"); if (!move_uploaded_file($file["tmp_name"], $tempZip)) { die("Failed to save uploaded backup file."); } @chmod($tempZip, 0600); // ---------- 3) Extract OUTER backup zip ---------- $tempDir = sys_get_temp_dir() . "/restore_temp_" . bin2hex(random_bytes(6)); if (!mkdir($tempDir, 0700, true)) { @unlink($tempZip); die("Failed to create temp directory."); } $zip = new ZipArchive; if ($zip->open($tempZip) !== TRUE) { @unlink($tempZip); deleteDir($tempDir); die("Failed to open backup zip file."); } try { safeExtractZip($zip, $tempDir); // helper (safe extraction) } catch (Throwable $e) { $zip->close(); @unlink($tempZip); deleteDir($tempDir); die("Invalid backup archive: " . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8')); } $zip->close(); @unlink($tempZip); $sqlPath = $tempDir . "/db.sql"; $uploadsZip = $tempDir . "/uploads.zip"; // inner uploads zip $versionTxt = $tempDir . "/version.txt"; if (!is_file($sqlPath) || !is_readable($sqlPath)) { deleteDir($tempDir); die("Missing db.sql in the backup archive."); } if (!is_file($uploadsZip) || !is_readable($uploadsZip)) { deleteDir($tempDir); die("Missing uploads.zip in the backup archive."); } // ---------- 4) Optional: version compatibility ---------- if (defined('LATEST_DATABASE_VERSION') && is_file($versionTxt)) { $txt = @file_get_contents($versionTxt) ?: ''; if (preg_match('/^Database Version:\s*(.+)$/mi', $txt, $m)) { $backupVersion = trim($m[1]); $running = LATEST_DATABASE_VERSION; if (version_compare($backupVersion, $running, '>')) { deleteDir($tempDir); die("Backup schema ($backupVersion) is newer than this app ($running). Please upgrade ITFlow first, then retry restore."); } } } // ---------- 5) Restore SQL ---------- mysqli_query($mysqli, "SET FOREIGN_KEY_CHECKS = 0"); $tables = mysqli_query($mysqli, "SHOW TABLES"); if ($tables) { while ($row = mysqli_fetch_array($tables)) { $tbl = $row[0]; mysqli_query($mysqli, "DROP TABLE IF EXISTS `".$mysqli->real_escape_string($tbl)."`"); } } mysqli_query($mysqli, "SET FOREIGN_KEY_CHECKS = 1"); try { importSqlFile($mysqli, $sqlPath); // helper } catch (Throwable $e) { deleteDir($tempDir); die("SQL import failed: " . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8')); } // ---------- 6) Restore UPLOADS: DELETE existing, then REPLACE ---------- $appRoot = realpath(__DIR__ . "/.."); if ($appRoot === false) { deleteDir($tempDir); die("Failed to resolve app root."); } $uploadDir = $appRoot . "/uploads"; // Extract inner uploads.zip to staging (with validation & scan report) $staging = $appRoot . "/uploads_restoring_" . bin2hex(random_bytes(4)); if (!mkdir($staging, 0700, true)) { deleteDir($tempDir); die("Failed to create staging directory."); } $uz = new ZipArchive; if ($uz->open($uploadsZip) !== TRUE) { deleteDir($staging); deleteDir($tempDir); die("Failed to open uploads.zip in backup."); } $scan = extractUploadsZipWithValidationReport($uz, $staging, [ 'max_file_bytes' => 200 * 1024 * 1024, 'blocked_exts' => [ 'php','php3','php4','php5','php7','php8','phtml','phar', 'cgi','pl','sh','bash','zsh','exe','dll','bat','cmd','com', 'ps1','vbs','vb','jar','jsp','asp','aspx','so','dylib','bin' ], ]); $uz->close(); if (!$scan['ok']) { $lines = ["Unsafe file(s) detected in uploads.zip:"]; foreach ($scan['issues'] as $issue) { $p = htmlspecialchars($issue['path'], ENT_QUOTES, 'UTF-8'); $r = htmlspecialchars($issue['reason'], ENT_QUOTES, 'UTF-8'); $lines[] = "• {$p} — {$r}"; } deleteDir($staging); deleteDir($tempDir); $_SESSION['alert_message'] = nl2br(implode("\n", $lines)); header("Location: ?restore"); exit; } // If inner zip has a single "uploads/" folder, promote its contents $roots = []; if ($dh = opendir($staging)) { while (($e = readdir($dh)) !== false) { if ($e === '.' || $e === '..') continue; $roots[] = $e; } closedir($dh); } sort($roots); if (count($roots) === 1) { $candidate = $staging . DIRECTORY_SEPARATOR . $roots[0]; if (is_dir($candidate)) { $promoted = $staging . "_promoted"; if (!@rename($candidate, $promoted)) { // fallback to copy $rit = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($candidate, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST ); if (!mkdir($promoted, 0700, true)) { deleteDir($staging); deleteDir($tempDir); die("Failed to create promoted staging directory."); } foreach ($rit as $it) { $rel = substr($it->getPathname(), strlen($candidate) + 1); $dst = $promoted . DIRECTORY_SEPARATOR . $rel; if ($it->isDir()) { if (!is_dir($dst) && !mkdir($dst, 0700, true)) { deleteDir($staging); deleteDir($tempDir); die("Failed to create $dst"); } } else { $pdir = dirname($dst); if (!is_dir($pdir) && !mkdir($pdir, 0700, true)) { deleteDir($staging); deleteDir($tempDir); die("Failed to create $pdir"); } if (!copy($it->getPathname(), $dst)) { deleteDir($staging); deleteDir($tempDir); die("Failed to copy $rel into promoted staging"); } @chmod($dst, 0640); } } } deleteDir($staging); $staging = isset($promoted) ? $promoted : $staging; } } // Sanity: staging must contain files $hasFiles = false; $it = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($staging, FilesystemIterator::SKIP_DOTS) ); foreach ($it as $f) { if ($f->isFile()) { $hasFiles = true; break; } } if (!$hasFiles) { deleteDir($staging); deleteDir($tempDir); die("Uploads restore failed: staging is empty (inner uploads.zip may be malformed or fully blocked)."); } // --- DELETE existing /uploads first, then REPLACE with staging --- if (is_dir($uploadDir)) { deleteDir($uploadDir, $appRoot); // guarded delete (under app root) } if (!@rename($staging, $uploadDir)) { // fallback: copy if (!mkdir($uploadDir, 0750, true)) { deleteDir($staging); deleteDir($tempDir); die("Failed to create uploads directory for placement."); } $rit = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($staging, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST ); foreach ($rit as $it) { $rel = substr($it->getPathname(), strlen($staging) + 1); $dst = $uploadDir . DIRECTORY_SEPARATOR . $rel; if ($it->isDir()) { if (!is_dir($dst) && !mkdir($dst, 0750, true)) { deleteDir($uploadDir); deleteDir($staging); deleteDir($tempDir); die("Failed to create uploads subdir: $dst"); } } else { $pdir = dirname($dst); if (!is_dir($pdir) && !mkdir($pdir, 0750, true)) { deleteDir($uploadDir); deleteDir($staging); deleteDir($tempDir); die("Failed to create uploads parent dir: $pdir"); } if (!copy($it->getPathname(), $dst)) { deleteDir($uploadDir); deleteDir($staging); deleteDir($tempDir); die("Failed to place file into uploads: $rel"); } @chmod($dst, 0640); } } deleteDir($staging); } // Verify uploads has files $okFiles = false; $it2 = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($uploadDir, FilesystemIterator::SKIP_DOTS) ); foreach ($it2 as $f) { if ($f->isFile()) { $okFiles = true; break; } } if (!$okFiles) { deleteDir($uploadDir); deleteDir($tempDir); die("Uploads replace failed: resulting directory is empty."); } // ---------- 7) Log version info (optional) ---------- if (is_file($versionTxt)) { $versionInfo = @file_get_contents($versionTxt); if ($versionInfo !== false) { logAction("Backup Restore", "Version Info", $versionInfo); } } // ---------- 8) Cleanup temp ---------- deleteDir($tempDir); // ---------- 9) Finalize setup flag ---------- try { setConfigFlagAtomic(__DIR__ . "/../config.php", "config_enable_setup", 0); } catch (Throwable $e) { try { setConfigFlag(__DIR__ . "/../config.php", "config_enable_setup", 0); if (function_exists('opcache_invalidate')) { @opcache_invalidate(__DIR__ . "/../config.php", true); } } catch (\Throwable $e2) { $_SESSION['alert_message'] = "Restore completed, but couldn’t finalize setup flag automatically: " . htmlspecialchars($e2->getMessage(), ENT_QUOTES, 'UTF-8'); header("Location: ../login.php"); exit; } } // ---------- 10) Done ---------- $_SESSION['alert_message'] = "Full backup restored successfully. Uploads directory was replaced."; header("Location: ../login.php"); exit; } if (isset($_POST['add_user'])) { $user_count = mysqli_num_rows(mysqli_query($mysqli,"SELECT COUNT(*) FROM users")); if ($user_count < 0) { $_SESSION['alert_message'] = "Users already exist in the database. Clear them to reconfigure here."; header("Location: ?company"); exit; } $name = sanitizeInput($_POST['name']); $email = sanitizeInput($_POST['email']); $password = password_hash(trim($_POST['password']), PASSWORD_DEFAULT); //Generate master encryption key $site_encryption_master_key = randomString(); //Generate user specific key $user_specific_encryption_ciphertext = setupFirstUserSpecificKey(trim($_POST['password']), $site_encryption_master_key); mysqli_query($mysqli,"INSERT INTO users SET user_name = '$name', user_email = '$email', user_password = '$password', user_specific_encryption_ciphertext = '$user_specific_encryption_ciphertext', user_role_id = 3"); mkdirMissing("../uploads/users/1"); //Check to see if a file is attached if ($_FILES['file']['tmp_name'] != '') { // get details of the uploaded file $file_error = 0; $file_tmp_path = $_FILES['file']['tmp_name']; $file_name = $_FILES['file']['name']; $file_size = $_FILES['file']['size']; $file_type = $_FILES['file']['type']; $file_extension = strtolower(end(explode('.',$_FILES['file']['name']))); // sanitize file-name $new_file_name = md5(time() . $file_name) . '.' . $file_extension; // check if file has one of the following extensions $allowed_file_extensions = array('jpg', 'jpeg', 'gif', 'png', 'webp'); if (in_array($file_extension,$allowed_file_extensions) === false) { $file_error = 1; } //Check File Size if ($file_size > 2097152) { $file_error = 1; } if ($file_error == 0) { // directory in which the uploaded file will be moved $upload_file_dir = "../uploads/users/1/"; $dest_path = $upload_file_dir . $new_file_name; move_uploaded_file($file_tmp_path, $dest_path); //Set Avatar mysqli_query($mysqli,"UPDATE users SET user_avatar = '$new_file_name' WHERE user_id = 1"); $_SESSION['alert_message'] = 'File successfully uploaded.'; } else { $_SESSION['alert_message'] = 'There was an error moving the file to upload directory. Please make sure the upload directory is writable by web server.'; } } //Create Settings mysqli_query($mysqli,"INSERT INTO user_settings SET user_id = 1"); $_SESSION['alert_message'] = "User $name created"; header("Location: ?company"); exit; } if (isset($_POST['add_company_settings'])) { $name = sanitizeInput($_POST['name']); $country = sanitizeInput($_POST['country']); $address = sanitizeInput($_POST['address']); $city = sanitizeInput($_POST['city']); $state = sanitizeInput($_POST['state']); $zip = sanitizeInput($_POST['zip']); $phone = preg_replace("/[^0-9]/", '',$_POST['phone']); $email = sanitizeInput($_POST['email']); $website = sanitizeInput($_POST['website']); $tax_id = sanitizeInput($_POST['tax_id']); mysqli_query($mysqli,"INSERT INTO companies SET company_name = '$name', company_address = '$address', company_city = '$city', company_state = '$state', company_zip = '$zip', company_country = '$country', company_phone = '$phone', company_email = '$email', company_website = '$website', company_tax_id = '$tax_id'"); //Check to see if a file is attached if ($_FILES['file']['tmp_name'] != '') { // get details of the uploaded file $file_error = 0; $file_tmp_path = $_FILES['file']['tmp_name']; $file_name = $_FILES['file']['name']; $file_size = $_FILES['file']['size']; $file_type = $_FILES['file']['type']; $file_extension = strtolower(end(explode('.',$_FILES['file']['name']))); // sanitize file-name $new_file_name = md5(time() . $file_name) . '.' . $file_extension; // check if file has one of the following extensions $allowed_file_extensions = array('jpg', 'jpeg', 'png'); if (in_array($file_extension,$allowed_file_extensions) === false) { $file_error = 1; } //Check File Size if ($file_size > 2097152) { $file_error = 1; } if ($file_error == 0) { // directory in which the uploaded file will be moved $upload_file_dir = "../uploads/settings/"; $dest_path = $upload_file_dir . $new_file_name; move_uploaded_file($file_tmp_path, $dest_path); mysqli_query($mysqli,"UPDATE companies SET company_logo = '$new_file_name' WHERE company_id = 1"); $_SESSION['alert_message'] = 'File successfully uploaded.'; } else { $_SESSION['alert_message'] = 'There was an error moving the file to upload directory. Please make sure the upload directory is writable by web server.'; } } $latest_database_version = LATEST_DATABASE_VERSION; mysqli_query($mysqli,"INSERT INTO settings SET company_id = 1, config_current_database_version = '$latest_database_version', config_invoice_prefix = 'INV-', config_invoice_next_number = 1, config_recurring_invoice_prefix = 'REC-', config_invoice_overdue_reminders = '1,3,7', config_quote_prefix = 'QUO-', config_quote_next_number = 1, config_default_net_terms = 30, config_ticket_next_number = 1, config_ticket_prefix = 'TCK-'"); // Create Categories // Expense Categories Examples mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Office Supplies', category_type = 'Expense', category_color = 'blue'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Travel', category_type = 'Expense', category_color = 'purple'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Advertising', category_type = 'Expense', category_color = 'orange'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Processing Fee', category_type = 'Expense', category_color = 'gray'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Shipping and Postage', category_type = 'Expense', category_color = 'teal'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Software', category_type = 'Expense', category_color = 'lightblue'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Bank Fees', category_type = 'Expense', category_color = 'yellow'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Payroll', category_type = 'Expense', category_color = 'green'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Professional Services', category_type = 'Expense', category_color = 'darkblue'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Contractor', category_type = 'Expense', category_color = 'brown'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Insurance', category_type = 'Expense', category_color = 'red'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Infrastructure', category_type = 'Expense', category_color = 'darkgreen'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Equipment', category_type = 'Expense', category_color = 'gray'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Education', category_type = 'Expense', category_color = 'lightyellow'"); // Income Categories Examples mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Managed Services', category_type = 'Income', category_color = 'green'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Consulting', category_type = 'Income', category_color = 'blue'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Projects', category_type = 'Income', category_color = 'purple'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Hardware Sales', category_type = 'Income', category_color = 'silver'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Software Sales', category_type = 'Income', category_color = 'lightblue'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Cloud Services', category_type = 'Income', category_color = 'skyblue'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Support', category_type = 'Income', category_color = 'yellow'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Training', category_type = 'Income', category_color = 'lightyellow'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Telecom Services', category_type = 'Income', category_color = 'orange'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Backup', category_type = 'Income', category_color = 'darkblue'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Security', category_type = 'Income', category_color = 'red'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Licensing', category_type = 'Income', category_color = 'green'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Monitoring', category_type = 'Income', category_color = 'teal'"); // Referral Examples mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Friend', category_type = 'Referral', category_color = 'blue'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Search', category_type = 'Referral', category_color = 'orange'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Social Media', category_type = 'Referral', category_color = 'green'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Email', category_type = 'Referral', category_color = 'yellow'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Partner', category_type = 'Referral', category_color = 'purple'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Event', category_type = 'Referral', category_color = 'red'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Affiliate', category_type = 'Referral', category_color = 'pink'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Client', category_type = 'Referral', category_color = 'lightblue'"); // Payment Methods mysqli_query($mysqli,"INSERT INTO payment_methods SET payment_method_name = 'Cash'"); mysqli_query($mysqli,"INSERT INTO payment_methods SET payment_method_name = 'Check'"); mysqli_query($mysqli,"INSERT INTO payment_methods SET payment_method_name = 'Bank Transfer'"); mysqli_query($mysqli,"INSERT INTO payment_methods SET payment_method_name = 'Credit Card'"); // Default Calendar mysqli_query($mysqli,"INSERT INTO calendars SET calendar_name = 'Default', calendar_color = 'blue'"); // Add default ticket statuses mysqli_query($mysqli, "INSERT INTO ticket_statuses SET ticket_status_name = 'New', ticket_status_color = '#dc3545'"); // Default ID for new tickets is 1 mysqli_query($mysqli, "INSERT INTO ticket_statuses SET ticket_status_name = 'Open', ticket_status_color = '#007bff'"); // 2 mysqli_query($mysqli, "INSERT INTO ticket_statuses SET ticket_status_name = 'On Hold', ticket_status_color = '#28a745'"); // 3 mysqli_query($mysqli, "INSERT INTO ticket_statuses SET ticket_status_name = 'Resolved', ticket_status_color = '#343a40'"); // 4 (was auto-close) mysqli_query($mysqli, "INSERT INTO ticket_statuses SET ticket_status_name = 'Closed', ticket_status_color = '#343a40'"); // 5 // Add default modules mysqli_query($mysqli, "INSERT INTO modules SET module_name = 'module_client', module_description = 'General client & contact management'"); mysqli_query($mysqli, "INSERT INTO modules SET module_name = 'module_support', module_description = 'Access to ticketing, assets and documentation'"); mysqli_query($mysqli, "INSERT INTO modules SET module_name = 'module_credential', module_description = 'Access to client credentials - usernames, passwords and 2FA codes'"); mysqli_query($mysqli, "INSERT INTO modules SET module_name = 'module_sales', module_description = 'Access to quotes, invoices and products'"); mysqli_query($mysqli, "INSERT INTO modules SET module_name = 'module_financial', module_description = 'Access to payments, accounts, expenses and budgets'"); mysqli_query($mysqli, "INSERT INTO modules SET module_name = 'module_reporting', module_description = 'Access to all reports'"); // Add default roles mysqli_query($mysqli, "INSERT INTO user_roles SET role_id = 1, role_name = 'Accountant', role_description = 'Built-in - Limited access to financial-focused modules'"); mysqli_query($mysqli, "INSERT INTO user_role_permissions SET user_role_id = 1, module_id = 1, user_role_permission_level = 1"); // Read clients mysqli_query($mysqli, "INSERT INTO user_role_permissions SET user_role_id = 1, module_id = 2, user_role_permission_level = 1"); // Read support mysqli_query($mysqli, "INSERT INTO user_role_permissions SET user_role_id = 1, module_id = 4, user_role_permission_level = 1"); // Read sales mysqli_query($mysqli, "INSERT INTO user_role_permissions SET user_role_id = 1, module_id = 5, user_role_permission_level = 2"); // Modify financial mysqli_query($mysqli, "INSERT INTO user_role_permissions SET user_role_id = 1, module_id = 6, user_role_permission_level = 1"); // Read reports mysqli_query($mysqli, "INSERT INTO user_roles SET role_id = 2, role_name = 'Technician', role_description = 'Built-in - Limited access to technical-focused modules'"); mysqli_query($mysqli, "INSERT INTO user_role_permissions SET user_role_id = 2, module_id = 1, user_role_permission_level = 2"); // Modify clients mysqli_query($mysqli, "INSERT INTO user_role_permissions SET user_role_id = 2, module_id = 2, user_role_permission_level = 2"); // Modify support mysqli_query($mysqli, "INSERT INTO user_role_permissions SET user_role_id = 2, module_id = 3, user_role_permission_level = 2"); // Modify credentials mysqli_query($mysqli, "INSERT INTO user_role_permissions SET user_role_id = 2, module_id = 4, user_role_permission_level = 2"); // Modify sales mysqli_query($mysqli, "INSERT INTO user_roles SET role_id = 3, role_name = 'Administrator', role_description = 'Built-in - Full administrative access to all modules (including user management)', role_is_admin = 1"); // Custom Links mysqli_query($mysqli,"INSERT INTO custom_links SET custom_link_name = 'Docs', custom_link_uri = 'https://docs.itflow.org', custom_link_new_tab = 1, custom_link_icon = 'question-circle'"); $_SESSION['alert_message'] = "Company $name created"; header("Location: ?localization"); } if (isset($_POST['add_localization_settings'])) { $locale = sanitizeInput($_POST['locale']); $currency_code = sanitizeInput($_POST['currency_code']); $timezone = sanitizeInput($_POST['timezone']); mysqli_query($mysqli,"UPDATE companies SET company_locale = '$locale', company_currency = '$currency_code' WHERE company_id = 1"); mysqli_query($mysqli,"UPDATE settings SET config_timezone = '$timezone' WHERE company_id = 1"); // Create Default Cash Account mysqli_query($mysqli,"INSERT INTO accounts SET account_name = 'Cash', account_currency_code = '$currency_code'"); $_SESSION['alert_message'] = "Localization Info saved"; header("Location: ?telemetry"); } if (isset($_POST['add_telemetry'])) { if (isset($_POST['share_data']) && $_POST['share_data'] == 1) { mysqli_query($mysqli,"UPDATE settings SET config_telemetry = 2"); $comments = sanitizeInput($_POST['comments']); $sql = mysqli_query($mysqli,"SELECT * FROM companies WHERE company_id = 1"); $row = mysqli_fetch_array($sql); $company_name = $row['company_name']; $website = $row['company_website']; $city = $row['company_city']; $state = $row['company_state']; $country = $row['company_country']; $currency = $row['company_currency']; $postdata = http_build_query( array( 'installation_id' => "$installation_id", 'company_name' => "$company_name", 'website' => "$website", 'city' => "$city", 'state' => "$state", 'country' => "$country", 'currency' => "$currency", 'comments' => "$comments", 'collection_method' => 1 ) ); $opts = array('http' => array( 'method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => $postdata ) ); $context = stream_context_create($opts); $result = file_get_contents('https://telemetry.itflow.org', false, $context); echo $result; } //final setup stages $myfile = fopen("../config.php", "a"); $txt = "\$config_enable_setup = 0;\n\n"; fwrite($myfile, $txt); fclose($myfile); header("Location: ../login.php"); exit; } ?> ITFlow Setup
'mysqli', 'php-intl' => 'intl', 'php-curl' => 'curl', 'php-mbstring' => 'mbstring', 'php-gd' => 'gd', ]; foreach ($extensions as $name => $ext) { $loaded = extension_loaded($ext); $phpExtensions[] = [ 'name' => "$name installed", 'passed' => $loaded, 'value' => $loaded ? 'Installed' : 'Not Installed', ]; } // Section: PHP Configuration $phpConfig = []; // Check if shell_exec is enabled $disabled_functions = explode(',', ini_get('disable_functions')); $disabled_functions = array_map('trim', $disabled_functions); $shell_exec_enabled = !in_array('shell_exec', $disabled_functions); $phpConfig[] = [ 'name' => 'shell_exec is enabled', 'passed' => $shell_exec_enabled, 'value' => $shell_exec_enabled ? 'Enabled' : 'Disabled', ]; // Check upload_max_filesize and post_max_size >= 500M function return_bytes($val) { $val = trim($val); $unit = strtolower(substr($val, -1)); $num = (float)$val; switch ($unit) { case 'g': $num *= 1024; case 'm': $num *= 1024; case 'k': $num *= 1024; } return $num; } $required_bytes = 500 * 1024 * 1024; // 500M in bytes $upload_max_filesize = ini_get('upload_max_filesize'); $post_max_size = ini_get('post_max_size'); $upload_passed = return_bytes($upload_max_filesize) >= $required_bytes; $post_passed = return_bytes($post_max_size) >= $required_bytes; $phpConfig[] = [ 'name' => 'upload_max_filesize >= 500M', 'passed' => $upload_passed, 'value' => $upload_max_filesize, ]; $phpConfig[] = [ 'name' => 'post_max_size >= 500M', 'passed' => $post_passed, 'value' => $post_max_size, ]; // Check PHP version >= 8.2.0 $php_version = PHP_VERSION; $php_passed = version_compare($php_version, '8.2.0', '>='); $phpConfig[] = [ 'name' => 'PHP version >= 8.2.0', 'passed' => $php_passed, 'value' => $php_version, ]; // Section: Shell Commands $shellCommands = []; if ($shell_exec_enabled) { $commands = ['whois', 'dig', 'git']; foreach ($commands as $command) { $which = trim(shell_exec("which $command 2>/dev/null")); $exists = !empty($which); $shellCommands[] = [ 'name' => "Command '$command' available", 'passed' => $exists, 'value' => $exists ? $which : 'Not Found', ]; } } else { // If shell_exec is disabled, mark commands as unavailable foreach (['whois', 'dig', 'git'] as $command) { $shellCommands[] = [ 'name' => "Command '$command' available", 'passed' => false, 'value' => 'shell_exec Disabled', ]; } } // Section: SSL Checks $sslChecks = []; // Check if accessing via HTTPS $https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] == 443; $sslChecks[] = [ 'name' => 'Accessing via HTTPS', 'passed' => $https, 'value' => $https ? 'Yes' : 'No', ]; // SSL Certificate Validity Check if ($https) { $streamContext = stream_context_create(["ssl" => ["capture_peer_cert" => true]]); $socket = @stream_socket_client("ssl://{$_SERVER['HTTP_HOST']}:443", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $streamContext); if ($socket) { $params = stream_context_get_params($socket); $cert = $params['options']['ssl']['peer_certificate']; $certInfo = openssl_x509_parse($cert); $validFrom = $certInfo['validFrom_time_t']; $validTo = $certInfo['validTo_time_t']; $currentTime = time(); $certValid = ($currentTime >= $validFrom && $currentTime <= $validTo); $sslChecks[] = [ 'name' => 'SSL Certificate is valid', 'passed' => $certValid, 'value' => $certValid ? 'Valid' : 'Invalid or Expired', ]; } else { $sslChecks[] = [ 'name' => 'SSL Certificate is valid', 'passed' => false, 'value' => 'Unable to retrieve certificate', ]; } } else { $sslChecks[] = [ 'name' => 'SSL Certificate is valid', 'passed' => false, 'value' => 'Not using HTTPS', ]; } // Section: Domain Checks $domainChecks = []; // Check if the site has a valid FQDN $fqdn = $_SERVER['HTTP_HOST']; $isValidFqdn = (bool) filter_var('http://' . $fqdn, FILTER_VALIDATE_URL) && preg_match('/^[a-z0-9.-]+\.[a-z]{2,}$/i', $fqdn); $domainChecks[] = [ 'name' => 'Site has a valid FQDN', 'passed' => $isValidFqdn, 'value' => $fqdn, ]; // Section: File Permissions $filePermissions = []; // Check if web user has write access to webroot directory $webroot = $_SERVER['DOCUMENT_ROOT']; $writable = is_writable($webroot); $filePermissions[] = [ 'name' => 'Web user has write access to webroot directory', 'passed' => $writable, 'value' => $webroot, ]; ?>

Step 1 - Setup Checks

PHP Extensions
PHP Configuration
Shell Commands
SSL Checks
Domain Checks
File Permissions

Next (Database)

Step 2 - Connect your Database

Database is already configured. Any further changes should be made by editing the config.php file.

"; if (@$mysqli) { echo "Next Step (User Setup) "; } else { echo "
Database connection failed. Check config.php.
"; } } else { ?>
Database Connection Details

Database Authentication Details

Database Not Ready

You must configure the database before restoring a backup.

Go to Database Setup

Restore from Backup

Large restores may take several minutes. Do not close this page.


Step 3 - Create your first user


Step 4 - Company Details


Step 5 - Region and Language


Step 6 - Telemetry

Would you like to share some data with us?



Post installation steps:

A few housekeeping steps are required to ensure everything runs smoothly, namely:


ITFlow Setup

Thank you for choosing to try ITFlow!

This is the start of your journey towards amazing client management

A few tips:

  • Please take a look over the install docs, if you haven't already
  • Don't hesitate to reach out on the forums if you need any assistance
  • Apache/PHP Error log:

A database must be created before proceeding - click on the button below to get started.



ITFlow is free software: you can redistribute and/or modify it under the terms of the GNU General Public License.
It 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.

Warning: The current directory is not writable. Ensure the webserver process has write access (chmod/chown). Check the docs for info.
"; } ?>