Settings - every settings page at a glance (functions/settings.php), * each with how it is set now; the settings that need looking at, at the top; * and Find a setting, which searches every setting by its name or what it is * for and goes straight to it (js/settings.js). */ require_once "includes/inc_all_admin.php"; $company = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT company_name, company_city, company_state, company_country, company_logo, company_locale, company_currency, company_email FROM companies WHERE company_id = 1")) ?: []; $localization = settingsLocalizationArrays(); $themes = settingsThemes(); $retention = appLogRetention(); $forward = logForwardSettings(); $on_off = function ($on) { return $on ? 'on' : 'off'; }; $days = function ($n) { return $n == 1 ? '1 day' : intval($n) . ' days'; }; // How each page is set now - a line or two, and whether it is switched off $start_pages = settingsStartPages(); $modules_on = array_keys(array_filter([ 'IT documentation' => $config_module_enable_itdoc, 'Ticketing' => $config_module_enable_ticketing, 'Invoicing' => $config_module_enable_accounting, 'Client portal' => $config_client_portal_enable, ])); $where = array_filter([$company['company_city'] ?? '', $company['company_country'] ?? '']); $summaries = [ 'company' => escapeHtml($company['company_name'] ?? '') . ($where ? ' · ' . escapeHtml(implode(', ', $where)) : '') . (empty($company['company_logo']) ? ' · no logo yet' : ''), 'localization' => escapeHtml(implode(' · ', array_filter([$localization['locales'][$company['company_locale'] ?? ''] ?? '', $company['company_currency'] ?? '', $config_timezone]))), 'theme' => '' . escapeHtml($themes[$config_theme] ?? $config_theme) . (file_exists(dirname(__DIR__) . '/uploads/favicon.ico') ? ' · its own tab icon' : ''), 'default' => 'Opens on ' . escapeHtml($start_pages[$config_start_page] ?? $config_start_page) . ' · ' . ($config_default_net_terms ? 'net ' . intval($config_default_net_terms) : 'due on receipt') . ($config_default_hourly_rate > 0 ? ' · ' . escapeHtml(numfmt_format_currency($currency_format, $config_default_hourly_rate, $session_company_currency)) . ' an hour' : ''), 'ticket' => 'Next ' . escapeHtml($config_ticket_prefix . $config_ticket_next_number) . ' · email to ticket ' . $on_off($config_ticket_email_parse), 'project' => 'Next ' . escapeHtml($config_project_prefix . $config_project_next_number), 'quote' => 'Next ' . escapeHtml($config_quote_prefix . $config_quote_next_number), 'invoice' => 'Next ' . escapeHtml($config_invoice_prefix . $config_invoice_next_number) . ' · reminders ' . $on_off($config_send_invoice_reminders) . ' · late fees ' . $on_off($config_invoice_late_fee_enable) . ' · cards ' . $on_off(billingStripeProvider()), 'mail' => 'Sending ' . ($config_smtp_provider ? 'on' : 'off') . ' · receiving ' . $on_off($config_imap_provider) . (mailQueueSummary()['failed_week'] ? ' · ' . mailQueueSummary()['failed_week'] . ' failed this week' : ''), 'notification' => 'Domain alerts ' . $on_off($config_enable_alert_domain_expire) . ' · ticket emails to clients ' . $on_off($config_ticket_client_general_notifications), 'security' => 'Login key ' . $on_off($config_login_key_required) . ' · devices remembered ' . $days($config_login_remember_me_expire), 'client_portal' => 'Two-step sign-in ' . ($config_client_portal_require_mfa ? 'required' : 'optional'), 'identity' => 'Microsoft Entra ' . ($config_azure_client_id ? 'set up' : 'not set up'), 'module' => $modules_on ? escapeHtml(implode(', ', $modules_on)) : 'None in use', 'logs' => 'Audit log ' . ($retention['audit'] ? 'kept ' . $days($retention['audit']) : 'kept forever') . ' · ' . ($forward['enabled'] ? 'sent to ' . escapeHtml($forward['host']) : 'not sent on'), 'telemetry' => $config_telemetry ? 'Nightly report on' : 'Off', ]; // What needs looking at $attention = []; if (empty($config_smtp_provider)) { $attention[] = ['danger', 'fa-envelope', 'Mail sending is off', 'No email leaves ITFlow - not invoices, reminders, ticket replies or password resets.', 'settings_mail.php?tab=sending#setting=config_smtp_provider', 'Set up sending']; } if ($config_ticket_email_parse && $config_module_enable_ticketing && empty($config_imap_provider)) { $attention[] = ['warning', 'fa-inbox', 'Email to ticket has no mailbox', 'Turning emails into tickets is on, but no mailbox is set up for ITFlow to read.', 'settings_mail.php?tab=receiving#setting=config_imap_provider', 'Set up receiving']; } if ($config_login_key_required && trim((string) $config_login_key_secret) === '') { $attention[] = ['warning', 'fa-key', 'A login key is required, but there isn\'t one', 'The sign-in page asks for a key nobody has been given.', 'settings_security.php#setting=config_login_key_secret', 'Set the key']; } // The mail queue - email given up on, or a queue that isn't moving (sending off is said above) $mail_queue = mailQueueSummary(); if ($mail_queue['failed_day']) { $attention[] = ['danger', 'fa-envelope-open-text', $mail_queue['failed_day'] . ' email' . ($mail_queue['failed_day'] === 1 ? '' : 's') . ' failed to send', 'In the last day - ITFlow tried ' . MAIL_QUEUE_TRIES . ' times and gave up. The Mail Queue says why, and can try again.', 'mail_queue.php?show=failed', 'See why']; } $mail_queue_health = mailQueueHealth(); if (!empty($config_smtp_provider) && in_array($mail_queue_health['key'], ['cron_off', 'cron_stopped', 'job_off', 'slow'], true)) { $attention[] = [$mail_queue_health['level'], 'fa-inbox', $mail_queue_health['title'], $mail_queue_health['text'], 'mail_queue.php', 'Mail Queue']; } // Taking card payments - test keys, an account that is archived or gone, fees not being recorded if ($config_module_enable_accounting) { foreach (paymentProviderProblems() as $problem) { $attention[] = $problem; } } // AI requests failing - and still failing foreach (aiProblems() as $problem) { $attention[] = $problem; } if (trim((string) $config_timezone) === '') { $attention[] = ['warning', 'fa-clock', 'No timezone', 'Dates and times are in the server\'s timezone until one is picked.', 'settings_localization.php#setting=config_timezone', 'Pick one']; } $pages = settingsPages(); $groups = settingsGroups(); ?>

Settings

How ITFlow is set up, for everyone in it. Your own preferences are under your name, top right.

-
$group_title) { ?>

$page) { if ($page['group'] !== $group_key) { continue; } $available = settingsPageAvailable($page); ?>