mirror of
https://github.com/flarum/core.git
synced 2025-10-17 09:46:14 +02:00
Merge branch 'master' into psr-7
Conflicts: composer.json composer.lock src/Api/Actions/TokenAction.php src/Core/Formatter/FormatterManager.php src/Core/Handlers/Events/EmailConfirmationMailer.php src/Forum/Actions/ConfirmEmailAction.php src/Forum/Actions/IndexAction.php src/Forum/Actions/ResetPasswordAction.php src/Forum/Actions/SavePasswordAction.php src/Forum/routes.php
This commit is contained in:
@@ -41,7 +41,9 @@ class IndexAction extends SerializeCollectionAction
|
||||
'lastUser' => true,
|
||||
'startPost' => false,
|
||||
'lastPost' => false,
|
||||
'relevantPosts' => false
|
||||
'relevantPosts' => false,
|
||||
'relevantPosts.discussion' => false,
|
||||
'relevantPosts.user' => false
|
||||
];
|
||||
|
||||
/**
|
||||
|
@@ -51,7 +51,7 @@ class ShowAction extends SerializeResourceAction
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $link = ['posts'];
|
||||
public static $link = ['posts', 'posts.discussion'];
|
||||
|
||||
/**
|
||||
* The fields that are available to be sorted by.
|
||||
|
@@ -74,6 +74,7 @@ class IndexAction extends SerializeCollectionAction
|
||||
|
||||
$user->markNotificationsAsRead()->save();
|
||||
|
||||
return $this->notifications->findByUser($user, $request->limit, $request->offset);
|
||||
return $this->notifications->findByUser($user, $request->limit, $request->offset)
|
||||
->load($request->include);
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ use Flarum\Api\Request;
|
||||
use Flarum\Core\Commands\GenerateAccessTokenCommand;
|
||||
use Flarum\Core\Repositories\UserRepositoryInterface;
|
||||
use Flarum\Core\Exceptions\PermissionDeniedException;
|
||||
use Flarum\Core\Events\UserEmailChangeWasRequested;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
|
||||
class TokenAction extends JsonApiAction
|
||||
@@ -36,6 +37,14 @@ class TokenAction extends JsonApiAction
|
||||
throw new PermissionDeniedException;
|
||||
}
|
||||
|
||||
if (! $user->is_activated) {
|
||||
event(new UserEmailChangeWasRequested($user, $user->email));
|
||||
return $this->json([
|
||||
'code' => 'confirm_email',
|
||||
'email' => $user->email
|
||||
], 401);
|
||||
}
|
||||
|
||||
$token = $this->bus->dispatch(
|
||||
new GenerateAccessTokenCommand($user->id)
|
||||
);
|
||||
|
@@ -17,7 +17,7 @@ class ShowAction extends SerializeResourceAction
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $serializer = 'Flarum\Api\Serializers\UserSerializer';
|
||||
public static $serializer = 'Flarum\Api\Serializers\CurrentUserSerializer';
|
||||
|
||||
/**
|
||||
* The relationships that are available to be included, and which ones are
|
||||
|
@@ -49,12 +49,12 @@ abstract class BaseSerializer extends SerializerAbstract
|
||||
$relation = $caller['function'];
|
||||
}
|
||||
|
||||
return function ($model, $include, $links) use ($serializer, $many, $relation) {
|
||||
return function ($model, $include, $included, $links) use ($serializer, $many, $relation) {
|
||||
if ($relation instanceof Closure) {
|
||||
$data = $relation($model, $include);
|
||||
} else {
|
||||
if ($include) {
|
||||
$data = !is_null($model->$relation) ? $model->$relation : ($many ? $model->$relation()->get() : $model->$relation()->first());
|
||||
$data = !is_null($model->$relation) ? $model->$relation : $model->$relation()->getResults();
|
||||
} elseif ($many) {
|
||||
$relationIds = $relation.'_ids';
|
||||
$data = $model->$relationIds ?: $model->$relation()->get(['id'])->fetch('id')->all();
|
||||
@@ -67,7 +67,7 @@ abstract class BaseSerializer extends SerializerAbstract
|
||||
if ($serializer instanceof Closure) {
|
||||
$serializer = $serializer($model, $data);
|
||||
}
|
||||
$serializer = new $serializer($this->actor, $links);
|
||||
$serializer = new $serializer($this->actor, $included, $links);
|
||||
return $many ? $serializer->collection($data) : $serializer->resource($data);
|
||||
};
|
||||
}
|
||||
|
21
src/Api/Serializers/CurrentUserSerializer.php
Normal file
21
src/Api/Serializers/CurrentUserSerializer.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php namespace Flarum\Api\Serializers;
|
||||
|
||||
class CurrentUserSerializer extends UserSerializer
|
||||
{
|
||||
protected function attributes($user)
|
||||
{
|
||||
$attributes = parent::attributes($user);
|
||||
|
||||
$actingUser = $this->actor->getUser();
|
||||
|
||||
if ($user->id === $actingUser->id) {
|
||||
$attributes += [
|
||||
'readTime' => $user->read_time ? $user->read_time->toRFC3339String() : null,
|
||||
'unreadNotificationsCount' => $user->getUnreadNotificationsCount(),
|
||||
'preferences' => $user->preferences
|
||||
];
|
||||
}
|
||||
|
||||
return $this->extendAttributes($user, $attributes);
|
||||
}
|
||||
}
|
@@ -2,13 +2,6 @@
|
||||
|
||||
class DiscussionSerializer extends DiscussionBasicSerializer
|
||||
{
|
||||
/**
|
||||
* Default relations to include.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $include = ['startUser', 'lastUser'];
|
||||
|
||||
/**
|
||||
* Serialize attributes of a Discussion model for JSON output.
|
||||
*
|
||||
|
@@ -9,20 +9,6 @@ class PostBasicSerializer extends BaseSerializer
|
||||
*/
|
||||
protected $type = 'posts';
|
||||
|
||||
/**
|
||||
* Default relations to link.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $link = ['discussion'];
|
||||
|
||||
/**
|
||||
* Default relations to include.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $include = ['user'];
|
||||
|
||||
/**
|
||||
* Serialize attributes of a Post model for JSON output.
|
||||
*
|
||||
@@ -39,7 +25,7 @@ class PostBasicSerializer extends BaseSerializer
|
||||
];
|
||||
|
||||
if ($post->type === 'comment') {
|
||||
$attributes['excerpt'] = str_limit($post->contentPlain, 200);
|
||||
$attributes['contentHtml'] = $post->content_html;
|
||||
} else {
|
||||
$attributes['content'] = $post->content;
|
||||
}
|
||||
|
@@ -2,13 +2,6 @@
|
||||
|
||||
class PostSerializer extends PostBasicSerializer
|
||||
{
|
||||
/**
|
||||
* Default relations to include.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $include = ['user', 'editUser', 'hideUser'];
|
||||
|
||||
/**
|
||||
* Serialize attributes of a Post model for JSON output.
|
||||
*
|
||||
|
@@ -2,13 +2,6 @@
|
||||
|
||||
class UserSerializer extends UserBasicSerializer
|
||||
{
|
||||
/**
|
||||
* Default relations to include.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $include = ['groups'];
|
||||
|
||||
/**
|
||||
* Serialize attributes of a User model for JSON output.
|
||||
*
|
||||
@@ -19,8 +12,8 @@ class UserSerializer extends UserBasicSerializer
|
||||
{
|
||||
$attributes = parent::attributes($user);
|
||||
|
||||
$actorUser = $this->actor->getUser();
|
||||
$canEdit = $user->can($actorUser, 'edit');
|
||||
$actingUser = $this->actor->getUser();
|
||||
$canEdit = $user->can($actingUser, 'edit');
|
||||
|
||||
$attributes += [
|
||||
'bioHtml' => $user->bio_html,
|
||||
@@ -28,7 +21,7 @@ class UserSerializer extends UserBasicSerializer
|
||||
'discussionsCount' => (int) $user->discussions_count,
|
||||
'commentsCount' => (int) $user->comments_count,
|
||||
'canEdit' => $canEdit,
|
||||
'canDelete' => $user->can($actorUser, 'delete'),
|
||||
'canDelete' => $user->can($actingUser, 'delete'),
|
||||
];
|
||||
|
||||
if ($user->preference('discloseOnline')) {
|
||||
@@ -46,14 +39,6 @@ class UserSerializer extends UserBasicSerializer
|
||||
];
|
||||
}
|
||||
|
||||
if ($user->id === $actorUser->id) {
|
||||
$attributes += [
|
||||
'readTime' => $user->read_time ? $user->read_time->toRFC3339String() : null,
|
||||
'unreadNotificationsCount' => $user->getUnreadNotificationsCount(),
|
||||
'preferences' => $user->preferences
|
||||
];
|
||||
}
|
||||
|
||||
return $this->extendAttributes($user, $attributes);
|
||||
}
|
||||
}
|
||||
|
@@ -28,8 +28,8 @@ class SeedCommand extends Command
|
||||
*/
|
||||
public function fire()
|
||||
{
|
||||
$this->call('db:seed', ['--class' => 'Flarum\Core\Seeders\DiscussionsTableSeeder']);
|
||||
$this->call('db:seed', ['--class' => 'Flarum\Core\Seeders\UsersTableSeeder']);
|
||||
$this->call('db:seed', ['--class' => 'Flarum\Core\Seeders\DiscussionsTableSeeder']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -11,6 +11,10 @@ class Core
|
||||
|
||||
public static function config($key, $default = null)
|
||||
{
|
||||
if (! static::isInstalled()) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
if (is_null($value = DB::table('config')->where('key', $key)->pluck('value'))) {
|
||||
$value = $default;
|
||||
}
|
||||
|
@@ -2,13 +2,10 @@
|
||||
|
||||
class ConfirmEmailCommand
|
||||
{
|
||||
public $userId;
|
||||
|
||||
public $token;
|
||||
|
||||
public function __construct($userId, $token)
|
||||
public function __construct($token)
|
||||
{
|
||||
$this->userId = $userId;
|
||||
$this->token = $token;
|
||||
}
|
||||
}
|
||||
|
@@ -92,6 +92,11 @@ class CoreServiceProvider extends ServiceProvider
|
||||
'Flarum\Core\Repositories\EloquentActivityRepository'
|
||||
);
|
||||
|
||||
$this->app->bind(
|
||||
'Flarum\Core\Search\Discussions\Fulltext\DriverInterface',
|
||||
'Flarum\Core\Search\Discussions\Fulltext\MySqlFulltextDriver'
|
||||
);
|
||||
|
||||
$avatarFilesystem = function (Container $app) {
|
||||
return $app->make('Illuminate\Contracts\Filesystem\Factory')->disk('flarum-avatars')->getDriver();
|
||||
};
|
||||
|
16
src/Core/Events/NotificationWillBeSent.php
Normal file
16
src/Core/Events/NotificationWillBeSent.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Notifications\NotificationInterface;
|
||||
|
||||
class NotificationWillBeSent
|
||||
{
|
||||
public $notification;
|
||||
|
||||
public $users;
|
||||
|
||||
public function __construct(NotificationInterface $notification, array &$users)
|
||||
{
|
||||
$this->notification = $notification;
|
||||
$this->users = $users;
|
||||
}
|
||||
}
|
16
src/Core/Events/UserEmailChangeWasRequested.php
Normal file
16
src/Core/Events/UserEmailChangeWasRequested.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
class UserEmailChangeWasRequested
|
||||
{
|
||||
public $user;
|
||||
|
||||
public $email;
|
||||
|
||||
public function __construct(User $user, $email)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->email = $email;
|
||||
}
|
||||
}
|
48
src/Core/Formatter/FormatterAbstract.php
Normal file
48
src/Core/Formatter/FormatterAbstract.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php namespace Flarum\Core\Formatter;
|
||||
|
||||
use Flarum\Core\Models\Post;
|
||||
use Closure;
|
||||
|
||||
abstract class FormatterAbstract
|
||||
{
|
||||
public function beforePurification($text, Post $post = null)
|
||||
{
|
||||
return $text;
|
||||
}
|
||||
|
||||
public function afterPurification($text, Post $post = null)
|
||||
{
|
||||
return $text;
|
||||
}
|
||||
|
||||
protected function ignoreTags($text, array $tags, Closure $callback)
|
||||
{
|
||||
$chunks = preg_split('/(<.+?>)/is', $text, 0, PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
$openTag = null;
|
||||
|
||||
for ($i = 0; $i < count($chunks); $i++) {
|
||||
if ($i % 2 === 0) { // even numbers are text
|
||||
// Only process this chunk if there are no unclosed $ignoreTags
|
||||
if (null === $openTag) {
|
||||
$chunks[$i] = $callback($chunks[$i]);
|
||||
}
|
||||
} else { // odd numbers are tags
|
||||
// Only process this tag if there are no unclosed $ignoreTags
|
||||
if (null === $openTag) {
|
||||
// Check whether this tag is contained in $ignoreTags and is not self-closing
|
||||
if (preg_match("`<(" . implode('|', $tags) . ").*(?<!/)>$`is", $chunks[$i], $matches)) {
|
||||
$openTag = $matches[1];
|
||||
}
|
||||
} else {
|
||||
// Otherwise, check whether this is the closing tag for $openTag.
|
||||
if (preg_match('`</\s*' . $openTag . '>`i', $chunks[$i], $matches)) {
|
||||
$openTag = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return implode($chunks);
|
||||
}
|
||||
}
|
10
src/Core/Formatter/FormatterInterface.php
Normal file
10
src/Core/Formatter/FormatterInterface.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php namespace Flarum\Core\Formatter;
|
||||
|
||||
use Flarum\Core\Models\Post;
|
||||
|
||||
interface FormatterInterface
|
||||
{
|
||||
public function beforePurification($text, Post $post = null);
|
||||
|
||||
public function afterPurification($text, Post $post = null);
|
||||
}
|
@@ -1,6 +1,8 @@
|
||||
<?php namespace Flarum\Core\Formatter;
|
||||
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use HTMLPurifier;
|
||||
use HTMLPurifier_Config;
|
||||
|
||||
class FormatterManager
|
||||
{
|
||||
@@ -55,20 +57,32 @@ class FormatterManager
|
||||
|
||||
public function format($text, $post = null)
|
||||
{
|
||||
$formatters = [];
|
||||
foreach ($this->getFormatters() as $formatter) {
|
||||
$text = $this->container->make($formatter)->format($text, $post);
|
||||
$formatters[] = $this->container->make($formatter);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
foreach ($formatters as $formatter) {
|
||||
$text = $formatter->beforePurification($text, $post);
|
||||
}
|
||||
|
||||
public function strip($text)
|
||||
{
|
||||
foreach ($this->getFormatters() as $formatter) {
|
||||
$formatter = $this->container->make($formatter);
|
||||
if (method_exists($formatter, 'strip')) {
|
||||
$text = $formatter->strip($text);
|
||||
}
|
||||
// Studio does not yet merge autoload_files...
|
||||
// https://github.com/franzliedke/studio/commit/4f0f4314db4ed3e36c869a5f79b855c97bdd1be7
|
||||
require __DIR__.'/../../../vendor/ezyang/htmlpurifier/library/HTMLPurifier.composer.php';
|
||||
|
||||
$config = HTMLPurifier_Config::createDefault();
|
||||
$config->set('Core.Encoding', 'UTF-8');
|
||||
$config->set('Core.EscapeInvalidTags', true);
|
||||
$config->set('HTML.Doctype', 'HTML 4.01 Strict');
|
||||
$config->set('HTML.Allowed', 'p,em,strong,a[href|title],ul,ol,li,code,pre,blockquote,h1,h2,h3,h4,h5,h6,br,hr');
|
||||
$config->set('HTML.Nofollow', true);
|
||||
|
||||
$purifier = new HTMLPurifier($config);
|
||||
|
||||
$text = $purifier->purify($text);
|
||||
|
||||
foreach ($formatters as $formatter) {
|
||||
$text = $formatter->afterPurification($text, $post);
|
||||
}
|
||||
|
||||
return $text;
|
||||
|
@@ -1,8 +1,9 @@
|
||||
<?php namespace Flarum\Core\Formatter;
|
||||
|
||||
use Flarum\Core\Models\Post;
|
||||
use Misd\Linkify\Linkify;
|
||||
|
||||
class LinkifyFormatter
|
||||
class LinkifyFormatter extends FormatterAbstract
|
||||
{
|
||||
protected $linkify;
|
||||
|
||||
@@ -11,7 +12,7 @@ class LinkifyFormatter
|
||||
$this->linkify = $linkify;
|
||||
}
|
||||
|
||||
public function format($text)
|
||||
public function beforePurification($text, Post $post = null)
|
||||
{
|
||||
return $this->linkify->process($text, ['attr' => ['target' => '_blank']]);
|
||||
}
|
||||
|
@@ -3,6 +3,8 @@
|
||||
use Flarum\Core\Repositories\UserRepositoryInterface as UserRepository;
|
||||
use Flarum\Core\Events\UserWillBeSaved;
|
||||
use Flarum\Core\Support\DispatchesEvents;
|
||||
use Flarum\Core\Exceptions\InvalidConfirmationTokenException;
|
||||
use Flarum\Core\Models\EmailToken;
|
||||
|
||||
class ConfirmEmailCommandHandler
|
||||
{
|
||||
@@ -17,10 +19,14 @@ class ConfirmEmailCommandHandler
|
||||
|
||||
public function handle($command)
|
||||
{
|
||||
$user = $this->users->findOrFail($command->userId);
|
||||
$token = EmailToken::find($command->token)->first();
|
||||
|
||||
$user->assertConfirmationTokenValid($command->token);
|
||||
$user->confirmEmail();
|
||||
if (! $token) {
|
||||
throw new InvalidConfirmationTokenException;
|
||||
}
|
||||
|
||||
$user = $token->user;
|
||||
$user->changeEmail($token->email);
|
||||
|
||||
if (! $user->is_activated) {
|
||||
$user->activate();
|
||||
@@ -31,6 +37,8 @@ class ConfirmEmailCommandHandler
|
||||
$user->save();
|
||||
$this->dispatchEventsFor($user);
|
||||
|
||||
$token->delete();
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
@@ -23,11 +23,12 @@ class EditUserCommandHandler
|
||||
$userToEdit->assertCan($user, 'edit');
|
||||
|
||||
if (isset($command->data['username'])) {
|
||||
$userToEdit->assertCan($user, 'rename');
|
||||
$userToEdit->rename($command->data['username']);
|
||||
}
|
||||
|
||||
if (isset($command->data['email'])) {
|
||||
$userToEdit->changeEmail($command->data['email']);
|
||||
$userToEdit->requestEmailChange($command->data['email']);
|
||||
}
|
||||
|
||||
if (isset($command->data['password'])) {
|
||||
|
@@ -1,10 +1,11 @@
|
||||
<?php namespace Flarum\Core\Handlers\Commands;
|
||||
|
||||
use Flarum\Core\Commands\RequestPasswordResetCommand;
|
||||
use Flarum\Core\Models\ResetToken;
|
||||
use Flarum\Core\Models\PasswordToken;
|
||||
use Flarum\Core\Repositories\UserRepositoryInterface;
|
||||
use Illuminate\Contracts\Mail\Mailer;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Flarum\Core;
|
||||
|
||||
class RequestPasswordResetCommandHandler
|
||||
{
|
||||
@@ -34,15 +35,16 @@ class RequestPasswordResetCommandHandler
|
||||
throw new ModelNotFoundException;
|
||||
}
|
||||
|
||||
$token = ResetToken::generate($user->id);
|
||||
$token = PasswordToken::generate($user->id);
|
||||
$token->save();
|
||||
|
||||
$data = [
|
||||
'username' => $user->username,
|
||||
'url' => route('flarum.forum.resetPassword', ['token' => $token->id])
|
||||
'url' => route('flarum.forum.resetPassword', ['token' => $token->id]),
|
||||
'forumTitle' => Core::config('forum_title')
|
||||
];
|
||||
|
||||
$this->mailer->send(['text' => 'flarum::emails.reset'], $data, function ($message) use ($user) {
|
||||
$this->mailer->send(['text' => 'flarum::emails.resetPassword'], $data, function ($message) use ($user) {
|
||||
$message->to($user->email);
|
||||
$message->subject('Reset Your Password');
|
||||
});
|
||||
|
@@ -1,8 +1,9 @@
|
||||
<?php namespace Flarum\Core\Handlers\Events;
|
||||
|
||||
use Config;
|
||||
use Flarum\Core\Events\UserWasRegistered;
|
||||
use Flarum\Core\Events\EmailWasChanged;
|
||||
use Flarum\Core\Events\UserEmailChangeWasRequested;
|
||||
use Flarum\Core;
|
||||
use Flarum\Core\Models\EmailToken;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Contracts\Mail\Mailer;
|
||||
|
||||
@@ -23,28 +24,47 @@ class EmailConfirmationMailer
|
||||
public function subscribe(Dispatcher $events)
|
||||
{
|
||||
$events->listen('Flarum\Core\Events\UserWasRegistered', __CLASS__.'@whenUserWasRegistered');
|
||||
$events->listen('Flarum\Core\Events\EmailWasChanged', __CLASS__.'@whenEmailWasChanged');
|
||||
$events->listen('Flarum\Core\Events\UserEmailChangeWasRequested', __CLASS__.'@whenUserEmailChangeWasRequested');
|
||||
}
|
||||
|
||||
public function whenUserWasRegistered(UserWasRegistered $event)
|
||||
{
|
||||
$user = $event->user;
|
||||
$data = $this->getPayload($user, $user->email);
|
||||
|
||||
$forumTitle = Config::get('flarum::forum_title');
|
||||
|
||||
$data = [
|
||||
'username' => $user->username,
|
||||
'forumTitle' => $forumTitle,
|
||||
'url' => route('flarum.forum.confirm', ['id' => $user->id, 'token' => $user->confirmation_token])
|
||||
];
|
||||
|
||||
$this->mailer->send(['text' => 'flarum::emails.confirm'], $data, function ($message) use ($user) {
|
||||
$this->mailer->send(['text' => 'flarum::emails.activateAccount'], $data, function ($message) use ($user) {
|
||||
$message->to($user->email);
|
||||
$message->subject('Confirm Your Email Address');
|
||||
$message->subject('Activate Your New Account');
|
||||
});
|
||||
}
|
||||
|
||||
public function whenEmailWasChanged(EmailWasChanged $event)
|
||||
public function whenUserEmailChangeWasRequested(UserEmailChangeWasRequested $event)
|
||||
{
|
||||
$email = $event->email;
|
||||
$data = $this->getPayload($event->user, $email);
|
||||
|
||||
$this->mailer->send(['text' => 'flarum::emails.confirmEmail'], $data, function ($message) use ($email) {
|
||||
$message->to($email);
|
||||
$message->subject('Confirm Your New Email Address');
|
||||
});
|
||||
}
|
||||
|
||||
protected function generateToken($user, $email)
|
||||
{
|
||||
$token = EmailToken::generate($user->id, $email);
|
||||
$token->save();
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
protected function getPayload($user, $email)
|
||||
{
|
||||
$token = $this->generateToken($user, $email);
|
||||
|
||||
return [
|
||||
'username' => $user->username,
|
||||
'url' => route('flarum.forum.confirmEmail', ['token' => $token->id]),
|
||||
'forumTitle' => Core::config('forum_title')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -16,18 +16,28 @@ class AccessToken extends Model
|
||||
*/
|
||||
public $incrementing = false;
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = ['created_at', 'expires_at'];
|
||||
|
||||
/**
|
||||
* Generate an access token for the specified user.
|
||||
*
|
||||
* @param int $userId
|
||||
* @param int $minutes
|
||||
* @return static
|
||||
*/
|
||||
public static function generate($userId)
|
||||
public static function generate($userId, $minutes = 60)
|
||||
{
|
||||
$token = new static;
|
||||
|
||||
$token->id = str_random(40);
|
||||
$token->user_id = $userId;
|
||||
$token->created_at = time();
|
||||
$token->expires_at = time() + $minutes * 60;
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
@@ -119,17 +119,6 @@ class CommentPost extends Post
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content formatter as HTML.
|
||||
*
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
public function getContentPlainAttribute()
|
||||
{
|
||||
return static::$formatter->strip($this->content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get text formatter instance.
|
||||
*
|
||||
|
@@ -295,6 +295,11 @@ class Discussion extends Model
|
||||
*/
|
||||
public function stateFor(User $user)
|
||||
{
|
||||
$loadedState = array_get($this->relations, 'state');
|
||||
if ($loadedState && $loadedState->user_id === $user->id) {
|
||||
return $loadedState;
|
||||
}
|
||||
|
||||
$state = $this->state($user)->first();
|
||||
|
||||
if (! $state) {
|
||||
|
46
src/Core/Models/EmailToken.php
Normal file
46
src/Core/Models/EmailToken.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php namespace Flarum\Core\Models;
|
||||
|
||||
class EmailToken extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'email_tokens';
|
||||
|
||||
/**
|
||||
* Use a custom primary key for this model.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $incrementing = false;
|
||||
|
||||
/**
|
||||
* Generate a reset token for the specified user.
|
||||
*
|
||||
* @param int $userId
|
||||
* @return static
|
||||
*/
|
||||
public static function generate($userId, $email)
|
||||
{
|
||||
$token = new static;
|
||||
|
||||
$token->id = str_random(40);
|
||||
$token->user_id = $userId;
|
||||
$token->email = $email;
|
||||
$token->created_at = time();
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with the owner of this reset token.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('Flarum\Core\Models\User');
|
||||
}
|
||||
}
|
@@ -110,14 +110,19 @@ class Model extends Eloquent
|
||||
*/
|
||||
public function assertValid()
|
||||
{
|
||||
$validation = $this->makeValidator();
|
||||
if ($validation->fails()) {
|
||||
throw (new ValidationFailureException)
|
||||
->setErrors($validation->errors())
|
||||
->setInput($validation->getData());
|
||||
$validator = $this->makeValidator();
|
||||
if ($validator->fails()) {
|
||||
$this->throwValidationFailureException($validator);
|
||||
}
|
||||
}
|
||||
|
||||
protected function throwValidationFailureException($validator)
|
||||
{
|
||||
throw (new ValidationFailureException)
|
||||
->setErrors($validator->errors())
|
||||
->setInput($validator->getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a new validator instance for this model.
|
||||
*
|
||||
@@ -125,9 +130,11 @@ class Model extends Eloquent
|
||||
*/
|
||||
protected function makeValidator()
|
||||
{
|
||||
$rules = $this->expandUniqueRules(static::$rules);
|
||||
$dirty = $this->getDirty();
|
||||
|
||||
return static::$validator->make($this->attributes, $rules);
|
||||
$rules = $this->expandUniqueRules(array_only(static::$rules, array_keys($dirty)));
|
||||
|
||||
return static::$validator->make($dirty, $rules);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,13 +1,13 @@
|
||||
<?php namespace Flarum\Core\Models;
|
||||
|
||||
class ResetToken extends Model
|
||||
class PasswordToken extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'reset_tokens';
|
||||
protected $table = 'password_tokens';
|
||||
|
||||
/**
|
||||
* Use a custom primary key for this model.
|
||||
@@ -28,6 +28,7 @@ class ResetToken extends Model
|
||||
|
||||
$token->id = str_random(40);
|
||||
$token->user_id = $userId;
|
||||
$token->created_at = time();
|
||||
|
||||
return $token;
|
||||
}
|
@@ -13,6 +13,7 @@ use Flarum\Core\Events\UserBioWasChanged;
|
||||
use Flarum\Core\Events\UserAvatarWasChanged;
|
||||
use Flarum\Core\Events\UserWasActivated;
|
||||
use Flarum\Core\Events\UserEmailWasConfirmed;
|
||||
use Flarum\Core\Events\UserEmailChangeWasRequested;
|
||||
|
||||
class User extends Model
|
||||
{
|
||||
@@ -31,7 +32,7 @@ class User extends Model
|
||||
* @var array
|
||||
*/
|
||||
public static $rules = [
|
||||
'username' => 'required|unique',
|
||||
'username' => 'required|alpha_dash|unique',
|
||||
'email' => 'required|email|unique',
|
||||
'password' => 'required',
|
||||
'join_time' => 'date',
|
||||
@@ -94,8 +95,6 @@ class User extends Model
|
||||
$user->password = $password;
|
||||
$user->join_time = time();
|
||||
|
||||
$user->refreshConfirmationToken();
|
||||
|
||||
$user->raise(new UserWasRegistered($user));
|
||||
|
||||
return $user;
|
||||
@@ -111,6 +110,7 @@ class User extends Model
|
||||
{
|
||||
if ($username !== $this->username) {
|
||||
$this->username = $username;
|
||||
|
||||
$this->raise(new UserWasRenamed($this));
|
||||
}
|
||||
|
||||
@@ -127,12 +127,31 @@ class User extends Model
|
||||
{
|
||||
if ($email !== $this->email) {
|
||||
$this->email = $email;
|
||||
|
||||
$this->raise(new UserEmailWasChanged($this));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function requestEmailChange($email)
|
||||
{
|
||||
if ($email !== $this->email) {
|
||||
$validator = static::$validator->make(
|
||||
compact('email'),
|
||||
$this->expandUniqueRules(array_only(static::$rules, 'email'))
|
||||
);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$this->throwValidationFailureException($validator);
|
||||
}
|
||||
|
||||
$this->raise(new UserEmailChangeWasRequested($this, $email));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the user's password.
|
||||
*
|
||||
@@ -155,7 +174,7 @@ class User extends Model
|
||||
*/
|
||||
public function setPasswordAttribute($value)
|
||||
{
|
||||
$this->attributes['password'] = $value ? static::$hasher->make($value) : null;
|
||||
$this->attributes['password'] = $value ? static::$hasher->make($value) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -257,41 +276,12 @@ class User extends Model
|
||||
public function activate()
|
||||
{
|
||||
$this->is_activated = true;
|
||||
$this->groups()->sync([3]);
|
||||
|
||||
$this->raise(new UserWasActivated($this));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given confirmation token is valid for this user.
|
||||
*
|
||||
* @param string $token
|
||||
* @return boolean
|
||||
*/
|
||||
public function assertConfirmationTokenValid($token)
|
||||
{
|
||||
if ($this->is_confirmed ||
|
||||
! $token ||
|
||||
$this->confirmation_token !== $token) {
|
||||
throw new InvalidConfirmationTokenException;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new confirmation token for the user.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function refreshConfirmationToken()
|
||||
{
|
||||
$this->is_confirmed = false;
|
||||
$this->confirmation_token = str_random(30);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm the user's email.
|
||||
*
|
||||
@@ -461,7 +451,13 @@ class User extends Model
|
||||
*/
|
||||
public function permissions()
|
||||
{
|
||||
return Permission::whereIn('group_id', array_merge([Group::GUEST_ID], $this->groups->lists('id')));
|
||||
$groupIds = [Group::GUEST_ID];
|
||||
|
||||
if ($this->is_activated) {
|
||||
$groupIds = array_merge($groupIds, [Group::MEMBER_ID], $this->groups->lists('id'));
|
||||
}
|
||||
|
||||
return Permission::whereIn('group_id', $groupIds);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
use Flarum\Core\Repositories\NotificationRepositoryInterface;
|
||||
use Flarum\Core\Models\Notification;
|
||||
use Flarum\Core\Events\NotificationWillBeSent;
|
||||
use Carbon\Carbon;
|
||||
use Closure;
|
||||
|
||||
@@ -66,6 +67,8 @@ class NotificationSyncer
|
||||
if (count($newRecipients)) {
|
||||
$now = Carbon::now('utc')->toDateTimeString();
|
||||
|
||||
event(new NotificationWillBeSent($notification, $newRecipients));
|
||||
|
||||
Notification::insert(
|
||||
array_map(function ($user) use ($attributes, $notification, $now) {
|
||||
return $attributes + ['user_id' => $user->id, 'time' => $now];
|
||||
|
@@ -3,9 +3,17 @@
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Flarum\Core\Models\Post;
|
||||
use Flarum\Core\Models\User;
|
||||
use Flarum\Core\Search\Discussions\Fulltext\DriverInterface;
|
||||
|
||||
class EloquentPostRepository implements PostRepositoryInterface
|
||||
{
|
||||
protected $fulltext;
|
||||
|
||||
public function __construct(DriverInterface $fulltext)
|
||||
{
|
||||
$this->fulltext = $fulltext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a post by ID, optionally making sure it is visible to a certain
|
||||
* user, or throw an exception.
|
||||
@@ -72,10 +80,13 @@ class EloquentPostRepository implements PostRepositoryInterface
|
||||
*/
|
||||
public function findByContent($string, User $user = null)
|
||||
{
|
||||
$query = Post::select('id', 'discussion_id')
|
||||
->where('content', 'like', '%'.$string.'%');
|
||||
// ->whereRaw('MATCH (`content`) AGAINST (? IN BOOLEAN MODE)', [$string])
|
||||
// ->orderByRaw('MATCH (`content`) AGAINST (?) DESC', [$string])
|
||||
$ids = $this->fulltext->match($string);
|
||||
|
||||
$query = Post::select('id', 'discussion_id')->whereIn('id', $ids);
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$query->orderByRaw('id != ?', [$id]);
|
||||
}
|
||||
|
||||
return $this->scopeVisibleForUser($query, $user)->get();
|
||||
}
|
||||
|
@@ -93,7 +93,7 @@ class DiscussionSearcher implements SearcherInterface
|
||||
}
|
||||
|
||||
if (in_array('relevantPosts', $load) && count($this->relevantPosts)) {
|
||||
$load = array_diff($load, ['relevantPosts']);
|
||||
$load = array_diff($load, ['relevantPosts', 'relevantPosts.discussion', 'relevantPosts.user']);
|
||||
|
||||
$postIds = [];
|
||||
foreach ($this->relevantPosts as $id => $posts) {
|
||||
@@ -104,12 +104,6 @@ class DiscussionSearcher implements SearcherInterface
|
||||
foreach ($discussions as $discussion) {
|
||||
$discussion->relevantPosts = $posts->filter(function ($post) use ($discussion) {
|
||||
return $post->discussion_id == $discussion->id;
|
||||
})
|
||||
->each(function ($post) {
|
||||
$pos = strpos(strtolower($post->content), strtolower($this->fulltext));
|
||||
// TODO: make clipping more intelligent (full words only)
|
||||
$start = max(0, $pos - 50);
|
||||
$post->content = ($start > 0 ? '...' : '').str_limit(substr($post->content, $start), 300);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
6
src/Core/Search/Discussions/Fulltext/DriverInterface.php
Normal file
6
src/Core/Search/Discussions/Fulltext/DriverInterface.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<?php namespace Flarum\Core\Search\Discussions\Fulltext;
|
||||
|
||||
interface DriverInterface
|
||||
{
|
||||
public function match($string);
|
||||
}
|
13
src/Core/Search/Discussions/Fulltext/MySqlFulltextDriver.php
Normal file
13
src/Core/Search/Discussions/Fulltext/MySqlFulltextDriver.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php namespace Flarum\Core\Search\Discussions\Fulltext;
|
||||
|
||||
use Flarum\Core\Models\Post;
|
||||
|
||||
class MySqlFulltextDriver implements DriverInterface
|
||||
{
|
||||
public function match($string)
|
||||
{
|
||||
return Post::whereRaw('MATCH (`content`) AGAINST (? IN BOOLEAN MODE)', [$string])
|
||||
->orderByRaw('MATCH (`content`) AGAINST (?) DESC', [$string])
|
||||
->lists('id');
|
||||
}
|
||||
}
|
@@ -14,12 +14,16 @@ class ConfigTableSeeder extends Seeder
|
||||
public function run()
|
||||
{
|
||||
$config = [
|
||||
'api_url' => 'http://flarum.dev/api',
|
||||
'base_url' => 'http://flarum.dev',
|
||||
'forum_title' => 'Flarum Demo Forum',
|
||||
'welcome_message' => 'Flarum is now at a point where you can have basic conversations, so here is a little demo for you to break.',
|
||||
'welcome_title' => 'Welcome to Flarum Demo Forum',
|
||||
'extensions_enabled' => '[]',
|
||||
'api_url' => 'http://flarum.dev/api',
|
||||
'base_url' => 'http://flarum.dev',
|
||||
'forum_title' => 'Flarum Demo Forum',
|
||||
'welcome_message' => 'Flarum is now at a point where you can have basic conversations, so here is a little demo for you to break.',
|
||||
'welcome_title' => 'Welcome to Flarum Demo Forum',
|
||||
'extensions_enabled' => '[]',
|
||||
'theme_primary_color' => '#536F90',
|
||||
'theme_secondary_color' => '#536F90',
|
||||
'theme_dark_mode' => false,
|
||||
'theme_colored_header' => false,
|
||||
];
|
||||
|
||||
DB::table('config')->insert(array_map(function ($key, $value) {
|
||||
|
@@ -5,16 +5,15 @@ use Flarum\Core\Commands\GenerateAccessTokenCommand;
|
||||
use Flarum\Core\Exceptions\InvalidConfirmationTokenException;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class ConfirmAction extends BaseAction
|
||||
class ConfirmEmailAction extends BaseAction
|
||||
{
|
||||
use WritesRememberCookie;
|
||||
|
||||
public function handle(Request $request, $routeParams = [])
|
||||
{
|
||||
try {
|
||||
$userId = array_get($routeParams, 'id');
|
||||
$token = array_get($routeParams, 'token');
|
||||
$command = new ConfirmEmailCommand($userId, $token);
|
||||
$command = new ConfirmEmailCommand($token);
|
||||
$user = $this->dispatch($command);
|
||||
} catch (InvalidConfirmationTokenException $e) {
|
||||
return 'Invalid confirmation token';
|
||||
@@ -23,7 +22,7 @@ class ConfirmAction extends BaseAction
|
||||
$token = $this->dispatch(new GenerateAccessTokenCommand($user->id));
|
||||
|
||||
return $this->withRememberCookie(
|
||||
$this->redirectTo(''),
|
||||
$this->redirectTo('/'),
|
||||
$token->id
|
||||
);
|
||||
// TODO: ->with('alert', ['type' => 'success', 'message' => 'Thanks for confirming!']);
|
@@ -1,13 +1,12 @@
|
||||
<?php namespace Flarum\Forum\Actions;
|
||||
|
||||
use Flarum\Api\Client;
|
||||
use Flarum\Core;
|
||||
use Flarum\Support\Actor;
|
||||
use Flarum\Support\HtmlAction;
|
||||
use Flarum\Forum\Events\RenderView;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Session;
|
||||
use Auth;
|
||||
use Config;
|
||||
use DB;
|
||||
|
||||
class IndexAction extends HtmlAction
|
||||
@@ -44,7 +43,7 @@ class IndexAction extends HtmlAction
|
||||
}
|
||||
|
||||
$view = view('flarum.forum::index')
|
||||
->with('title', Config::get('flarum::forum_title', 'Flarum Demo Forum'))
|
||||
->with('title', Core::config('forum_title'))
|
||||
->with('config', $config)
|
||||
->with('layout', 'flarum.forum::forum')
|
||||
->with('data', $data)
|
||||
@@ -57,6 +56,12 @@ class IndexAction extends HtmlAction
|
||||
$root.'/js/forum/dist/app.js',
|
||||
$root.'/less/forum/app.less'
|
||||
]);
|
||||
$assetManager->addLess('
|
||||
@fl-primary-color: '.Core::config('theme_primary_color').';
|
||||
@fl-secondary-color: '.Core::config('theme_secondary_color').';
|
||||
@fl-dark-mode: '.(Core::config('theme_dark_mode') ? 'true' : 'false').';
|
||||
@fl-colored_header: '.(Core::config('theme_colored_header') ? 'true' : 'false').';
|
||||
');
|
||||
|
||||
event(new RenderView($view, $assetManager, $this));
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<?php namespace Flarum\Forum\Actions;
|
||||
|
||||
use Flarum\Core\Models\ResetToken;
|
||||
use Flarum\Core\Models\PasswordToken;
|
||||
use Flarum\Support\HtmlAction;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
@@ -10,7 +10,7 @@ class ResetPasswordAction extends HtmlAction
|
||||
{
|
||||
$token = array_get($routeParams, 'token');
|
||||
|
||||
$token = ResetToken::findOrFail($token);
|
||||
$token = PasswordToken::findOrFail($token);
|
||||
|
||||
return view('flarum::reset')->with('token', $token->id);
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<?php namespace Flarum\Forum\Actions;
|
||||
|
||||
use Flarum\Core\Models\ResetToken;
|
||||
use Flarum\Core\Models\PasswordToken;
|
||||
use Flarum\Core\Commands\EditUserCommand;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
@@ -8,7 +8,7 @@ class SavePasswordAction extends BaseAction
|
||||
{
|
||||
public function handle(Request $request, $routeParams = [])
|
||||
{
|
||||
$token = ResetToken::findOrFail($request->getAttribute('token'));
|
||||
$token = PasswordToken::findOrFail($request->getAttribute('token'));
|
||||
|
||||
$password = $request->getAttribute('password');
|
||||
$confirmation = $request->getAttribute('password_confirmation');
|
||||
|
@@ -24,7 +24,7 @@ $router->get('/logout', 'flarum.forum.logout', $action('Flarum\Forum\Actions\Log
|
||||
|
||||
$router->post('/login', 'flarum.forum.login', $action('Flarum\Forum\Actions\LoginAction'));
|
||||
|
||||
$router->get('/confirm/{id}/{token}', 'flarum.forum.confirm', $action('Flarum\Forum\Actions\ConfirmAction'));
|
||||
$router->get('/confirm/{token}', 'flarum.forum.confirmEmail', $action('Flarum\Forum\Actions\ConfirmEmailAction'));
|
||||
|
||||
$router->get('/reset/{token}', 'flarum.forum.resetPassword', $action('Flarum\Forum\Actions\ResetPasswordAction'));
|
||||
|
||||
|
Reference in New Issue
Block a user