users = $this->load(); } /** * Add user account. * * @param string $username * @param string $password1 * @param string $password2 * @param string $email * @param Messenger $Messenger * @return bool true on success */ public function createUser( string $username, string $password1, string $password2, string $email, Messenger $Messenger, ): bool { $username = trim($username); $email = trim($email); if (!$this->validUsername($username)) { $Messenger->setError($this->invalidUsernameError()); return false; } if ($email && !$this->validEmail($email)) { $Messenger->setError($this->invalidEmailError()); return false; } if (!$username || !$password1 || !$password2) { $Messenger->setError(Text::get('invalidFormError')); return false; } if ($password1 != $password2) { $Messenger->setError(Text::get('repeatPassword')); return false; } if ($this->getUser($username)) { $Messenger->setError('"' . $username . '" ' . Text::get('alreadyExists')); return false; } if ($this->getUser($email)) { $Messenger->setError('"' . $email . '" ' . Text::get('alreadyExists')); return false; } $this->users[] = new User($username, $password1, $email); return true; } /** * Delete one ore more user accounts. * * @param array $users * @param Messenger $Messenger * @return bool true on success */ public function delete(array $users, Messenger $Messenger): bool { if (is_writable(UserCollection::FILE_ACCOUNTS)) { foreach ($users as $username) { $id = $this->getUserId($username); if (!is_null($id) && isset($this->users[$id])) { unset($this->users[$id]); } } return $this->save($Messenger); } $Messenger->setError(Text::get('permissionsDeniedError')); return false; } /** * Edit info of the currently logged in user. * * @param string $username * @param string $email * @param Messenger $Messenger * @return bool true on success */ public function editCurrentUserInfo(string $username, string $email, Messenger $Messenger): bool { $id = $this->getUserId(Session::getUsername()); if (is_null($id) || empty($this->users[$id])) { return false; } $User = $this->users[$id]; // Unset temporary the array item here in order to check easily for duplicate eamils or names. unset($this->users[$id]); if (!$username) { $Messenger->setError(Text::get('invalidFormError')); return false; } $username = trim($username); $email = trim($email); if (!$this->validUsername($username)) { $Messenger->setError($this->invalidUsernameError()); return false; } if (!$this->validEmail($email)) { $Messenger->setError($this->invalidEmailError()); return false; } if ($this->getUser($username)) { $Messenger->setError('"' . $username . '" ' . Text::get('alreadyExists')); return false; } if ($this->getUser($email)) { $Messenger->setError('"' . $email . '" ' . Text::get('alreadyExists')); return false; } $User->name = $username; $_SESSION['username'] = $username; $User->email = $email; $this->users[$id] = $User; return true; } /** * Generate the PHP code for the accounts file. Basically the code returns the unserialized serialized array with all users. * That way, the accounts array can be stored as PHP. * The accounts file has to be a PHP file for security reasons. When trying to access the file directly via the browser, * it gets executed instead of revealing any user names. * * @return string the PHP code */ public function generatePHP(): string { ksort($this->users); // The actual class name is replaced with a placeholder in order // to be able to refactor the type class in the future easily. $serialized = str_replace( 'O:' . strlen($this->userType) . ':"' . $this->userType . '"', $this->userTypeSerialized, serialize($this->users) ); return "users; } /** * Return a user by name or email address. * * @param string $nameOrEmail * @return User|null the requested user account */ public function getUser(string $nameOrEmail): ?User { if (empty($nameOrEmail)) { return null; } foreach ($this->users as $User) { if ($nameOrEmail === $User->name || $nameOrEmail === $User->email) { return $User; } } return null; } /** * Save the accounts array as PHP to FILE_ACCOUNTS. * * @param Messenger $Messenger * @return bool true on success */ public function save(Messenger $Messenger): bool { if (!FileSystem::write(UserCollection::FILE_ACCOUNTS, $this->generatePHP())) { $Messenger->setError(Text::get('permissionsDeniedError')); return false; } if (function_exists('opcache_invalidate')) { opcache_invalidate(UserCollection::FILE_ACCOUNTS, true); } Cache::clear(); return true; } /** * Send invitation email. * * @param string $username * @param string $email * @param Messenger $Messenger * @return bool true on success */ public function sendInvitation(string $username, string $email, Messenger $Messenger): bool { $website = $_SERVER['SERVER_NAME'] ?? '' . AM_BASE_URL; $link = AM_SERVER . AM_BASE_INDEX . AM_PAGE_DASHBOARD . '/resetpassword?username=' . urlencode($username); $subject = 'Automad: ' . Text::get('emailInviteSubject'); $message = InvitationEmail::render($website, $username, $link); $headers = "MIME-Version: 1.0\r\n"; $headers .= 'Content-type: text/html; charset=UTF-8'; if (!mail($email, $subject, $message, $headers)) { $Messenger->setError(Text::get('sendMailError')); return false; } return true; } /** * Convert legacy accounts file content. * * @param array $contents * @return string the serialized accounts */ private function convertLegacyAccountsFile(array $contents): string { $accounts = array(); foreach ($contents as $name => $passwordHash) { $accounts[] = (object) array('name' => $name, 'passwordHash' => $passwordHash); } return str_replace( 'O:8:"stdClass"', $this->userTypeSerialized, serialize($accounts) ); } /** * Return a user id by name or email address. * * @param string $nameOrEmail * @return int|null the requested user id */ private function getUserId(string $nameOrEmail): ?int { foreach ($this->users as $id => $User) { if ($nameOrEmail === $User->name || $nameOrEmail === $User->email) { return $id; } } return null; } /** * The invalid email error message. * * @return string the error message */ private function invalidEmailError(): string { return Text::get('invalidEmailError'); } /** * The invalid username error message. * * @return string the error message */ private function invalidUsernameError(): string { return Text::get('invalidUsernameError') . ' "a-z", "A-Z", ".", "-", "_", "@"'; } /** * Get the accounts array by including the accounts PHP file. * * @see User * @return array The registered accounts */ private function load(): array { if (!is_readable(UserCollection::FILE_ACCOUNTS)) { return array(); } $contents = include UserCollection::FILE_ACCOUNTS; // Lagacy support. if (is_array($contents)) { $contents = $this->convertLegacyAccountsFile($contents); } $serialized = str_replace( $this->userTypeSerialized, 'O:' . strlen($this->userType) . ':"' . $this->userType . '"', $contents ); return unserialize($serialized); } /** * Verify if a given email address is valid. * * @param string $email * @return bool true in case the username is valid */ private function validEmail(string $email = ''): bool { preg_match('/^[a-zA-Z0-9]+[\w\.\-\_]*@[\w\.\-\_]+\.[a-zA-Z]+$/', $email, $matches); return (bool) $matches; } /** * Verify if a given username is valid. * * @param string $username * @return bool true in case the username is valid */ private function validUsername(string $username): bool { preg_match('/[^@\w\.\-]/', $username, $matches); return empty($matches); } } __halt_compiler();----SIGNATURE:----r1+GK4iW1RicUZp4fETKGbP0VOE/iBgDRt47wGHeL3Qk4KMfoP/ySIFuc724BTva4Z6jp1edIWqBY8cMeoXqI7JJIczYmNzzvDEOxiukXMe1cvdLwJnhAmOV73RhNLWFM8uwf+ybAHoBT8kkZbgWkBAXnc5D9WrRswsZ+PxrbDNFXUAfPb3YEoOq/N/q1jwZO4d1Pa6NeVEjf6tb40y+fYM8gzaSfqhydjXCCBbtNlGx8feITR+rmj7Upklr4iRwL4FXD2xVRlPaZ1QFWKUrv1UuhnzVfIK/KhagVt8ntxZK4zw+FnyuDtaxZO27qvpxhaysPoaIon9dSc3B9HJFc7V9ctqlLtJ77l+hBUwrUDSpZveXu1mG+uJs8O5iUa6fbUkrg+F9sB/X0moOj6mYPbpgKhxe7FltOlBCHPVe1D4onimDbsVJiInbcEwhMDCPBs8rvdtm3WF1nZuYWnLMabmWhqJ6mprEekcJHOfkqSqXU/HiAkh1SY23GGpkj6NxIZ11gUjwC3KXWP6urB1e0L7sLCsDXCN7alaGMSjXsuen6dgAM+F7YJJyTLfunGKnt/XTmSSOsb5xg2aAYomGg4GSW1O4802WUU7Tpl2B7hi8OOQkzO+xFt/IsgRWrgzy03I6jt2J3GxnbH+MgCiSSaGCqMTTCxdGHg7an5UmJC0=----ATTACHMENT:----MTg5MTYwNzg2MTkzNzQ5NyA3Mjc3MzQ5Mjk0ODk4MTAzIDMxNTU1NDAzODYzNzA0NjA=