getSession($session); $routerId = $creds['id'] ?? null; // If no ID (Legacy), fallback to empty list or handle gracefully. // For now, we assume ID exists as per migration plan. $packages = $routerId ? $qpModel->getAllByRouterId($routerId) : []; $data = [ 'session' => $session, 'packages' => $packages ]; // Note: View will be 'quick_print/index' return $this->view('quick_print/index', $data); } // List/Manage Packages (CRUD) public function manage($session) { $qpModel = new QuickPrintModel(); $configModel = new Config(); $creds = $configModel->getSession($session); $routerId = $creds['id'] ?? null; $packages = $routerId ? $qpModel->getAllByRouterId($routerId) : []; $profiles = []; if ($creds) { $API = new RouterOSAPI(); $password = $creds['password']; if (isset($creds['source']) && $creds['source'] === 'legacy') { $password = RouterOSAPI::decrypt($password); } if ($API->connect($creds['ip'], $creds['user'], $password)) { $profiles = $API->comm("/ip/hotspot/user/profile/print"); $API->disconnect(); } } $data = [ 'session' => $session, 'packages' => $packages, 'profiles' => $profiles ]; return $this->view('quick_print/list', $data); } // CRUD: Store public function store() { if ($_SERVER['REQUEST_METHOD'] !== 'POST') return; $session = $_POST['session'] ?? ''; $configModel = new Config(); $creds = $configModel->getSession($session); $routerId = $creds['id'] ?? 0; $data = [ 'router_id' => $routerId, 'session_name' => $session, 'name' => $_POST['name'] ?? 'Package', 'server' => $_POST['server'] ?? 'all', 'profile' => $_POST['profile'] ?? 'default', 'prefix' => $_POST['prefix'] ?? '', 'char_length' => $_POST['char_length'] ?? 4, 'price' => $_POST['price'] ?? 0, 'selling_price' => $_POST['selling_price'] ?? ($_POST['price'] ?? 0), 'time_limit' => $_POST['time_limit'] ?? '', 'data_limit' => $_POST['data_limit'] ?? '', 'comment' => $_POST['comment'] ?? '', 'color' => $_POST['color'] ?? 'bg-blue-500' ]; $qpModel = new QuickPrintModel(); $qpModel->add($data); \App\Helpers\FlashHelper::set('success', 'toasts.package_saved', 'toasts.package_saved_desc', [], true); header("Location: /" . $session . "/quick-print/manage"); exit; } // CRUD: Update public function update() { if ($_SERVER['REQUEST_METHOD'] !== 'POST') return; $session = $_POST['session'] ?? ''; $id = $_POST['id'] ?? ''; if (empty($id)) { \App\Helpers\FlashHelper::set('error', 'common.error', 'toasts.error_missing_id', [], true); header("Location: /" . $session . "/quick-print/manage"); exit; } $data = [ 'name' => $_POST['name'] ?? 'Package', 'profile' => $_POST['profile'] ?? 'default', 'prefix' => $_POST['prefix'] ?? '', 'char_length' => $_POST['char_length'] ?? 4, 'price' => $_POST['price'] ?? 0, 'selling_price' => $_POST['selling_price'] ?? ($_POST['price'] ?? 0), 'time_limit' => $_POST['time_limit'] ?? '', 'data_limit' => $_POST['data_limit'] ?? '', 'comment' => $_POST['comment'] ?? '', 'color' => $_POST['color'] ?? 'bg-blue-500' ]; $qpModel = new QuickPrintModel(); $qpModel->update($id, $data); // Assuming update method exists in simple JSON model \App\Helpers\FlashHelper::set('success', 'toasts.package_updated', 'toasts.package_updated_desc', [], true); header("Location: /" . $session . "/quick-print/manage"); exit; } // CRUD: Delete public function delete() { if ($_SERVER['REQUEST_METHOD'] !== 'POST') return; $session = $_POST['session'] ?? ''; $id = $_POST['id'] ?? ''; $qpModel = new QuickPrintModel(); $qpModel->delete($id); \App\Helpers\FlashHelper::set('success', 'toasts.package_deleted', 'toasts.package_deleted_desc', [], true); header("Location: /" . $session . "/quick-print/manage"); exit; } // ACTION: Generate User & Print public function printPacket($session, $id) { // 1. Get Package Details $qpModel = new QuickPrintModel(); $package = $qpModel->getById($id); if (!$package) { die("Package not found"); } // 2. Generate Credentials $prefix = $package['prefix']; $length = $package['char_length']; $charSet = '1234567890abcdefghijklmnopqrstuvwxyz'; // Simple lowercase + num $rand = substr(str_shuffle($charSet), 0, $length); $username = $prefix . $rand; $password = $username; // Default: user=pass (User Mode) - Can be improved later // 3. Connect to Mikrotik & Add User $configModel = new Config(); $creds = $configModel->getSession($session); if (!$creds) die("Session error"); $API = new RouterOSAPI(); $password_router = $creds['password']; if (isset($creds['source']) && $creds['source'] === 'legacy') { $password_router = RouterOSAPI::decrypt($password_router); } if ($API->connect($creds['ip'], $creds['user'], $password_router)) { $userData = [ 'name' => $username, 'password' => $password, 'profile' => $package['profile'], 'comment' => $package['comment'] . " [QP]" // Mark as QuickPrint ]; // Limits if(!empty($package['time_limit'])) $userData['limit-uptime'] = $package['time_limit']; if(!empty($package['data_limit'])) { // Check if M or G // Simple logic for now, assuming raw if number, or passing string if Mikrotik accepts it (usually requires bytes) // Let's assume user inputs "100M" or "1G" which usually needs parsing. // For now, let's assume input is NUMBER in MB as per standard Mivo practice, OR generic string. // We'll pass as is for strings, or multiply if strictly numeric? // Let's rely on standard Mikrotik parsing if string passed, or convert. // Mivo usually uses dropdown "MB/GB". // Implementing simple conversion: $val = intval($package['data_limit']); if (strpos(strtolower($package['data_limit']), 'g') !== false) { $userData['limit-bytes-total'] = $val * 1024 * 1024 * 1024; } else { $userData['limit-bytes-total'] = $val * 1024 * 1024; // Default MB } } $API->comm("/ip/hotspot/user/add", $userData); $API->disconnect(); } else { \App\Helpers\FlashHelper::set('error', 'Connection Failed', 'Could not connect to router at ' . $creds['ip']); header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? '/' . $session . '/quick-print/manage')); exit; } // 4. Render Template $tplModel = new VoucherTemplateModel(); $templates = $tplModel->getAll(); $currentTemplate = $_GET['template'] ?? 'default'; $templateContent = ''; $viewName = 'print/default'; if ($currentTemplate !== 'default') { $tpl = $tplModel->getById($currentTemplate); if ($tpl) { $templateContent = $tpl['content']; $viewName = 'print/custom'; } else { $currentTemplate = 'default'; } } // Calculate bytes for display $dlVal = intval($package['data_limit']); $bytes = (strpos(strtolower($package['data_limit']), 'g') !== false) ? $dlVal * 1024*1024*1024 : $dlVal * 1024*1024; $userDataValues = [ 'username' => $username, 'password' => $password, 'price' => $package['price'], 'validity' => $package['time_limit'], 'timelimit' => \App\Helpers\HotspotHelper::formatValidity($package['time_limit']), 'datalimit' => \App\Helpers\HotspotHelper::formatBytes($bytes), 'profile' => $package['profile'], 'comment' => 'Quick Print', 'hotspotname' => $creds['hotspot_name'], 'dns_name' => $creds['dns_name'], 'login_url' => (preg_match("~^(?:f|ht)tps?://~i", $creds['dns_name']) ? $creds['dns_name'] : "http://" . $creds['dns_name']) . "/login" ]; // --- Logo Handling --- $logoModel = new \App\Models\Logo(); $logos = $logoModel->getAll(); $logoMap = []; foreach ($logos as $l) { $logoMap[$l['id']] = $l['path']; } $data = [ 'users' => [$userDataValues], 'templates' => $templates, 'currentTemplate' => $currentTemplate, 'templateContent' => $templateContent, 'session' => $session, 'logoMap' => $logoMap ]; return $this->view($viewName, $data); } }