1 // SuperTuxKart - a fun racing game with go-kart
2 // Copyright (C) 2014-2015 Joerg Henrichs
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 3
7 // of the License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18 #include "states_screens/online/register_screen.hpp"
19
20 #include "config/player_manager.hpp"
21 #include "config/user_config.hpp"
22 #include "audio/sfx_manager.hpp"
23 #include "guiengine/widgets/button_widget.hpp"
24 #include "guiengine/widgets/check_box_widget.hpp"
25 #include "guiengine/widgets/label_widget.hpp"
26 #include "guiengine/widgets/ribbon_widget.hpp"
27 #include "guiengine/widgets/text_box_widget.hpp"
28 #include "online/link_helper.hpp"
29 #include "online/request_manager.hpp"
30 #include "online/xml_request.hpp"
31 #include "states_screens/dialogs/registration_dialog.hpp"
32 #include "states_screens/dialogs/message_dialog.hpp"
33 #include "states_screens/main_menu_screen.hpp"
34 #include "states_screens/state_manager.hpp"
35 #include "states_screens/options/user_screen.hpp"
36 #include "utils/log.hpp"
37 #include "utils/translation.hpp"
38
39 using namespace GUIEngine;
40 using namespace Online;
41 using namespace irr;
42 using namespace core;
43
44 // -----------------------------------------------------------------------------
45
RegisterScreen()46 RegisterScreen::RegisterScreen() : Screen("online/register.stkgui")
47 {
48 m_existing_player = NULL;
49 m_account_mode = ACCOUNT_OFFLINE;
50 m_parent_screen = NULL;
51 } // RegisterScreen
52
53 // -----------------------------------------------------------------------------
init()54 void RegisterScreen::init()
55 {
56 getWidget<TextBoxWidget>("username")->setText(L"");
57 m_info_widget = getWidget<LabelWidget>("info");
58 assert(m_info_widget);
59 m_info_widget->setDefaultColor();
60 m_info_widget->setText(L"", false);
61 m_options_widget = getWidget<RibbonWidget>("options");
62 assert(m_options_widget);
63 m_password_widget = getWidget<TextBoxWidget>("password");
64 assert(m_password_widget);
65
66 RibbonWidget* ribbon = getWidget<RibbonWidget>("mode_tabs");
67 assert(ribbon);
68 if (UserConfigParams::m_internet_status !=
69 Online::RequestManager::IPERM_NOT_ALLOWED)
70 {
71 m_account_mode = ACCOUNT_NEW_ONLINE;
72 ribbon->select("tab_new_online", PLAYER_ID_GAME_MASTER);
73 }
74 else
75 {
76 m_account_mode = ACCOUNT_OFFLINE;
77 ribbon->select("tab_offline", PLAYER_ID_GAME_MASTER);
78 }
79
80 // Hide the tabs in case of a rename
81 ribbon->setVisible(m_existing_player == NULL);
82 Screen::init();
83
84 // If there is no player (i.e. first start of STK), try to pick
85 // a good default name
86 stringw username = "";
87 if(m_existing_player)
88 {
89 username = m_existing_player->getName();
90 }
91 else if (PlayerManager::get()->getNumPlayers() == 0)
92 {
93 #ifndef MOBILE_STK
94 // For mobile stk always use the name Player as in iOS the following
95 // getenv return "mobile" for some reason
96 #if defined(WIN32)
97 std::vector<wchar_t> env;
98 // An environment variable has a maximum size limit of 32,767 characters
99 env.resize(32767, 0);
100 DWORD length = GetEnvironmentVariable(L"USERNAME", env.data(), 32767);
101 if (length != 0)
102 username = env.data();
103 #else
104 if (getenv("USER") != NULL) // Linux, Macs
105 username = getenv("USER");
106 else if (getenv("LOGNAME") != NULL) // Linux, Macs
107 username = getenv("LOGNAME");
108 #endif
109 #endif
110 }
111
112 TextBoxWidget* local_username = getWidget<TextBoxWidget>("local_username");
113 local_username->setText(username);
114
115 m_password_widget->setPasswordBox(true, L'*');
116 getWidget<TextBoxWidget>("password_confirm")->setPasswordBox(true, L'*');
117
118 m_signup_request = nullptr;
119 m_info_message_shown = false;
120
121 onDialogClose();
122 makeEntryFieldsVisible();
123
124 // The behaviour of the screen is slightly different at startup, i.e.
125 // when it is the first screen: cancel will exit the game, and in
126 // this case no 'back' error should be shown.
127 bool has_player_profile = (PlayerManager::get()->getNumPlayers() > 0);
128 getWidget<IconButtonWidget>("back")->setVisible(has_player_profile);
129 getWidget<IconButtonWidget>("cancel")->setLabel(has_player_profile
130 ? _("Cancel")
131 : _("Exit game"));
132 } // init
133
134 // -----------------------------------------------------------------------------
setRename(PlayerProfile * player)135 void RegisterScreen::setRename(PlayerProfile *player)
136 {
137 m_existing_player = player;
138 } // setRename
139
140 // -----------------------------------------------------------------------------
141 /** Will be called first time STK is started, when the 'internet yes/no' dialog
142 * is closed. Adjust the state of the online checkbox depending on that
143 * answer.
144 */
onDialogClose()145 void RegisterScreen::onDialogClose()
146 {
147 bool online = UserConfigParams::m_internet_status
148 != Online::RequestManager::IPERM_NOT_ALLOWED;
149 m_account_mode = online ? ACCOUNT_NEW_ONLINE : ACCOUNT_OFFLINE;
150
151 RibbonWidget* ribbon = getWidget<RibbonWidget>("mode_tabs");
152 assert(ribbon);
153 if (m_account_mode == ACCOUNT_NEW_ONLINE)
154 {
155 ribbon->select("tab_new_online", PLAYER_ID_GAME_MASTER);
156 }
157 else
158 {
159 m_account_mode = ACCOUNT_OFFLINE;
160 ribbon->select("tab_offline", PLAYER_ID_GAME_MASTER);
161 }
162 makeEntryFieldsVisible();
163 } // onDialogClose
164
165 // -----------------------------------------------------------------------------
onFocusChanged(GUIEngine::Widget * previous,GUIEngine::Widget * focus,int playerID)166 void RegisterScreen::onFocusChanged(GUIEngine::Widget* previous,
167 GUIEngine::Widget* focus, int playerID)
168 {
169 TextBoxWidget *online_name = getWidget<TextBoxWidget>("username");
170 if (focus == online_name)
171 {
172 TextBoxWidget *local_name = getWidget<TextBoxWidget>("local_username");
173 if (online_name->getText() == "")
174 online_name->setText(local_name->getText());
175 }
176 } // onFocusChanged
177
178 // -----------------------------------------------------------------------------
179 /** Shows or hides the entry fields for online registration, depending on
180 * online mode.
181 * \param online True if an online account should be created.
182 */
makeEntryFieldsVisible()183 void RegisterScreen::makeEntryFieldsVisible()
184 {
185 // In case of a rename, hide all other fields.
186 if(m_existing_player)
187 {
188 m_info_widget->setVisible(false);
189 m_account_mode = ACCOUNT_OFFLINE;
190 }
191
192 bool online = m_account_mode != ACCOUNT_OFFLINE;
193 getWidget<TextBoxWidget>("username")->setVisible(online);
194 getWidget<LabelWidget >("label_username")->setVisible(online);
195 m_password_widget->setVisible(online);
196 getWidget<LabelWidget >("label_password")->setVisible(online);
197
198 bool new_account = online && (m_account_mode == ACCOUNT_NEW_ONLINE);
199 getWidget<TextBoxWidget>("password_confirm")->setVisible(new_account);
200 getWidget<LabelWidget >("label_password_confirm")->setVisible(new_account);
201 getWidget<TextBoxWidget>("email")->setVisible(new_account);
202 getWidget<TextBoxWidget>("email")->setTextBoxType(TBT_EMAIL);
203 getWidget<LabelWidget >("label_email")->setVisible(new_account);
204 if(getWidget<TextBoxWidget>("email_confirm"))
205 {
206 getWidget<TextBoxWidget>("email_confirm")->setVisible(new_account);
207 getWidget<LabelWidget >("label_email_confirm")->setVisible(new_account);
208 }
209
210 getWidget<ButtonWidget >("password_reset")->setVisible(LinkHelper::isSupported() && (online && !new_account));
211 } // makeEntryFieldsVisible
212
213 // -----------------------------------------------------------------------------
214 /** If necessary creates the local user.
215 * \param local_name Name of the local user.
216 */
handleLocalName(const stringw & local_name)217 void RegisterScreen::handleLocalName(const stringw &local_name)
218 {
219 if (local_name.size() == 0)
220 return;
221
222 // If a local player with that name does not exist, create one
223 if(!PlayerManager::get()->getPlayer(local_name))
224 {
225 PlayerProfile *player;
226 // If it's a rename, change the name of the player
227 if(m_existing_player && local_name.size()>0)
228 {
229 m_existing_player->setName(local_name);
230 player = m_existing_player;
231 }
232 else
233 {
234 player = PlayerManager::get()->addNewPlayer(local_name);
235 }
236 PlayerManager::get()->save();
237 if (player)
238 {
239 PlayerManager::get()->setCurrentPlayer(player);
240 }
241 else
242 {
243 m_info_widget->setErrorColor();
244 m_info_widget->setText(_("Could not create player '%s'.", local_name),
245 false);
246 }
247 }
248 else
249 {
250 m_info_widget->setErrorColor();
251 m_info_widget->setText(_("Could not create player '%s'.", local_name),
252 false);
253 }
254 } // handleLocalName
255
256 // -----------------------------------------------------------------------------
257 /** Handles the actual registration process. It does some tests on id, password
258 * and email address, then submits a corresponding request.
259 */
doRegister()260 void RegisterScreen::doRegister()
261 {
262 stringw local_name = getWidget<TextBoxWidget>("local_username")
263 ->getText().trim();
264
265 if (local_name.empty())
266 {
267 m_info_widget->setErrorColor();
268 m_info_widget->setText(_("User name cannot be empty."), false);
269 return;
270 }
271
272 handleLocalName(local_name);
273
274 // If no online account is requested, don't register
275 if(m_account_mode!=ACCOUNT_NEW_ONLINE|| m_existing_player)
276 {
277 bool online = m_account_mode == ACCOUNT_EXISTING_ONLINE;
278 core::stringw password = online ? m_password_widget->getText() : "";
279 core::stringw online_name =
280 online ? getWidget<TextBoxWidget>("username")->getText().trim()
281 : "";
282 m_parent_screen->setNewAccountData(online, /*auto login*/true,
283 online_name, password);
284 m_existing_player = NULL;
285 StateManager::get()->popMenu();
286 return;
287 }
288
289 stringw username = getWidget<TextBoxWidget>("username")->getText().trim();
290 stringw password = m_password_widget->getText().trim();
291 stringw password_confirm = getWidget<TextBoxWidget>("password_confirm")
292 ->getText().trim();
293 stringw email = getWidget<TextBoxWidget>("email")->getText().trim();
294
295 // If there is an email_confirm field, use it and check if the email
296 // address is correct. If there is no such field, set the confirm email
297 // address to email address (so the test below will be passed).
298 stringw email_confirm = getWidget<TextBoxWidget>("email_confirm")
299 ? getWidget<TextBoxWidget>("email_confirm")->getText()
300 : getWidget<TextBoxWidget>("email")->getText();
301 email_confirm.trim();
302 m_info_widget->setErrorColor();
303
304 if (password != password_confirm)
305 {
306 m_info_widget->setText(_("Passwords don't match!"), false);
307 }
308 else if (email != email_confirm)
309 {
310 m_info_widget->setText(_("Emails don't match!"), false);
311 }
312 else if (username.size() < 3 || username.size() > 30)
313 {
314 m_info_widget->setText(_("Online username has to be between 3 and 30 characters long!"), false);
315 }
316 else if (username[0]>='0' && username[0]<='9')
317 {
318 m_info_widget->setText(_("Online username must not start with a number!"), false);
319 }
320 else if (password.size() < 8 || password.size() > 30)
321 {
322 m_info_widget->setText(_("Password has to be between 8 and 30 characters long!"), false);
323 }
324 else if (email.size() < 5 || email.size() > 254)
325 {
326 m_info_widget->setText(_("Email has to be between 5 and 254 characters long!"), false);
327 }
328 else if ( email.find(L"@")== -1 || email.find(L".")== -1 ||
329 (email.findLast(L'.') - email.findLast(L'@') <= 2 ) ||
330 email.findLast(L'@')==0 )
331 {
332 m_info_widget->setText(_("Email is invalid!"), false);
333 }
334 else
335 {
336 m_info_widget->setDefaultColor();
337 new RegistrationDialog();
338 if (local_name.size() > 0)
339 {
340 PlayerProfile *player = PlayerManager::get()->getPlayer(local_name);
341 if (player)
342 {
343 core::stringw online_name = getWidget<TextBoxWidget>("username")->getText().trim();
344 m_parent_screen->setNewAccountData(/*online*/true,
345 /*auto_login*/false,
346 username, password);
347
348 player->setLastOnlineName(username);
349 player->setWasOnlineLastTime(true);
350 }
351 }
352 return;
353 }
354
355 SFXManager::get()->quickSound( "anvil" );
356 } // doRegister
357
358 // -----------------------------------------------------------------------------
359 /** Called from the registration info dialog when 'accept' is clicked.
360 */
acceptTerms()361 void RegisterScreen::acceptTerms()
362 {
363 m_options_widget->setActive(false);
364
365 core::stringw username = getWidget<TextBoxWidget>("username")->getText().trim();
366 core::stringw password = m_password_widget->getText().trim();
367 core::stringw password_confirm= getWidget<TextBoxWidget>("password_confirm")->getText().trim();
368 core::stringw email = getWidget<TextBoxWidget>("email")->getText().trim();
369
370 m_signup_request = std::make_shared<XMLRequest>();
371 m_signup_request->setApiURL(API::USER_PATH, "register");
372 m_signup_request->addParameter("username", username );
373 m_signup_request->addParameter("password", password );
374 m_signup_request->addParameter("password_confirm", password_confirm);
375 m_signup_request->addParameter("email", email );
376 m_signup_request->addParameter("terms", "on" );
377 m_signup_request->queue();
378 } // acceptTerms
379
380 // -----------------------------------------------------------------------------
381
onUpdate(float dt)382 void RegisterScreen::onUpdate(float dt)
383 {
384 if(m_signup_request)
385 {
386 if(!m_options_widget->isActivated())
387 m_info_widget->setText(StringUtils::loadingDots(_("Validating info")),
388 false);
389
390 if(m_signup_request->isDone())
391 {
392 if(m_signup_request->isSuccess())
393 {
394 new MessageDialog(
395 _("You will receive an email with further instructions "
396 "regarding account activation. Please be patient and be "
397 "sure to check your spam folder."),
398 MessageDialog::MESSAGE_DIALOG_OK, NULL, false);
399 // Set the flag that the message was shown, which will triger
400 // a pop of this menu and so a return to the main menu
401 m_info_message_shown = true;
402 }
403 else
404 {
405 // Error signing up, display error message
406 m_info_widget->setErrorColor();
407 m_info_widget->setText(m_signup_request->getInfo(), false);
408 }
409 m_signup_request = nullptr;
410 m_options_widget->setActive(true);
411 }
412 }
413 else if(m_info_message_shown && !ModalDialog::isADialogActive())
414 {
415 // Once the info message was shown (signup was successful), but the
416 // message has been gone (user clicked on OK), go back to main menu
417 StateManager::get()->popMenu();
418 }
419 } // onUpdate
420
421 // -----------------------------------------------------------------------------
422
eventCallback(Widget * widget,const std::string & name,const int playerID)423 void RegisterScreen::eventCallback(Widget* widget, const std::string& name,
424 const int playerID)
425 {
426 if (name == "mode_tabs")
427 {
428 RibbonWidget *ribbon = static_cast<RibbonWidget*>(widget);
429 std::string selection = ribbon->getSelectionIDString(PLAYER_ID_GAME_MASTER);
430 if ( (selection == "tab_new_online" || selection == "tab_existing_online")
431 && (UserConfigParams::m_internet_status == Online::RequestManager::IPERM_NOT_ALLOWED) )
432 {
433 m_info_widget->setErrorColor();
434 m_info_widget->setText(_("Internet access is disabled, please enable it in the options"), false);
435 return;
436 }
437 if (selection == "tab_new_online")
438 m_account_mode = ACCOUNT_NEW_ONLINE;
439 else if (selection == "tab_existing_online")
440 m_account_mode = ACCOUNT_EXISTING_ONLINE;
441 else if (selection == "tab_offline")
442 m_account_mode = ACCOUNT_OFFLINE;
443
444 makeEntryFieldsVisible();
445 }
446 else if (name=="options")
447 {
448 const std::string button = m_options_widget
449 ->getSelectionIDString(PLAYER_ID_GAME_MASTER);
450 if(button=="next")
451 {
452 doRegister();
453 }
454 else if(button=="cancel")
455 {
456 // We poop this menu, onEscapePress will handle the special case
457 // of e.g. a fresh start of stk that is aborted.
458 StateManager::get()->popMenu();
459 onEscapePressed();
460 }
461 }
462 else if (name == "password_reset")
463 {
464 // Open password reset page
465 Online::LinkHelper::openURL(stk_config->m_password_reset_url);
466 }
467 else if (name == "back")
468 {
469 m_existing_player = NULL;
470 StateManager::get()->escapePressed();
471 }
472
473 } // eventCallback
474
475 // -----------------------------------------------------------------------------
onEscapePressed()476 bool RegisterScreen::onEscapePressed()
477 {
478 m_existing_player = NULL;
479 if (PlayerManager::get()->getNumPlayers() == 0)
480 {
481 // Must be first time start, and player cancelled player creation
482 // so quit stk. At this stage there are two menus on the stack:
483 // 1) The UserScreen, 2) RegisterStreen
484 // Popping them both will trigger STK to close.
485 StateManager::get()->popMenu();
486 return true;
487 }
488 return true;
489 } // onEscapePressed
490
491