diff --git a/src/Flarum/Core/CoreServiceProvider.php b/src/Flarum/Core/CoreServiceProvider.php index 78976fcc3..82952bd7b 100644 --- a/src/Flarum/Core/CoreServiceProvider.php +++ b/src/Flarum/Core/CoreServiceProvider.php @@ -32,6 +32,7 @@ class CoreServiceProvider extends ServiceProvider Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\DiscussionMetadataUpdater'); Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\UserMetadataUpdater'); Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\RenamedPostCreator'); + Event::listen('Flarum.Core.*', 'Flarum\Core\Listeners\EmailConfirmationMailer'); Post::addType('comment', 'Flarum\Core\Posts\CommentPost'); Post::addType('renamed', 'Flarum\Core\Posts\RenamedPost'); diff --git a/src/Flarum/Core/Listeners/EmailConfirmationMailer.php b/src/Flarum/Core/Listeners/EmailConfirmationMailer.php new file mode 100755 index 000000000..90bdda0b9 --- /dev/null +++ b/src/Flarum/Core/Listeners/EmailConfirmationMailer.php @@ -0,0 +1,36 @@ +mailer = $mailer; + } + + public function whenUserWasRegistered(UserWasRegistered $event) + { + $user = $event->user; + + $data = [ + 'user' => $user, + 'url' => route('flarum.confirm', ['id' => $user->id, 'token' => $user->confirmation_token]) + ]; + + $this->mailer->send('flarum::emails.confirm', $data, function ($message) use ($user) { + $message->to($user->email)->subject('Welcome!'); + }); + } + + public function whenEmailWasChanged(EmailWasChanged $event) + { + + } +} diff --git a/src/Flarum/Core/Support/Exceptions/InvalidConfirmationTokenException.php b/src/Flarum/Core/Support/Exceptions/InvalidConfirmationTokenException.php new file mode 100644 index 000000000..3deb1dd59 --- /dev/null +++ b/src/Flarum/Core/Support/Exceptions/InvalidConfirmationTokenException.php @@ -0,0 +1,7 @@ + $faker->userName, - 'email' => $faker->safeEmail, - 'password' => 'password', - 'join_time' => $faker->dateTimeThisYear, - 'time_zone' => $faker->timezone + 'username' => $faker->userName, + 'email' => $faker->safeEmail, + 'is_confirmed' => true, + 'password' => 'password', + 'join_time' => $faker->dateTimeThisYear ]); // Assign the users to the 'Member' group, and possibly some others. @@ -49,6 +49,7 @@ class UserTableSeeder extends Seeder // Guests can view the forum ['group.2' , 'forum' , 'view'], + ['group.2' , 'forum' , 'register'], // Members can create and reply to discussions + edit their own stuff ['group.3' , 'forum' , 'startDiscussion'], diff --git a/src/Flarum/Core/Users/Commands/ConfirmEmailCommand.php b/src/Flarum/Core/Users/Commands/ConfirmEmailCommand.php new file mode 100644 index 000000000..fc7d6547b --- /dev/null +++ b/src/Flarum/Core/Users/Commands/ConfirmEmailCommand.php @@ -0,0 +1,14 @@ +userId = $userId; + $this->token = $token; + } +} diff --git a/src/Flarum/Core/Users/Commands/ConfirmEmailCommandHandler.php b/src/Flarum/Core/Users/Commands/ConfirmEmailCommandHandler.php new file mode 100644 index 000000000..62b8219e2 --- /dev/null +++ b/src/Flarum/Core/Users/Commands/ConfirmEmailCommandHandler.php @@ -0,0 +1,38 @@ +userRepo = $userRepo; + } + + public function handle($command) + { + $user = $this->userRepo->findOrFail($command->userId); + + $user->confirmEmail($command->token); + + // If the user hasn't yet had their account activated, + if (! $user->join_time) { + $user->activate(); + } + + Event::fire('Flarum.Core.Users.Commands.ConfirmEmail.UserWillBeSaved', [$user, $command]); + + $this->userRepo->save($user); + $this->dispatchEventsFor($user); + + return $user; + } +} diff --git a/src/Flarum/Core/Users/Commands/ConfirmEmailValidator.php b/src/Flarum/Core/Users/Commands/ConfirmEmailValidator.php new file mode 100644 index 000000000..feac12843 --- /dev/null +++ b/src/Flarum/Core/Users/Commands/ConfirmEmailValidator.php @@ -0,0 +1,11 @@ +userRepo->save($user); - $this->userRepo->syncGroups($user, [3]); // default groups $this->dispatchEventsFor($user); return $user; diff --git a/src/Flarum/Core/Users/Events/EmailWasConfirmed.php b/src/Flarum/Core/Users/Events/EmailWasConfirmed.php new file mode 100644 index 000000000..441455a48 --- /dev/null +++ b/src/Flarum/Core/Users/Events/EmailWasConfirmed.php @@ -0,0 +1,13 @@ +user = $user; + } +} diff --git a/src/Flarum/Core/Users/Events/UserWasActivated.php b/src/Flarum/Core/Users/Events/UserWasActivated.php new file mode 100644 index 000000000..40c7b73d6 --- /dev/null +++ b/src/Flarum/Core/Users/Events/UserWasActivated.php @@ -0,0 +1,13 @@ +user = $user; + } +} diff --git a/src/Flarum/Core/Users/User.php b/src/Flarum/Core/Users/User.php index 2ad7cf893..68c6637a4 100755 --- a/src/Flarum/Core/Users/User.php +++ b/src/Flarum/Core/Users/User.php @@ -14,12 +14,13 @@ use Laracasts\Commander\Events\EventGenerator; use Flarum\Core\Entity; use Flarum\Core\Groups\Group; use Flarum\Core\Support\Exceptions\PermissionDeniedException; +use Flarum\Core\Support\Exceptions\InvalidConfirmationTokenException; class User extends Entity implements UserInterface, RemindableInterface { use EventGenerator; use Permissible; - + use UserTrait, RemindableTrait; protected static $rules = [ @@ -35,7 +36,7 @@ class User extends Entity implements UserInterface, RemindableInterface protected $table = 'users'; protected $hidden = ['password']; - + public static function boot() { parent::boot(); @@ -61,12 +62,20 @@ class User extends Entity implements UserInterface, RemindableInterface public function setUsernameAttribute($username) { + if ($username === $this->username) { + return; + } + $this->attributes['username'] = $username; $this->raise(new Events\UserWasRenamed($this)); } public function setEmailAttribute($email) { + if ($email === $this->email) { + return; + } + $this->attributes['email'] = $email; $this->raise(new Events\EmailWasChanged($this)); } @@ -77,6 +86,14 @@ class User extends Entity implements UserInterface, RemindableInterface $this->raise(new Events\PasswordWasChanged($this)); } + public function activate() + { + $this->join_time = time(); + $this->groups()->sync([3]); + + $this->raise(new Events\UserWasActivated($this)); + } + public static function register($username, $email, $password) { $user = new static; @@ -84,13 +101,39 @@ class User extends Entity implements UserInterface, RemindableInterface $user->username = $username; $user->email = $email; $user->password = $password; - $user->join_time = time(); + + $user->refreshConfirmationToken(); $user->raise(new Events\UserWasRegistered($user)); return $user; } + public function validateConfirmationToken($token) + { + return ! $this->is_confirmed + && $token + && $this->confirmation_token === $token; + } + + public function refreshConfirmationToken() + { + $this->is_confirmed = false; + $this->confirmation_token = str_random(30); + } + + public function confirmEmail($token) + { + if (! $this->validateConfirmationToken($token)) { + throw new InvalidConfirmationTokenException; + } + + $this->is_confirmed = true; + $this->confirmation_token = null; + + $this->raise(new Events\EmailWasConfirmed($this)); + } + public function getDates() { return ['join_time', 'last_seen_time', 'read_time']; diff --git a/src/migrations/2014_01_14_231404_create_users_table.php b/src/migrations/2014_01_14_231404_create_users_table.php index 9d7d7e42a..0efe6efef 100644 --- a/src/migrations/2014_01_14_231404_create_users_table.php +++ b/src/migrations/2014_01_14_231404_create_users_table.php @@ -5,37 +5,38 @@ use Illuminate\Database\Migrations\Migration; class CreateUsersTable extends Migration { - /** - * Run the migrations. - * - * @return void - */ - public function up() - { - Schema::create('users', function(Blueprint $table) - { - $table->increments('id'); - $table->string('username'); - $table->string('email'); - $table->string('password'); - $table->string('token'); - $table->dateTime('join_time'); - $table->string('time_zone'); - $table->dateTime('last_seen_time')->nullable(); - $table->dateTime('read_time')->nullable(); - $table->integer('discussions_count')->unsigned()->default(0); - $table->integer('posts_count')->unsigned()->default(0); - }); - } + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::create('users', function(Blueprint $table) + { + $table->increments('id'); + $table->string('username'); + $table->string('email'); + $table->boolean('is_confirmed')->default(0); + $table->string('confirmation_token')->nullable(); + $table->string('password'); + $table->string('token'); + $table->dateTime('join_time')->nullable(); + $table->dateTime('last_seen_time')->nullable(); + $table->dateTime('read_time')->nullable(); + $table->integer('discussions_count')->unsigned()->default(0); + $table->integer('posts_count')->unsigned()->default(0); + }); + } - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::drop('users'); - } + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('users'); + } } diff --git a/src/routes.php b/src/routes.php index 76042fd64..961be7629 100755 --- a/src/routes.php +++ b/src/routes.php @@ -1,7 +1,13 @@ with('title', Config::get('flarum::forum_title', 'Flarum Demo Forum')); +Route::get('/', function () { + return View::make('flarum.web::index') + ->with('title', Config::get('flarum::forum_title', 'Flarum Demo Forum')); }); + +Route::get('confirm/{id}/{token}', ['as' => 'flarum.confirm', function ($userId, $token) { + $command = new Flarum\Core\Users\Commands\ConfirmEmailCommand($userId, $token); + + $commandBus = App::make('Laracasts\Commander\CommandBus'); + $commandBus->execute($command); +}]); diff --git a/src/views/emails/confirm.blade.php b/src/views/emails/confirm.blade.php new file mode 100644 index 000000000..6bd5a7fc2 --- /dev/null +++ b/src/views/emails/confirm.blade.php @@ -0,0 +1,13 @@ + + +
+ + + +