Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
70.07% |
103 / 147 |
|
33.33% |
3 / 9 |
CRAP | |
0.00% |
0 / 1 |
add | |
70.07% |
103 / 147 |
|
33.33% |
3 / 9 |
46.55 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
configure | |
100.00% |
29 / 29 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
87.50% |
21 / 24 |
|
0.00% |
0 / 1 |
5.05 | |||
interact | |
76.92% |
20 / 26 |
|
0.00% |
0 / 1 |
6.44 | |||
validate_user_data | |
93.33% |
14 / 15 |
|
0.00% |
0 / 1 |
3.00 | |||
get_group_id | |
90.00% |
9 / 10 |
|
0.00% |
0 / 1 |
3.01 | |||
send_activation_email | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
20 | |||
get_activation_key | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
ask_user | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | /** |
3 | * |
4 | * This file is part of the phpBB Forum Software package. |
5 | * |
6 | * @copyright (c) phpBB Limited <https://www.phpbb.com> |
7 | * @license GNU General Public License, version 2 (GPL-2.0) |
8 | * |
9 | * For full copyright and license information, please see |
10 | * the docs/CREDITS.txt file. |
11 | * |
12 | */ |
13 | |
14 | namespace phpbb\console\command\user; |
15 | |
16 | use phpbb\config\config; |
17 | use phpbb\console\command\command; |
18 | use phpbb\db\driver\driver_interface; |
19 | use phpbb\exception\runtime_exception; |
20 | use phpbb\language\language; |
21 | use phpbb\messenger\method\email; |
22 | use phpbb\passwords\manager; |
23 | use phpbb\user; |
24 | use Symfony\Component\Console\Command\Command as symfony_command; |
25 | use Symfony\Component\Console\Helper\QuestionHelper; |
26 | use Symfony\Component\Console\Input\InputInterface; |
27 | use Symfony\Component\Console\Input\InputOption; |
28 | use Symfony\Component\Console\Output\OutputInterface; |
29 | use Symfony\Component\Console\Question\Question; |
30 | use Symfony\Component\Console\Style\SymfonyStyle; |
31 | |
32 | class add extends command |
33 | { |
34 | /** @var array Array of interactively acquired options */ |
35 | protected $data; |
36 | |
37 | /** @var driver_interface */ |
38 | protected $db; |
39 | |
40 | /** @var config */ |
41 | protected $config; |
42 | |
43 | /** @var email */ |
44 | protected $email_method; |
45 | |
46 | /** @var language */ |
47 | protected $language; |
48 | |
49 | /** @var manager */ |
50 | protected $password_manager; |
51 | |
52 | /** |
53 | * phpBB root path |
54 | * |
55 | * @var string |
56 | */ |
57 | protected $phpbb_root_path; |
58 | |
59 | /** |
60 | * PHP extension. |
61 | * |
62 | * @var string |
63 | */ |
64 | protected $php_ext; |
65 | |
66 | /** |
67 | * Construct method |
68 | * |
69 | * @param user $user |
70 | * @param driver_interface $db |
71 | * @param config $config |
72 | * @param language $language |
73 | * @param service_collection $messenger |
74 | * @param manager $password_manager |
75 | * @param string $phpbb_root_path |
76 | * @param string $php_ext |
77 | */ |
78 | public function __construct(user $user, driver_interface $db, config $config, language $language, email $email_method, manager $password_manager, $phpbb_root_path, $php_ext) |
79 | { |
80 | $this->config = $config; |
81 | $this->db = $db; |
82 | $this->email_method = $email_method; |
83 | $this->language = $language; |
84 | $this->password_manager = $password_manager; |
85 | $this->phpbb_root_path = $phpbb_root_path; |
86 | $this->php_ext = $php_ext; |
87 | |
88 | $this->language->add_lang('ucp'); |
89 | parent::__construct($user); |
90 | } |
91 | |
92 | /** |
93 | * Sets the command name and description |
94 | * |
95 | * @return void |
96 | */ |
97 | protected function configure() |
98 | { |
99 | $this |
100 | ->setName('user:add') |
101 | ->setDescription($this->language->lang('CLI_DESCRIPTION_USER_ADD')) |
102 | ->setHelp($this->language->lang('CLI_HELP_USER_ADD')) |
103 | ->addOption( |
104 | 'username', |
105 | 'U', |
106 | InputOption::VALUE_REQUIRED, |
107 | $this->language->lang('CLI_DESCRIPTION_USER_ADD_OPTION_USERNAME') |
108 | ) |
109 | ->addOption( |
110 | 'password', |
111 | 'P', |
112 | InputOption::VALUE_REQUIRED, |
113 | $this->language->lang('CLI_DESCRIPTION_USER_ADD_OPTION_PASSWORD') |
114 | ) |
115 | ->addOption( |
116 | 'email', |
117 | 'E', |
118 | InputOption::VALUE_REQUIRED, |
119 | $this->language->lang('CLI_DESCRIPTION_USER_ADD_OPTION_EMAIL') |
120 | ) |
121 | ->addOption( |
122 | 'send-email', |
123 | null, |
124 | InputOption::VALUE_NONE, |
125 | $this->language->lang('CLI_DESCRIPTION_USER_ADD_OPTION_NOTIFY') |
126 | ) |
127 | ; |
128 | } |
129 | |
130 | /** |
131 | * Executes the command user:add |
132 | * |
133 | * Adds a new user to the database. If options are not provided, it will ask for the username, password and email. |
134 | * User is added to the registered user group. Language and timezone default to $config settings. |
135 | * |
136 | * @param InputInterface $input The input stream used to get the options |
137 | * @param OutputInterface $output The output stream, used to print messages |
138 | * |
139 | * @return int 0 if all is well, 1 if any errors occurred |
140 | */ |
141 | protected function execute(InputInterface $input, OutputInterface $output) |
142 | { |
143 | $io = new SymfonyStyle($input, $output); |
144 | |
145 | try |
146 | { |
147 | $this->validate_user_data(); |
148 | $group_id = $this->get_group_id(); |
149 | } |
150 | catch (runtime_exception $e) |
151 | { |
152 | $io->error($e->getMessage()); |
153 | return symfony_command::FAILURE; |
154 | } |
155 | |
156 | $user_row = array( |
157 | 'username' => $this->data['username'], |
158 | 'user_password' => $this->password_manager->hash($this->data['new_password']), |
159 | 'user_email' => $this->data['email'], |
160 | 'group_id' => $group_id, |
161 | 'user_timezone' => $this->config['board_timezone'], |
162 | 'user_lang' => $this->config['default_lang'], |
163 | 'user_type' => USER_NORMAL, |
164 | 'user_regdate' => time(), |
165 | ); |
166 | |
167 | $user_id = (int) user_add($user_row); |
168 | |
169 | if (!$user_id) |
170 | { |
171 | $io->error($this->language->lang('AUTH_NO_PROFILE_CREATED')); |
172 | return symfony_command::FAILURE; |
173 | } |
174 | |
175 | if ($input->getOption('send-email') && $this->config['email_enable']) |
176 | { |
177 | $this->send_activation_email($user_id); |
178 | } |
179 | |
180 | $io->success($this->language->lang('CLI_USER_ADD_SUCCESS', $this->data['username'])); |
181 | |
182 | return symfony_command::SUCCESS; |
183 | } |
184 | |
185 | /** |
186 | * Interacts with the user. |
187 | * |
188 | * @param InputInterface $input An InputInterface instance |
189 | * @param OutputInterface $output An OutputInterface instance |
190 | */ |
191 | protected function interact(InputInterface $input, OutputInterface $output) |
192 | { |
193 | $helper = $this->getHelper('question'); |
194 | if (!$helper instanceof QuestionHelper) |
195 | { |
196 | return; |
197 | } |
198 | |
199 | $this->data = array( |
200 | 'username' => $input->getOption('username'), |
201 | 'new_password' => $input->getOption('password'), |
202 | 'email' => $input->getOption('email'), |
203 | ); |
204 | |
205 | if (!$this->data['username']) |
206 | { |
207 | $question = new Question($this->ask_user('USERNAME')); |
208 | $this->data['username'] = $helper->ask($input, $output, $question); |
209 | } |
210 | |
211 | if (!$this->data['new_password']) |
212 | { |
213 | $question = new Question($this->ask_user('PASSWORD')); |
214 | $question->setValidator(function ($value) use ($helper, $input, $output) { |
215 | $question = new Question($this->ask_user('CONFIRM_PASSWORD')); |
216 | $question->setHidden(true); |
217 | if ($helper->ask($input, $output, $question) != $value) |
218 | { |
219 | throw new runtime_exception($this->language->lang('NEW_PASSWORD_ERROR')); |
220 | } |
221 | return $value; |
222 | }); |
223 | $question->setHidden(true); |
224 | $question->setMaxAttempts(5); |
225 | |
226 | $this->data['new_password'] = $helper->ask($input, $output, $question); |
227 | } |
228 | |
229 | if (!$this->data['email']) |
230 | { |
231 | $question = new Question($this->ask_user('EMAIL_ADDRESS')); |
232 | $this->data['email'] = $helper->ask($input, $output, $question); |
233 | } |
234 | } |
235 | |
236 | /** |
237 | * Validate the submitted user data |
238 | * |
239 | * @throws runtime_exception if any data fails validation |
240 | * @return void |
241 | */ |
242 | protected function validate_user_data() |
243 | { |
244 | if (!function_exists('validate_data')) |
245 | { |
246 | require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); |
247 | } |
248 | |
249 | $error = validate_data($this->data, array( |
250 | 'username' => array( |
251 | array('string', false, $this->config['min_name_chars'], $this->config['max_name_chars']), |
252 | array('username', '')), |
253 | 'new_password' => array( |
254 | array('string', false, $this->config['min_pass_chars'], 0), |
255 | array('password')), |
256 | 'email' => array( |
257 | array('string', false, 6, 60), |
258 | array('user_email')), |
259 | )); |
260 | |
261 | if ($error) |
262 | { |
263 | throw new runtime_exception(implode("\n", array_map(array($this->language, 'lang'), $error))); |
264 | } |
265 | } |
266 | |
267 | /** |
268 | * Get the group id |
269 | * |
270 | * Go and find in the database the group_id corresponding to 'REGISTERED' |
271 | * |
272 | * @throws runtime_exception if the group id does not exist in database. |
273 | * @return null |
274 | */ |
275 | protected function get_group_id() |
276 | { |
277 | $sql = 'SELECT group_id |
278 | FROM ' . GROUPS_TABLE . " |
279 | WHERE group_name = '" . $this->db->sql_escape('REGISTERED') . "' |
280 | AND group_type = " . GROUP_SPECIAL; |
281 | $result = $this->db->sql_query($sql); |
282 | $row = $this->db->sql_fetchrow($result); |
283 | $this->db->sql_freeresult($result); |
284 | |
285 | if (!$row || !$row['group_id']) |
286 | { |
287 | throw new runtime_exception($this->language->lang('NO_GROUP')); |
288 | } |
289 | |
290 | return $row['group_id']; |
291 | } |
292 | |
293 | /** |
294 | * Send account activation email |
295 | * |
296 | * @param int $user_id The new user's id |
297 | * @return void |
298 | */ |
299 | protected function send_activation_email($user_id) |
300 | { |
301 | switch ($this->config['require_activation']) |
302 | { |
303 | case USER_ACTIVATION_SELF: |
304 | $email_template = 'user_welcome_inactive'; |
305 | break; |
306 | case USER_ACTIVATION_ADMIN: |
307 | $email_template = 'admin_welcome_inactive'; |
308 | break; |
309 | default: |
310 | $email_template = 'user_welcome'; |
311 | break; |
312 | } |
313 | |
314 | $user_actkey = $this->get_activation_key($user_id); |
315 | |
316 | $this->email_method->set_use_queue(false); |
317 | $this->email_method->template($email_template, $this->user->lang_name); |
318 | $this->email_method->to($this->data['email'], $this->data['username']); |
319 | $this->email_method->anti_abuse_headers($this->config, $this->user); |
320 | $this->email_method->assign_vars([ |
321 | 'WELCOME_MSG' => html_entity_decode($this->language->lang('WELCOME_SUBJECT', $this->config['sitename']), ENT_COMPAT), |
322 | 'USERNAME' => html_entity_decode($this->data['username'], ENT_COMPAT), |
323 | 'PASSWORD' => html_entity_decode($this->data['new_password'], ENT_COMPAT), |
324 | 'U_ACTIVATE' => generate_board_url() . "/ucp.{$this->php_ext}?mode=activate&u=$user_id&k=$user_actkey", |
325 | ]); |
326 | $this->email_method->send(); |
327 | } |
328 | |
329 | /** |
330 | * Get user activation key |
331 | * |
332 | * @param int $user_id User ID |
333 | * |
334 | * @return string User activation key for user |
335 | */ |
336 | protected function get_activation_key(int $user_id): string |
337 | { |
338 | $user_actkey = ''; |
339 | |
340 | if ($this->config['require_activation'] == USER_ACTIVATION_SELF || $this->config['require_activation'] == USER_ACTIVATION_ADMIN) |
341 | { |
342 | $user_actkey = gen_rand_string(mt_rand(6, 10)); |
343 | |
344 | $sql_ary = [ |
345 | 'user_actkey' => $user_actkey, |
346 | 'user_actkey_expiration' => user::get_token_expiration(), |
347 | ]; |
348 | |
349 | $sql = 'UPDATE ' . USERS_TABLE . ' |
350 | SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' |
351 | WHERE user_id = ' . (int) $user_id; |
352 | $this->db->sql_query($sql); |
353 | } |
354 | |
355 | return $user_actkey; |
356 | } |
357 | |
358 | /** |
359 | * Helper to translate questions to the user |
360 | * |
361 | * @param string $key The language key |
362 | * @return string The language key translated with a colon and space appended |
363 | */ |
364 | protected function ask_user($key) |
365 | { |
366 | return $this->language->lang($key) . $this->language->lang('COLON') . ' '; |
367 | } |
368 | } |