chartboard/app/Controllers/Settings/SettingsController.php
2026-03-30 09:51:33 +05:30

232 lines
8.7 KiB
PHP

<?php
namespace App\Controllers\Settings;
use App\Controllers\BaseController;
use App\Libraries\AuditLogger;
use App\Models\SettingsModel;
use App\Models\WorkspaceMemberModel;
use App\Models\WorkspaceModel;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Email as EmailConfig;
use Config\Services;
class SettingsController extends BaseController
{
private function ensureWorkspaceMembership(int $workspaceId): bool
{
return (new WorkspaceMemberModel())
->where('workspace_id', $workspaceId)
->where('user_id', (int) $this->session->get('user_id'))
->first() !== null;
}
public function workspace()
{
$workspaceId = (int) $this->session->get('active_workspace_id');
if (! $this->ensureWorkspaceMembership($workspaceId)) {
return redirect()->to('/workspace')->with('error', 'Access denied.');
}
$workspace = (new WorkspaceModel())->find($workspaceId);
if (! $workspace) {
return redirect()->to('/workspace')->with('error', 'Workspace not found.');
}
$defaultTheme = (new SettingsModel())->getValue($workspaceId, 'default_theme', 'system') ?? 'system';
return view('settings/workspace', [
'title' => 'Workspace preferences | Chart-Board',
'workspace' => $workspace,
'default_theme' => $defaultTheme,
]);
}
public function saveWorkspace()
{
$workspaceId = (int) $this->session->get('active_workspace_id');
if (! $this->ensureWorkspaceMembership($workspaceId)) {
return redirect()->to('/workspace')->with('error', 'Access denied.');
}
$workspaceModel = new WorkspaceModel();
$workspace = $workspaceModel->find($workspaceId);
if (! $workspace) {
return redirect()->to('/workspace')->with('error', 'Workspace not found.');
}
$rules = [
'name' => 'required|min_length[3]|max_length[150]',
'description' => 'permit_empty|max_length[1000]',
'timezone' => 'required|max_length[80]',
'default_refresh' => 'required|in_list[60,300,900,3600]',
'is_active' => 'required|in_list[0,1]',
'default_theme' => 'required|in_list[light,dark,system]',
'logo' => 'permit_empty|uploaded[logo]|max_size[logo,512]|is_image[logo]|mime_in[logo,image/jpg,image/jpeg,image/png,image/webp,image/svg+xml]',
];
$hasLogo = $this->request->getFile('logo') && $this->request->getFile('logo')->isValid();
if (! $hasLogo) {
unset($rules['logo']);
}
if (! $this->validate($rules)) {
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
}
$update = [
'name' => strip_tags((string) $this->request->getPost('name')),
'slug' => (string) $this->request->getPost('slug'),
'description' => strip_tags((string) $this->request->getPost('description')),
'timezone' => (string) $this->request->getPost('timezone'),
'default_refresh' => (int) $this->request->getPost('default_refresh'),
'is_active' => (int) $this->request->getPost('is_active'),
];
if ($hasLogo) {
$logo = $this->request->getFile('logo');
$logoName = $logo->getRandomName();
$logo->move(WRITEPATH . 'uploads/workspaces', $logoName);
$update['logo'] = 'writable/uploads/workspaces/' . $logoName;
}
$workspaceModel->update($workspaceId, $update);
(new SettingsModel())->setValue(
$workspaceId,
'default_theme',
(string) $this->request->getPost('default_theme'),
'string'
);
AuditLogger::log(
'settings.workspace_saved',
'workspace',
$workspaceId,
null,
['name' => $update['name'], 'default_theme' => (string) $this->request->getPost('default_theme')],
$workspaceId,
(int) $this->session->get('user_id')
);
return redirect()->to('/settings/workspace')->with('success', 'Workspace preferences saved.');
}
public function notifications()
{
$settings = new SettingsModel();
$slackUrl = $settings->getValue(null, 'slack_webhook_url', '') ?? '';
$emailConfig = new EmailConfig();
return view('settings/notifications', [
'title' => 'Notification settings | Chart-Board',
'smtpHost' => $emailConfig->SMTPHost,
'smtpPort' => $emailConfig->SMTPPort,
'smtpUser' => $emailConfig->SMTPUser,
'smtpCrypto' => $emailConfig->SMTPCrypto,
'fromEmail' => $emailConfig->fromEmail,
'slackWebhookUrl' => $slackUrl,
]);
}
public function saveSlackWebhook(): ResponseInterface
{
$rules = ['slack_webhook_url' => 'permit_empty|max_length[2000]'];
if (! $this->validate($rules)) {
return redirect()->back()->with('errors', $this->validator->getErrors());
}
$url = trim((string) $this->request->getPost('slack_webhook_url'));
(new SettingsModel())->setValue(null, 'slack_webhook_url', $url, 'string');
AuditLogger::log(
'settings.slack_webhook_saved',
'settings',
null,
null,
['slack_webhook_url' => $url !== '' ? '[redacted]' : 'cleared'],
(int) $this->session->get('active_workspace_id') ?: null,
(int) $this->session->get('user_id')
);
return redirect()->to('/settings/notifications')->with('success', 'Slack webhook URL saved.');
}
public function testSmtp(): ResponseInterface
{
$userId = (int) $this->session->get('user_id');
$user = (new \App\Models\UserModel())->find($userId);
if (! $user) {
return redirect()->back()->with('error', 'User not found.');
}
$to = (string) ($user['email'] ?? '');
if (! filter_var($to, FILTER_VALIDATE_EMAIL)) {
return redirect()->back()->with('error', 'Your profile email is invalid.');
}
$config = new EmailConfig();
$email = Services::email();
$email->setFrom($config->fromEmail, $config->fromName);
$email->setTo($to);
$email->setSubject('Chart-Board SMTP test');
$email->setMessage('<p>This is a test message from Chart-Board. If you received it, SMTP is configured correctly.</p>');
if (! $email->send()) {
return redirect()->back()->with('error', 'SMTP test failed. Check server mail configuration.');
}
return redirect()->back()->with('success', 'Test email sent to your account email address.');
}
public function testSlack(): ResponseInterface
{
$url = trim((string) (new SettingsModel())->getValue(null, 'slack_webhook_url', '') ?? '');
if ($url === '' || ! filter_var($url, FILTER_VALIDATE_URL)) {
return redirect()->back()->with('error', 'Save a valid Slack webhook URL first.');
}
$payload = json_encode(['text' => 'Chart-Board: Slack webhook test from ' . date('c')], JSON_UNESCAPED_UNICODE);
$ch = curl_init($url);
if ($ch === false) {
return redirect()->back()->with('error', 'Could not start HTTP request.');
}
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
]);
$body = curl_exec($ch);
$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code < 200 || $code >= 300) {
return redirect()->back()->with('error', 'Slack returned HTTP ' . $code . '.');
}
return redirect()->back()->with('success', 'Slack test message sent.');
}
public function saveTheme()
{
$rules = ['theme' => 'required|in_list[light,dark,system]'];
if (! $this->validate($rules)) {
return redirect()->back()->with('error', 'Invalid theme.');
}
$theme = (string) $this->request->getPost('theme');
$userId = (int) $this->session->get('user_id');
if ($userId <= 0) {
return redirect()->to('/login');
}
(new \App\Models\UserModel())->skipValidation(true)->update($userId, ['theme_preference' => $theme]);
$this->session->set('theme_preference', $theme);
return redirect()->back()->with('success', 'Theme preference saved.');
}
}