projectRepository = $projectRepository; $this->clientRepository = $clientRepository; $this->commentService = $commentService; $this->fileService = $fileService; $this->userRepository = $userRepository; } /** * Gets clients accessible by a specific user based on their project assignments. * * @param int $userId The user ID * @return array List of clients the user has access to * * @internal Internal-only; deliberately excluded from the JSON-RPC surface. The $userId is * caller-supplied, so exposing it would let any user enumerate another user's * client list (IDOR). Only ever called internally with the session user's id * (e.g. the roadmap/milestone client-filter dropdown). */ public function getUserClients(int $userId): array { $userProjects = $this->projectRepository->getUserProjects($userId); $clients = []; if (is_array($userProjects)) { foreach ($userProjects as $project) { if (! array_key_exists($project['clientId'], $clients)) { $clients[$project['clientId']] = ['id' => $project['clientId'], 'name' => $project['clientName']]; } } } return $clients; } /** * Gets all clients. * * @param array|null $searchparams Optional search parameters * @return array List of all clients * * @api */ #[RequiresPermission(ClientsPermissions::VIEW, global: true)] public function getAll(?array $searchparams = null): array { return $this->clientRepository->getAll(); } /** * Patches the client by key. * * @param int $id Id of the object to be patched * @param array $params Key=>value array where key represents the object field name and value the value * @return bool Returns true on success, false on failure * * @api */ #[RequiresPermission(ClientsPermissions::EDIT, global: true)] public function patch(int $id, array $params): bool { return $this->clientRepository->patch($id, $params); } /** * Updates an existing client. * * @param array $values Client data including 'id' key * @return bool Returns true on success, false on failure * * @api */ #[RequiresPermission(ClientsPermissions::EDIT, global: true)] public function editClient(array $values): bool { return $this->clientRepository->editClient($values, $values['id']); } /** * Creates a new client. * * @param array $values Client data to create * @return int|false Returns id of new element or false * * @api */ #[RequiresPermission(ClientsPermissions::CREATE, global: true)] public function create(array $values): int|false { return $this->clientRepository->addClient($values); } /** * Deletes a client and its associated projects. * * @param int $id Id of the client to be deleted * @return bool Returns true on success, false on failure * * @api */ #[RequiresPermission(ClientsPermissions::DELETE, global: true)] public function delete(int $id): bool { return $this->clientRepository->deleteClient($id); } /** * Gets 1 specific client by id. * * @param int $id Id of the client to be retrieved * @return array|false Returns client data or false if not found * * @api */ #[RequiresPermission(ClientsPermissions::VIEW, global: true)] public function get(int $id): array|false { return $this->clientRepository->getClient($id); } /** * Checks if a client with the same name and street already exists. * * @param array $values Client data with 'name' and 'street' keys * @return bool Returns true if client exists * * @api */ #[RequiresPermission(ClientsPermissions::VIEW, global: true)] public function isClient(array $values): bool { return $this->clientRepository->isClient($values); } /** * Checks if a client has any tickets via its projects. * * @param int $id Client id * @return bool Returns true if client has tickets * * @api */ #[RequiresPermission(ClientsPermissions::VIEW, global: true)] public function hasTickets(int $id): bool { return $this->clientRepository->hasTickets($id); } /** * Gets all users assigned to a client. * * @param int $clientId Client id * @return array|false Returns list of users or false * * @api */ #[RequiresPermission(ClientsPermissions::VIEW, global: true)] public function getClientsUsers(int $clientId): array|false { return $this->clientRepository->getClientsUsers($clientId); } /** * Gets projects belonging to a client. * * @param int $clientId Client id * @return array List of projects for this client * * @api */ #[RequiresPermission(ClientsPermissions::VIEW, global: true)] public function getClientProjects(int $clientId): array { return $this->projectRepository->getClientProjects($clientId); } /** * Creates a new client after validating it and checking for duplicates. * * Encapsulates the name-required validation and the duplicate-name check * that previously lived in the controller. * * @param array $values Client data to create (requires a non-empty 'name') * @return int Id of the newly created client * * @throws MissingParameterException When the client name is empty * @throws EntityExistsException When a client with the same name/street already exists * * @api */ #[RequiresPermission(ClientsPermissions::CREATE, global: true)] public function createClient(array $values): int { if (($values['name'] ?? '') === '') { throw new MissingParameterException('Client name not specified'); } if ($this->isClient($values) === true) { throw new EntityExistsException('Client exists already'); } return (int) $this->clientRepository->addClient($values); } /** * Updates an existing client after validating the name is present. * * @param array $values Client data including 'id' key (requires a non-empty 'name') * @return bool Returns true on success, false on failure * * @throws MissingParameterException When the client name is empty * * @api */ #[RequiresPermission(ClientsPermissions::EDIT, global: true)] public function updateClient(array $values): bool { if (($values['name'] ?? '') === '') { throw new MissingParameterException('Client name not specified'); } return $this->editClient($values); } /** * Removes a user from a client by clearing the user's client assignment. * * Keeps the Clients controllers within the Clients service surface while the * underlying mutation lives on the Users repository. * * @param int $clientId Client the user should be removed from * @param int $userId User to remove from the client * @return bool Returns true on success, false on failure * * @api */ #[RequiresPermission(ClientsPermissions::EDIT, global: true)] public function removeUser(int $clientId, int $userId): bool { if ($clientId === 0 || $userId === 0) { return false; } return $this->userRepository->removeFromClient($userId); } /** * Assembles the template data needed to render the client detail page. * * Centralizes the shared assignment block previously duplicated across the * GET and POST handlers of the ShowClient controller. * * @param int $id Client id * @return array{userClients: array|false, comments: array|false, imgExtensions: array, clientProjects: array, files: array|false} * * @api */ #[RequiresPermission(ClientsPermissions::VIEW, global: true)] public function getClientPageData(int $id): array { return [ 'userClients' => $this->getClientsUsers($id), 'comments' => $this->commentService->getComments('client', $id), 'imgExtensions' => ['jpg', 'jpeg', 'png', 'gif', 'psd', 'bmp', 'tif', 'thm', 'yuv'], 'clientProjects' => $this->getClientProjects($id), 'files' => $this->fileService->getFilesByModule('client', $id), ]; } }