pdo = $pdo; $this->company_id = $company_id; $this->user_id = $user_id; } // ───────────────────────────────────────────────────────────── // READ / IDENTITY BASIS // ───────────────────────────────────────────────────────────── /** * Return the current user's profile row. * * Used to populate the profile settings page and to bootstrap the UI * with the logged-in user's name and avatar. * * @return array Keys: user_id, username, name, surname, email, phone, * country, address, profile_picture. * @throws Exception If the user row is not found. */ public function getProfile(): array { $sth = $this->pdo->prepare( "SELECT user_id, username, name, surname, email, phone, country, address, profile_picture FROM user WHERE user_id = :user_id LIMIT 1" ); $sth->execute([':user_id' => $this->user_id]); $user = $sth->fetch(PDO::FETCH_ASSOC); if (!$user) throw new Exception('User not found.'); return $user; } /** * Return all companies the current user belongs to, plus their role in each. * * Used to populate the branch-switcher dropdown. Results are ordered by * company name so the list is stable regardless of membership order. * * @return array Each row: company_id, channel_name, company_name, branch, role. */ public function getCompanyList(): array { $sth = $this->pdo->prepare( "SELECT cl.company_id, cl.channel_name, cl.company_name, cl.branch, cmu.role FROM company_map_user cmu JOIN company_list cl ON cl.company_id = cmu.company_id WHERE cmu.user_id = :user_id ORDER BY cl.company_name ASC" ); $sth->execute([':user_id' => $this->user_id]); return $sth->fetchAll(PDO::FETCH_ASSOC); } // ───────────────────────────────────────────────────────────── // COMPANY MEMBER BASIS // ───────────────────────────────────────────────────────────── /** * Return all users mapped to the current company, ordered by role then name. * * Role order: owner → admin → staff → viewer. * Used to populate the user management table on the settings page. * * @return array Each row: map_id, role, created_at, user_id, username, * name, surname, email, profile_picture. */ public function getCompanyUsers(): array { $sth = $this->pdo->prepare( "SELECT m.map_id, m.role, m.created_at, u.user_id, u.username, u.name, u.surname, u.email, u.profile_picture FROM company_map_user m JOIN user u ON u.user_id = m.user_id WHERE m.company_id = :company_id ORDER BY FIELD(m.role, 'owner', 'admin', 'staff', 'viewer'), u.name ASC" ); $sth->execute([':company_id' => $this->company_id]); return $sth->fetchAll(PDO::FETCH_ASSOC); } /** * Search for registered users by email keyword, excluding existing members. * * Returns up to 10 matches. Used for the invite autocomplete input. * Excludes users already mapped to this company so the results only show * people who can actually be invited. * * @param string $keyword Partial email to match. * @return array Each row: user_id, username, name, surname, email. */ public function searchUsers(string $keyword): array { if ($keyword === '') return []; $sth = $this->pdo->prepare( "SELECT u.user_id, u.username, u.name, u.surname, u.email FROM user u WHERE u.email LIKE :kw AND u.user_id NOT IN ( SELECT user_id FROM company_map_user WHERE company_id = :company_id ) ORDER BY u.email ASC LIMIT 10" ); $sth->execute([ ':kw' => '%' . $keyword . '%', ':company_id' => $this->company_id, ]); return $sth->fetchAll(PDO::FETCH_ASSOC); } /** * Add a registered user to the current company by email. * * Validates email format, role, and that the target account exists. * Blocks inviting self or someone already mapped to the company. * * @param string $email Email address of the user to invite. * @param string $role Role to assign: 'admin', 'staff', or 'viewer'. * @return string The invited user's email, for use in the success message. * @throws Exception On any validation or constraint failure. */ public function inviteUser(string $email, string $role): string { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new Exception('Invalid email address.'); } $allowed_roles = ['admin', 'staff', 'viewer']; if (!in_array($role, $allowed_roles, true)) { throw new Exception('Invalid role selected.'); } $sth = $this->pdo->prepare( "SELECT user_id, email FROM user WHERE email = :email LIMIT 1" ); $sth->execute([':email' => $email]); $target = $sth->fetch(PDO::FETCH_ASSOC); if (!$target) { throw new Exception('No registered account found with that email address.'); } $target_user_id = (int)$target['user_id']; if ($target_user_id === $this->user_id) { throw new Exception('You cannot invite yourself.'); } $sth = $this->pdo->prepare( "SELECT map_id FROM company_map_user WHERE company_id = :company_id AND user_id = :user_id LIMIT 1" ); $sth->execute([':company_id' => $this->company_id, ':user_id' => $target_user_id]); if ($sth->fetch()) { throw new Exception('This user is already a member of your company.'); } $this->pdo->prepare( "INSERT INTO company_map_user (company_id, user_id, role, created_at) VALUES (:company_id, :user_id, :role, NOW())" )->execute([ ':company_id' => $this->company_id, ':user_id' => $target_user_id, ':role' => $role, ]); return $target['email']; } /** * Change the role of a company member. * * Blocks changing the owner's role. Only 'admin', 'staff', 'viewer' * are assignable — the owner role is set at company creation and is immutable. * * @param int $map_id The company_map_user.map_id to update. * @param string $role New role: 'admin', 'staff', or 'viewer'. * @throws Exception If the member is not found or is the owner. */ public function updateRole(int $map_id, string $role): void { $allowed_roles = ['admin', 'staff', 'viewer']; if (!$map_id || !in_array($role, $allowed_roles, true)) { throw new Exception('Invalid request.'); } $sth = $this->pdo->prepare( "SELECT role FROM company_map_user WHERE map_id = :map_id AND company_id = :company_id LIMIT 1" ); $sth->execute([':map_id' => $map_id, ':company_id' => $this->company_id]); $current_role = $sth->fetchColumn(); if ($current_role === false) throw new Exception('User not found.'); if ($current_role === 'owner') throw new Exception('Owner role cannot be changed.'); $this->pdo->prepare( "UPDATE company_map_user SET role = :role WHERE map_id = :map_id AND company_id = :company_id" )->execute([ ':role' => $role, ':map_id' => $map_id, ':company_id' => $this->company_id, ]); } /** * Remove a user from the current company. * * Blocks removing the owner or removing yourself. * Hard-deletes the company_map_user row — membership history is not retained. * * @param int $map_id The company_map_user.map_id to delete. * @throws Exception If the member is not found, is the owner, or is the caller. */ public function removeUser(int $map_id): void { if (!$map_id) throw new Exception('Invalid request.'); $sth = $this->pdo->prepare( "SELECT role, user_id FROM company_map_user WHERE map_id = :map_id AND company_id = :company_id LIMIT 1" ); $sth->execute([':map_id' => $map_id, ':company_id' => $this->company_id]); $row = $sth->fetch(PDO::FETCH_ASSOC); if (!$row) throw new Exception('User not found.'); if ($row['role'] === 'owner') throw new Exception('The owner cannot be removed.'); if ((int)$row['user_id'] === $this->user_id) throw new Exception('You cannot remove yourself.'); $this->pdo->prepare( "DELETE FROM company_map_user WHERE map_id = :map_id AND company_id = :company_id" )->execute([':map_id' => $map_id, ':company_id' => $this->company_id]); } }