| 1 | <?php |
|---|
| 2 | require_once(WCF_DIR.'lib/data/user/UserEditor.class.php'); |
|---|
| 3 | require_once(WCF_DIR.'lib/util/UserUtil.class.php'); |
|---|
| 4 | |
|---|
| 5 | /** |
|---|
| 6 | * embeds the openid system into the wcf |
|---|
| 7 | * registers include pathes and cares for all dependencies |
|---|
| 8 | * |
|---|
| 9 | * @author Torben Brodt |
|---|
| 10 | * @copyright 2010 easy-coding.de |
|---|
| 11 | * @license GNU General Public License <http://opensource.org/licenses/gpl-3.0.html> |
|---|
| 12 | * @package de.easy-coding.wcf.openid |
|---|
| 13 | */ |
|---|
| 14 | class OpenID { |
|---|
| 15 | |
|---|
| 16 | /** |
|---|
| 17 | * sets include pathes |
|---|
| 18 | * |
|---|
| 19 | */ |
|---|
| 20 | public function __construct() { |
|---|
| 21 | |
|---|
| 22 | // fix for windows and openbasedire restrictions |
|---|
| 23 | define('Auth_OpenID_RAND_SOURCE', null); |
|---|
| 24 | |
|---|
| 25 | $path_extra = dirname(__FILE__); |
|---|
| 26 | $path = ini_get('include_path'); |
|---|
| 27 | $path = $path_extra . PATH_SEPARATOR . $path; |
|---|
| 28 | ini_set('include_path', $path); |
|---|
| 29 | |
|---|
| 30 | /** |
|---|
| 31 | * session wrapper |
|---|
| 32 | */ |
|---|
| 33 | require_once "Auth/Yadis/Manager.php"; |
|---|
| 34 | |
|---|
| 35 | /** |
|---|
| 36 | * session wrapper |
|---|
| 37 | */ |
|---|
| 38 | require_once "OpenIDSession.class.php"; |
|---|
| 39 | |
|---|
| 40 | /** |
|---|
| 41 | * Require the OpenID consumer code. |
|---|
| 42 | */ |
|---|
| 43 | require_once "Auth/OpenID/Consumer.php"; |
|---|
| 44 | |
|---|
| 45 | /** |
|---|
| 46 | * Require the "file store" module, which we'll need to store |
|---|
| 47 | * OpenID information. |
|---|
| 48 | */ |
|---|
| 49 | require_once "Auth/OpenID/FileStore.php"; |
|---|
| 50 | |
|---|
| 51 | /** |
|---|
| 52 | * Require the Simple Registration extension API. |
|---|
| 53 | */ |
|---|
| 54 | require_once "Auth/OpenID/SReg.php"; |
|---|
| 55 | |
|---|
| 56 | /** |
|---|
| 57 | * Require the PAPE extension module. |
|---|
| 58 | */ |
|---|
| 59 | require_once "Auth/OpenID/PAPE.php"; |
|---|
| 60 | |
|---|
| 61 | /** |
|---|
| 62 | * Attribution Exchange |
|---|
| 63 | */ |
|---|
| 64 | require_once "Auth/OpenID/AX.php"; |
|---|
| 65 | } |
|---|
| 66 | |
|---|
| 67 | /** |
|---|
| 68 | * returns file store (tempoary store object) |
|---|
| 69 | */ |
|---|
| 70 | protected function getStore() { |
|---|
| 71 | $store_path = FileUtil::getTemporaryFilename('openid_'); |
|---|
| 72 | $store_path = TMP_DIR."/_openid".WCF_N; |
|---|
| 73 | return new Auth_OpenID_FileStore($store_path); |
|---|
| 74 | } |
|---|
| 75 | |
|---|
| 76 | /** |
|---|
| 77 | * get authenticated user |
|---|
| 78 | */ |
|---|
| 79 | protected function getConsumer() { |
|---|
| 80 | $session = new OpenIDSession(); |
|---|
| 81 | |
|---|
| 82 | /** |
|---|
| 83 | * Create a consumer object using the store object created |
|---|
| 84 | * earlier. |
|---|
| 85 | */ |
|---|
| 86 | $store = $this->getStore(); |
|---|
| 87 | $consumer = new Auth_OpenID_Consumer($store, $session); |
|---|
| 88 | return $consumer; |
|---|
| 89 | } |
|---|
| 90 | |
|---|
| 91 | /** |
|---|
| 92 | * gets root url |
|---|
| 93 | * |
|---|
| 94 | * @return string |
|---|
| 95 | */ |
|---|
| 96 | public static function getTrustRoot() { |
|---|
| 97 | return PAGE_URL; |
|---|
| 98 | } |
|---|
| 99 | |
|---|
| 100 | /** |
|---|
| 101 | * call api and try authentication |
|---|
| 102 | */ |
|---|
| 103 | public function tryRegistration($openid, $returnTo, $policy_uris = array()) { |
|---|
| 104 | $consumer = $this->getConsumer(); |
|---|
| 105 | |
|---|
| 106 | // Begin the OpenID authentication process. |
|---|
| 107 | $auth_request = $consumer->begin($openid); |
|---|
| 108 | |
|---|
| 109 | // No auth request means we can't begin OpenID. |
|---|
| 110 | if (!$auth_request) { |
|---|
| 111 | throw new Exception("Authentication error; not a valid OpenID."); |
|---|
| 112 | } |
|---|
| 113 | |
|---|
| 114 | $sreg_request = Auth_OpenID_SRegRequest::build( |
|---|
| 115 | // Required |
|---|
| 116 | array('nickname'), |
|---|
| 117 | // Optional |
|---|
| 118 | array('email') |
|---|
| 119 | ); |
|---|
| 120 | |
|---|
| 121 | if ($sreg_request) { |
|---|
| 122 | $auth_request->addExtension($sreg_request); |
|---|
| 123 | } |
|---|
| 124 | |
|---|
| 125 | $pape_request = new Auth_OpenID_PAPE_Request($policy_uris); |
|---|
| 126 | if ($pape_request) { |
|---|
| 127 | $auth_request->addExtension($pape_request); |
|---|
| 128 | } |
|---|
| 129 | |
|---|
| 130 | // Redirect the user to the OpenID server for authentication. |
|---|
| 131 | // Store the token for this authentication so we can verify the |
|---|
| 132 | // response. |
|---|
| 133 | |
|---|
| 134 | // For OpenID 1, send a redirect. For OpenID 2, use a Javascript |
|---|
| 135 | // form to send a POST request to the server. |
|---|
| 136 | if ($auth_request->shouldSendRedirect()) { |
|---|
| 137 | $redirect_url = $auth_request->redirectURL(self::getTrustRoot(), $returnTo); |
|---|
| 138 | |
|---|
| 139 | // If the redirect URL can't be built, display an error |
|---|
| 140 | // message. |
|---|
| 141 | if (Auth_OpenID::isFailure($redirect_url)) { |
|---|
| 142 | throw new Exception("Could not redirect to server: " . $redirect_url->message); |
|---|
| 143 | } else { |
|---|
| 144 | // Send redirect. |
|---|
| 145 | header("Location: ".$redirect_url); |
|---|
| 146 | exit; |
|---|
| 147 | } |
|---|
| 148 | } else { |
|---|
| 149 | // Generate form markup and render it. |
|---|
| 150 | $form_id = 'openid_message'; |
|---|
| 151 | $form_html = $auth_request->htmlMarkup(self::getTrustRoot(), $returnTo, false, array('id' => $form_id)); |
|---|
| 152 | |
|---|
| 153 | // Display an error if the form markup couldn't be generated; |
|---|
| 154 | // otherwise, render the HTML. |
|---|
| 155 | if (Auth_OpenID::isFailure($form_html)) { |
|---|
| 156 | throw new Exception("Could not redirect to server: " . $form_html->message); |
|---|
| 157 | } else { |
|---|
| 158 | |
|---|
| 159 | // used by openid 2, formular and redirect are printed out |
|---|
| 160 | echo $form_html; |
|---|
| 161 | exit; |
|---|
| 162 | } |
|---|
| 163 | } |
|---|
| 164 | } |
|---|
| 165 | |
|---|
| 166 | /** |
|---|
| 167 | * got answer, save user |
|---|
| 168 | */ |
|---|
| 169 | public function finishRegistration($return_to) { |
|---|
| 170 | $consumer = $this->getConsumer(); |
|---|
| 171 | |
|---|
| 172 | // Complete the authentication process using the server's |
|---|
| 173 | // response. |
|---|
| 174 | $response = $consumer->complete($return_to); |
|---|
| 175 | |
|---|
| 176 | // Check the response status. |
|---|
| 177 | if ($response->status == Auth_OpenID_CANCEL) { |
|---|
| 178 | // This means the authentication was cancelled. |
|---|
| 179 | $msg = 'Verification cancelled.'; |
|---|
| 180 | } else if ($response->status == Auth_OpenID_FAILURE) { |
|---|
| 181 | // Authentication failed; display the error message. |
|---|
| 182 | $msg = "OpenID authentication failed: " . $response->message; |
|---|
| 183 | } else if ($response->status == Auth_OpenID_SUCCESS) { |
|---|
| 184 | // This means the authentication succeeded; extract the |
|---|
| 185 | // identity URL and Simple Registration data (if it was |
|---|
| 186 | // returned). |
|---|
| 187 | $openid = $response->getDisplayIdentifier(); |
|---|
| 188 | $esc_identity = StringUtil::encodeHTML($openid); |
|---|
| 189 | |
|---|
| 190 | if ($response->endpoint->canonicalID) { |
|---|
| 191 | $encoded_canonicalID = StringUtil::encodeHTML($response->endpoint->canonicalID); |
|---|
| 192 | } |
|---|
| 193 | |
|---|
| 194 | $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response); |
|---|
| 195 | $sreg = $sreg_resp->contents(); |
|---|
| 196 | |
|---|
| 197 | // save user authentication |
|---|
| 198 | $user = $this->finishUser(array( |
|---|
| 199 | 'name' => isset($sreg['nickname']) ? $sreg['nickname'] : null, |
|---|
| 200 | 'email' => isset($sreg['email']) ? $sreg['email'] : null, |
|---|
| 201 | 'identifier' => $openid |
|---|
| 202 | )); |
|---|
| 203 | |
|---|
| 204 | if($user) { |
|---|
| 205 | // set cookies |
|---|
| 206 | UserAuth::getInstance()->storeAccessData($user, $user->username, $user->password); |
|---|
| 207 | HeaderUtil::setCookie('password', $user->password, TIME_NOW + 365 * 24 * 3600); |
|---|
| 208 | |
|---|
| 209 | // change user |
|---|
| 210 | WCF::getSession()->changeUser($user); |
|---|
| 211 | } |
|---|
| 212 | } |
|---|
| 213 | } |
|---|
| 214 | |
|---|
| 215 | public function tryAttributionExchange($openid, $returnTo) { |
|---|
| 216 | $consumer = $this->getConsumer(); |
|---|
| 217 | |
|---|
| 218 | // Create an authentication request to the OpenID provider |
|---|
| 219 | $auth = $consumer->begin($openid); |
|---|
| 220 | |
|---|
| 221 | // Create attribute request object |
|---|
| 222 | // See http://code.google.com/apis/accounts/docs/OpenID.html#Parameters for parameters |
|---|
| 223 | // Usage: make($type_uri, $count=1, $required=false, $alias=null) |
|---|
| 224 | $attribute = array(); |
|---|
| 225 | $attribute[] = Auth_OpenID_AX_AttrInfo::make('http://axschema.org/contact/email', 1, 1, 'email'); |
|---|
| 226 | $attribute[] = Auth_OpenID_AX_AttrInfo::make('http://axschema.org/namePerson/first', 1, 1, 'firstname'); |
|---|
| 227 | $attribute[] = Auth_OpenID_AX_AttrInfo::make('http://axschema.org/namePerson/last', 1, 1, 'lastname'); |
|---|
| 228 | |
|---|
| 229 | // Create AX fetch request |
|---|
| 230 | $ax = new Auth_OpenID_AX_FetchRequest(); |
|---|
| 231 | |
|---|
| 232 | // Add attributes to AX fetch request |
|---|
| 233 | foreach($attribute as $attr){ |
|---|
| 234 | $ax->add($attr); |
|---|
| 235 | } |
|---|
| 236 | |
|---|
| 237 | // Add AX fetch request to authentication request |
|---|
| 238 | $auth->addExtension($ax); |
|---|
| 239 | |
|---|
| 240 | // Redirect to OpenID provider for authentication |
|---|
| 241 | $url = $auth->redirectURL(self::getTrustRoot(), $returnTo); |
|---|
| 242 | header('Location: ' . $url); |
|---|
| 243 | exit; |
|---|
| 244 | } |
|---|
| 245 | |
|---|
| 246 | public function finishAttributionExchange($return_to) { |
|---|
| 247 | $consumer = $this->getConsumer(); |
|---|
| 248 | |
|---|
| 249 | // Create an authentication request to the OpenID provider |
|---|
| 250 | $response = $consumer->complete($return_to); |
|---|
| 251 | |
|---|
| 252 | if ($response->status == Auth_OpenID_SUCCESS) { |
|---|
| 253 | |
|---|
| 254 | // Get registration informations |
|---|
| 255 | $ax = new Auth_OpenID_AX_FetchResponse(); |
|---|
| 256 | $obj = $ax->fromSuccessResponse($response); |
|---|
| 257 | |
|---|
| 258 | // Print me raw |
|---|
| 259 | $me = array(); |
|---|
| 260 | foreach($obj->data as $key => $val) { |
|---|
| 261 | if(!isset($val[0])) { |
|---|
| 262 | continue; |
|---|
| 263 | } |
|---|
| 264 | $key = substr($key, strrpos($key, '/') + 1); |
|---|
| 265 | $me[$key] = $val[0]; |
|---|
| 266 | } |
|---|
| 267 | |
|---|
| 268 | $userID = WCF::getUser()->userID; |
|---|
| 269 | if($userID) { |
|---|
| 270 | $editor = WCF::getUser()->getEditor(); |
|---|
| 271 | |
|---|
| 272 | // only update username, if old username is still hashed |
|---|
| 273 | $username = ''; |
|---|
| 274 | if(preg_match('/#\d+$/', $editor->username)) { |
|---|
| 275 | if(isset($me['first'])) { |
|---|
| 276 | $username .= ucfirst($me['first']); |
|---|
| 277 | } |
|---|
| 278 | if(isset($me['last'])) { |
|---|
| 279 | $username .= ucfirst($me['last']); |
|---|
| 280 | } |
|---|
| 281 | if(!empty($username)) { |
|---|
| 282 | $username = $this->findUsername($username); |
|---|
| 283 | } |
|---|
| 284 | } |
|---|
| 285 | |
|---|
| 286 | // update email address |
|---|
| 287 | $email = ''; |
|---|
| 288 | if(isset($me['email']) && UserUtil::isValidEmail($me['email']) && UserUtil::isAvailableEmail($me['email'])) { |
|---|
| 289 | $email = $me['email']; |
|---|
| 290 | } |
|---|
| 291 | |
|---|
| 292 | if($username || $email) { |
|---|
| 293 | $editor->update($username, $email); |
|---|
| 294 | WCF::getSession()->updateUserData(); |
|---|
| 295 | } |
|---|
| 296 | } |
|---|
| 297 | } |
|---|
| 298 | } |
|---|
| 299 | |
|---|
| 300 | /** |
|---|
| 301 | * is there an existing user with given facebook id? |
|---|
| 302 | * |
|---|
| 303 | * @param integer $userID |
|---|
| 304 | * @return boolean |
|---|
| 305 | */ |
|---|
| 306 | public static function hasOpenIDAccount($userID) { |
|---|
| 307 | $sql = "SELECT uto.userID |
|---|
| 308 | FROM wcf".WCF_N."_user_to_openid uto |
|---|
| 309 | WHERE utb.userID = ".intval($userID); |
|---|
| 310 | $row = WCF::getDB()->getFirstRow($sql); |
|---|
| 311 | |
|---|
| 312 | return $row && $row['userID'] > 0; |
|---|
| 313 | } |
|---|
| 314 | |
|---|
| 315 | /** |
|---|
| 316 | * is there an existing user with given openid id? |
|---|
| 317 | * |
|---|
| 318 | * @param array $me |
|---|
| 319 | * @return User |
|---|
| 320 | */ |
|---|
| 321 | protected static function getOpenIDEnabledUser($me) { |
|---|
| 322 | $sql = "SELECT userID |
|---|
| 323 | FROM wcf".WCF_N."_user_to_openid uto |
|---|
| 324 | WHERE openID = '".sha1($me['identifier'])."'"; |
|---|
| 325 | $row = WCF::getDB()->getFirstRow($sql); |
|---|
| 326 | |
|---|
| 327 | $user = $row ? new User($row['userID']) : null; |
|---|
| 328 | return $user && $user->userID ? $user : null; |
|---|
| 329 | } |
|---|
| 330 | |
|---|
| 331 | /** |
|---|
| 332 | * adds openid link to user |
|---|
| 333 | * |
|---|
| 334 | * @param array $me |
|---|
| 335 | * @param User $user |
|---|
| 336 | * @return boolean |
|---|
| 337 | */ |
|---|
| 338 | protected function addOpenIDUser($me, $user) { |
|---|
| 339 | $sql = "REPLACE INTO wcf".WCF_N."_user_to_openid |
|---|
| 340 | (openID, userID) |
|---|
| 341 | VALUES ('".sha1($me['identifier'])."', ".intval($user->userID).")"; |
|---|
| 342 | |
|---|
| 343 | return WCF::getDB()->sendQuery($sql); |
|---|
| 344 | } |
|---|
| 345 | |
|---|
| 346 | /** |
|---|
| 347 | * save incoming user |
|---|
| 348 | */ |
|---|
| 349 | protected function finishUser($me) { |
|---|
| 350 | |
|---|
| 351 | // take default username from hostname |
|---|
| 352 | if($me['name'] === null) { |
|---|
| 353 | $host = parse_url($me['identifier'], PHP_URL_HOST)." ID #1"; |
|---|
| 354 | $host = preg_replace("/^www\./", "", $host); |
|---|
| 355 | $me['name'] = $host; |
|---|
| 356 | } |
|---|
| 357 | |
|---|
| 358 | // openid permissions granted, does an login exist? |
|---|
| 359 | $user = self::getOpenIDEnabledUser($me); |
|---|
| 360 | |
|---|
| 361 | // openid permissions granted but no login exists |
|---|
| 362 | if(!$user) { |
|---|
| 363 | |
|---|
| 364 | // totally unknown, add a new user |
|---|
| 365 | $user = $this->registerUser($me); |
|---|
| 366 | |
|---|
| 367 | // either user is new, oder just got a link, but add a openid link |
|---|
| 368 | $this->addOpenIDUser($me, $user); |
|---|
| 369 | } |
|---|
| 370 | |
|---|
| 371 | return $user; |
|---|
| 372 | } |
|---|
| 373 | |
|---|
| 374 | /** |
|---|
| 375 | * get a available username |
|---|
| 376 | * |
|---|
| 377 | * @param string $username |
|---|
| 378 | * @return string |
|---|
| 379 | */ |
|---|
| 380 | protected function findUsername($username) { |
|---|
| 381 | if(!UserUtil::isValidUsername($username)) { |
|---|
| 382 | return null; |
|---|
| 383 | } |
|---|
| 384 | |
|---|
| 385 | if(UserUtil::isAvailableUsername($username)) { |
|---|
| 386 | return $username; |
|---|
| 387 | } |
|---|
| 388 | |
|---|
| 389 | // try to increase last digit |
|---|
| 390 | if(preg_match('/(\d+)$/', $username, $res)) { |
|---|
| 391 | return $this->findUsername(preg_replace('/(\d+)/', ($res[1] + 1), $username)); |
|---|
| 392 | } else { |
|---|
| 393 | return $this->findUsername($username.'2'); |
|---|
| 394 | } |
|---|
| 395 | } |
|---|
| 396 | |
|---|
| 397 | /** |
|---|
| 398 | * registers a new user with valid username |
|---|
| 399 | * |
|---|
| 400 | * @param array $me |
|---|
| 401 | * @return User |
|---|
| 402 | */ |
|---|
| 403 | protected function registerUser($me) { |
|---|
| 404 | $user = null; |
|---|
| 405 | // get a valid username |
|---|
| 406 | $username = $this->findUsername($me['name']); |
|---|
| 407 | |
|---|
| 408 | // take default email |
|---|
| 409 | if($me['email'] === null || !UserUtil::isValidEmail($me['email'])) { |
|---|
| 410 | $host = parse_url($me['identifier'], PHP_URL_HOST); |
|---|
| 411 | $host = preg_replace("/^www\./", "", $host); |
|---|
| 412 | $me['email'] = md5($me['identifier']).'@openid.'.$host; |
|---|
| 413 | } |
|---|
| 414 | |
|---|
| 415 | // create new user |
|---|
| 416 | if($username) { |
|---|
| 417 | $user = $this->createNewUser( |
|---|
| 418 | $username, |
|---|
| 419 | $me['email'] |
|---|
| 420 | ); |
|---|
| 421 | } else { |
|---|
| 422 | throw new SystemException('invalid openid username: '.$me['name']); |
|---|
| 423 | } |
|---|
| 424 | return $user; |
|---|
| 425 | } |
|---|
| 426 | |
|---|
| 427 | /** |
|---|
| 428 | * adds a new wcf user and sends, bypasses all registration steps and send out mails |
|---|
| 429 | * |
|---|
| 430 | * @param string $username |
|---|
| 431 | * @param string $email |
|---|
| 432 | * @return User |
|---|
| 433 | */ |
|---|
| 434 | protected function createNewUser($username, $email) { |
|---|
| 435 | |
|---|
| 436 | $password = UserRegistrationUtil::getNewPassword((REGISTER_PASSWORD_MIN_LENGTH > 9 ? REGISTER_PASSWORD_MIN_LENGTH : 9)); |
|---|
| 437 | $groups = array(); |
|---|
| 438 | $activeOptions = array(); |
|---|
| 439 | |
|---|
| 440 | $additionalFields = array(); |
|---|
| 441 | $additionalFields['languageID'] = WCF::getLanguage()->getLanguageID(); |
|---|
| 442 | $additionalFields['registrationIpAddress'] = WCF::getSession()->ipAddress; |
|---|
| 443 | |
|---|
| 444 | $visibleLanguages = $this->getAvailableLanguages(); |
|---|
| 445 | $visibleLanguages = array_keys($visibleLanguages); |
|---|
| 446 | $addDefaultGroups = true; |
|---|
| 447 | |
|---|
| 448 | // create the user |
|---|
| 449 | if(($user = UserEditor::create($username, $email, $password, $groups, $activeOptions, $additionalFields, $visibleLanguages, $addDefaultGroups))) { |
|---|
| 450 | |
|---|
| 451 | // notify admin |
|---|
| 452 | if (REGISTER_ADMIN_NOTIFICATION) { |
|---|
| 453 | // get default language |
|---|
| 454 | $language = (WCF::getLanguage()->getLanguageID() != Language::getDefaultLanguageID() |
|---|
| 455 | ? new Language(Language::getDefaultLanguageID()) |
|---|
| 456 | : WCF::getLanguage()); |
|---|
| 457 | $language->setLocale(); |
|---|
| 458 | |
|---|
| 459 | // send mail |
|---|
| 460 | $mail = new Mail( |
|---|
| 461 | MAIL_ADMIN_ADDRESS, |
|---|
| 462 | $language->get('wcf.user.register.notification.mail.subject', array( |
|---|
| 463 | 'PAGE_TITLE' => $language->get(PAGE_TITLE) |
|---|
| 464 | )), |
|---|
| 465 | $language->get('wcf.user.register.notification.mail', array( |
|---|
| 466 | 'PAGE_TITLE' => $language->get(PAGE_TITLE), |
|---|
| 467 | '$username' => $user->username |
|---|
| 468 | )) |
|---|
| 469 | ); |
|---|
| 470 | $mail->send(); |
|---|
| 471 | |
|---|
| 472 | WCF::getLanguage()->setLocale(); |
|---|
| 473 | } |
|---|
| 474 | } |
|---|
| 475 | |
|---|
| 476 | return $user; |
|---|
| 477 | } |
|---|
| 478 | |
|---|
| 479 | /** |
|---|
| 480 | * Returns a list of all available languages. |
|---|
| 481 | * |
|---|
| 482 | * @return array |
|---|
| 483 | */ |
|---|
| 484 | protected function getAvailableLanguages() { |
|---|
| 485 | $availableLanguages = array(); |
|---|
| 486 | foreach (Language::getAvailableLanguages(PACKAGE_ID) as $language) { |
|---|
| 487 | $availableLanguages[$language['languageID']] = WCF::getLanguage()->get('wcf.global.language.'.$language['languageCode']); |
|---|
| 488 | } |
|---|
| 489 | |
|---|
| 490 | // sort languages |
|---|
| 491 | StringUtil::sort($availableLanguages); |
|---|
| 492 | |
|---|
| 493 | return $availableLanguages; |
|---|
| 494 | } |
|---|
| 495 | |
|---|
| 496 | /** |
|---|
| 497 | * @see UserLoginForm::readData |
|---|
| 498 | */ |
|---|
| 499 | public static function updateCurrentUser() { |
|---|
| 500 | |
|---|
| 501 | } |
|---|
| 502 | } |
|---|