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