1b9a4a6c6SGreg Roach<?php 2b9a4a6c6SGreg Roach 3b9a4a6c6SGreg Roach/** 4b9a4a6c6SGreg Roach * webtrees: online genealogy 5b9a4a6c6SGreg Roach * Copyright (C) 2023 webtrees development team 6b9a4a6c6SGreg Roach * This program is free software: you can redistribute it and/or modify 7b9a4a6c6SGreg Roach * it under the terms of the GNU General Public License as published by 8b9a4a6c6SGreg Roach * the Free Software Foundation, either version 3 of the License, or 9b9a4a6c6SGreg Roach * (at your option) any later version. 10b9a4a6c6SGreg Roach * This program is distributed in the hope that it will be useful, 11b9a4a6c6SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12b9a4a6c6SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13b9a4a6c6SGreg Roach * GNU General Public License for more details. 14b9a4a6c6SGreg Roach * You should have received a copy of the GNU General Public License 15b9a4a6c6SGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>. 16b9a4a6c6SGreg Roach */ 17b9a4a6c6SGreg Roach 18b9a4a6c6SGreg Roachdeclare(strict_types=1); 19b9a4a6c6SGreg Roach 20b9a4a6c6SGreg Roachnamespace Fisharebest\Webtrees\Cli\Commands; 21b9a4a6c6SGreg Roach 22b9a4a6c6SGreg Roachuse Fisharebest\Webtrees\Contracts\UserInterface; 23b9a4a6c6SGreg Roachuse Fisharebest\Webtrees\DB; 24b9a4a6c6SGreg Roachuse Fisharebest\Webtrees\Services\UserService; 25b9a4a6c6SGreg Roachuse Symfony\Component\Console\Command\Command; 26b9a4a6c6SGreg Roachuse Symfony\Component\Console\Input\InputInterface; 27*bb87bb9bSGreg Roachuse Symfony\Component\Console\Input\InputOption; 28b9a4a6c6SGreg Roachuse Symfony\Component\Console\Output\OutputInterface; 29b9a4a6c6SGreg Roachuse Symfony\Component\Console\Style\SymfonyStyle; 30b9a4a6c6SGreg Roach 31b9a4a6c6SGreg Roachuse function bin2hex; 32b9a4a6c6SGreg Roachuse function random_bytes; 33b9a4a6c6SGreg Roach 34b9a4a6c6SGreg Roachclass UserCreate extends Command 35b9a4a6c6SGreg Roach{ 36b9a4a6c6SGreg Roach public function __construct(private readonly UserService $user_service) 37b9a4a6c6SGreg Roach { 38b9a4a6c6SGreg Roach parent::__construct(); 39b9a4a6c6SGreg Roach } 40b9a4a6c6SGreg Roach 41b9a4a6c6SGreg Roach protected function configure(): void 42b9a4a6c6SGreg Roach { 43b9a4a6c6SGreg Roach $this 44b9a4a6c6SGreg Roach ->setName(name: 'user-create') 45*bb87bb9bSGreg Roach ->setDescription(description: 'Create a new user') 46*bb87bb9bSGreg Roach ->addOption(name: 'username', shortcut: null, mode: InputOption::VALUE_REQUIRED, description: 'The username of the new user') 47*bb87bb9bSGreg Roach ->addOption(name: 'realname', shortcut: null, mode: InputOption::VALUE_REQUIRED, description: 'The real name of the new user') 48*bb87bb9bSGreg Roach ->addOption(name: 'email', shortcut: null, mode: InputOption::VALUE_REQUIRED, description: 'The email of the new user') 49*bb87bb9bSGreg Roach ->addOption(name: 'password', shortcut: null, mode: InputOption::VALUE_REQUIRED, description: 'The password of the new user') 50*bb87bb9bSGreg Roach ->addOption(name: 'timezone', shortcut: null, mode: InputOption::VALUE_REQUIRED, description: 'Set the timezone', default: 'UTC') 51*bb87bb9bSGreg Roach ->addOption(name: 'language', shortcut: null, mode: InputOption::VALUE_REQUIRED, description: 'Set the language', default: 'en-US') 52*bb87bb9bSGreg Roach ->addOption(name: 'admin', shortcut: null, mode: InputOption::VALUE_NONE, description: 'Make the new user an administrator'); 53b9a4a6c6SGreg Roach } 54b9a4a6c6SGreg Roach 55b9a4a6c6SGreg Roach protected function execute(InputInterface $input, OutputInterface $output): int 56b9a4a6c6SGreg Roach { 57b9a4a6c6SGreg Roach $io = new SymfonyStyle(input: $input, output: $output); 58b9a4a6c6SGreg Roach 59b9a4a6c6SGreg Roach $username = $input->getOption(name: 'username'); 60b9a4a6c6SGreg Roach $realname = $input->getOption(name: 'realname'); 61b9a4a6c6SGreg Roach $email = $input->getOption(name: 'email'); 62b9a4a6c6SGreg Roach $password = $input->getOption(name: 'password'); 63b9a4a6c6SGreg Roach $admin = (bool) $input->getOption(name: 'admin'); 64*bb87bb9bSGreg Roach $timezone = $input->getOption(name: 'timezone'); 65*bb87bb9bSGreg Roach $language = $input->getOption(name: 'language'); 66b9a4a6c6SGreg Roach 67*bb87bb9bSGreg Roach $errors = false; 68b9a4a6c6SGreg Roach 69b9a4a6c6SGreg Roach if ($username === null) { 70b9a4a6c6SGreg Roach $io->error(message: 'Missing required option: --username'); 71*bb87bb9bSGreg Roach $errors = true; 72b9a4a6c6SGreg Roach } 73b9a4a6c6SGreg Roach 74b9a4a6c6SGreg Roach if ($realname === null) { 75b9a4a6c6SGreg Roach $io->error(message: 'Missing required option: --realname'); 76*bb87bb9bSGreg Roach $errors = true; 77b9a4a6c6SGreg Roach } 78b9a4a6c6SGreg Roach 79b9a4a6c6SGreg Roach if ($email === null) { 80b9a4a6c6SGreg Roach $io->error(message: 'Missing required option: --email'); 81*bb87bb9bSGreg Roach $errors = true; 82b9a4a6c6SGreg Roach } 83b9a4a6c6SGreg Roach 84*bb87bb9bSGreg Roach if ($timezone === null) { 85*bb87bb9bSGreg Roach $io->error(message: 'Missing required option: --timezone'); 86*bb87bb9bSGreg Roach $errors = true; 87*bb87bb9bSGreg Roach } 88*bb87bb9bSGreg Roach 89*bb87bb9bSGreg Roach if ($errors) { 90*bb87bb9bSGreg Roach return Command::INVALID; 91b9a4a6c6SGreg Roach } 92b9a4a6c6SGreg Roach 93b9a4a6c6SGreg Roach $user = $this->user_service->findByUserName(user_name: $username); 94b9a4a6c6SGreg Roach 95b9a4a6c6SGreg Roach if ($user !== null) { 96b9a4a6c6SGreg Roach $io->error(message: 'A user with the username "' . $username . '" already exists.'); 97b9a4a6c6SGreg Roach 98b9a4a6c6SGreg Roach return Command::FAILURE; 99b9a4a6c6SGreg Roach } 100b9a4a6c6SGreg Roach 101b9a4a6c6SGreg Roach $user = $this->user_service->findByEmail(email: $email); 102b9a4a6c6SGreg Roach 103b9a4a6c6SGreg Roach if ($user !== null) { 104b9a4a6c6SGreg Roach $io->error(message: 'A user with the email "' . $email . '" already exists'); 105b9a4a6c6SGreg Roach 106b9a4a6c6SGreg Roach return Command::FAILURE; 107b9a4a6c6SGreg Roach } 108b9a4a6c6SGreg Roach 109b9a4a6c6SGreg Roach if ($password === null) { 110b9a4a6c6SGreg Roach $password = bin2hex(string: random_bytes(length: 8)); 111b9a4a6c6SGreg Roach $io->info(message: 'Generated password: ' . $password); 112b9a4a6c6SGreg Roach } 113b9a4a6c6SGreg Roach 114b9a4a6c6SGreg Roach $user = $this->user_service->create(user_name: $username, real_name:$realname, email: $email, password: $password); 115*bb87bb9bSGreg Roach $user->setPreference(setting_name: UserInterface::PREF_TIME_ZONE, setting_value: $timezone); 116*bb87bb9bSGreg Roach $user->setPreference(setting_name: UserInterface::PREF_LANGUAGE, setting_value: $language); 117b9a4a6c6SGreg Roach $user->setPreference(setting_name: UserInterface::PREF_IS_ACCOUNT_APPROVED, setting_value: '1'); 118b9a4a6c6SGreg Roach $user->setPreference(setting_name: UserInterface::PREF_IS_EMAIL_VERIFIED, setting_value: '1'); 119*bb87bb9bSGreg Roach $user->setPreference(setting_name: UserInterface::PREF_CONTACT_METHOD, setting_value: 'messaging'); 120b9a4a6c6SGreg Roach $io->success('User ' . $user->id() . ' created.'); 121b9a4a6c6SGreg Roach 122b9a4a6c6SGreg Roach if ($admin) { 123b9a4a6c6SGreg Roach $user->setPreference(setting_name: UserInterface::PREF_IS_ADMINISTRATOR, setting_value: '1'); 124b9a4a6c6SGreg Roach $io->success(message: 'User granted administrator role.'); 125b9a4a6c6SGreg Roach } 126b9a4a6c6SGreg Roach 127b9a4a6c6SGreg Roach DB::exec(sql: 'COMMIT'); 128b9a4a6c6SGreg Roach 129b9a4a6c6SGreg Roach return Command::SUCCESS; 130b9a4a6c6SGreg Roach } 131b9a4a6c6SGreg Roach} 132