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