1
0
mirror of https://github.com/flarum/core.git synced 2025-10-14 00:15:51 +02:00

Major refactor and improvements

- Reorganised all namespaces and class names for consistency and structure. Following PSR bylaws (Abstract prefix, Interface/Trait suffix).
  - Move models into root of Core, because writing `use Flarum\Core\Discussion` is nice. Namespace the rest by type. (Namespacing by entity was too arbitrary.)
  - Moved some non-domain stuff out of Core: Database, Formatter, Settings.
  - Renamed config table and all references to "settings" for consistency.
  - Remove Core class and add url()/isInstalled()/inDebugMode() as instance methods of Foundation\Application.
  - Cleanup, docblocking, etc.

- Improvements to HTTP architecture
  - API and forum/admin Actions are now actually all the same thing (simple PSR-7 Request handlers), renamed to Controllers.
  - Upgrade to tobscure/json-api 0.2 branch.
  - Where possible, moved generic functionality to tobscure/json-api (e.g. pagination links). I'm quite happy with the backend balance now re: #262

- Improvements to other architecture
  - Use Illuminate's Auth\Access\Gate interface/implementation instead of our old Locked trait. We still use events to actually determine the permissions though. Our Policy classes are actually glorified event subscribers.
  - Extract model validation into Core\Validator classes.
  - Make post visibility permission stuff much more efficient and DRY.

- Renamed Flarum\Event classes for consistency. ref #246
  - `Configure` prefix for events dedicated to configuring an object.
  - `Get` prefix for events whose listeners should return something.
  - `Prepare` prefix when a variable is passed by reference so it can be modified.
  - `Scope` prefix when a query builder is passed.

- Miscellaneous improvements/bug-fixes. I'm easily distracted!
  - Increase default height of post composer.
  - Improve post stream redraw flickering in Safari by keying loading post placeholders with their IDs. ref #451
  - Use a PHP JavaScript minification library for minifying TextFormatter's JavaScript, instead of ClosureCompilerService (can't rely on external service!)
  - Use UrlGenerator properly in various places. closes #123
  - Make Api\Client return Response object. closes #128
  - Allow extensions to specify custom icon images.
  - Allow external API/admin URLs to be optionally specified in config.php. If the value or "url" is an array, we look for the corresponding path inside. Otherwise, we append the path to the base URL, using the corresponding value in "paths" if present. closes #244
This commit is contained in:
Toby Zerner
2015-10-08 14:28:02 +10:30
parent 8c7cdb184f
commit dd67291ce0
434 changed files with 8676 additions and 7997 deletions

View File

@@ -0,0 +1,67 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Access;
use Flarum\Event\GetPermission;
use Flarum\Event\ScopeModelVisibility;
use Illuminate\Contracts\Events\Dispatcher;
abstract class AbstractPolicy
{
/**
* @var string
*/
protected $model;
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(GetPermission::class, [$this, 'getPermission']);
$events->listen(ScopeModelVisibility::class, [$this, 'scopeModelVisibility']);
}
/**
* @param GetPermission $event
* @return bool|null
*/
public function getPermission(GetPermission $event)
{
if (isset($event->arguments[0]) && $event->arguments[0] instanceof $this->model) {
if (method_exists($this, 'before')) {
$arguments = array_merge([$event->actor, $event->ability], $event->arguments);
$result = call_user_func_array([$this, 'before'], $arguments);
if (! is_null($result)) {
return $result;
}
}
if (method_exists($this, $event->ability)) {
$arguments = array_merge([$event->actor], $event->arguments);
return call_user_func_array([$this, $event->ability], $arguments);
}
}
}
/**
* @param ScopeModelVisibility $event
*/
public function scopeModelVisibility(ScopeModelVisibility $event)
{
if ($event->model instanceof $this->model && method_exists($this, 'find')) {
call_user_func_array([$this, 'find'], [$event->actor, $event->query]);
}
}
}

View File

@@ -0,0 +1,66 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Access;
use Flarum\Core\Exception\PermissionDeniedException;
use Flarum\Core\User;
trait AssertPermissionTrait
{
/**
* @param $condition
* @throws PermissionDeniedException
*/
protected function assertPermission($condition)
{
if (! $condition) {
throw new PermissionDeniedException;
}
}
/**
* @param User $actor
* @param string $ability
* @param mixed $arguments
* @throws PermissionDeniedException
*/
protected function assertCan(User $actor, $ability, $arguments = [])
{
$this->assertPermission($actor->can($ability, $arguments));
}
/**
* @param User $actor
* @throws PermissionDeniedException
*/
protected function assertGuest(User $actor)
{
$this->assertPermission($actor->isGuest());
}
/**
* @param User $actor
* @throws PermissionDeniedException
*/
protected function assertRegistered(User $actor)
{
$this->assertPermission(! $actor->isGuest());
}
/**
* @param User $actor
* @throws PermissionDeniedException
*/
protected function assertAdmin(User $actor)
{
$this->assertPermission($actor->isAdmin());
}
}

View File

@@ -0,0 +1,112 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Access;
use Carbon\Carbon;
use Flarum\Core\Discussion;
use Flarum\Core\User;
use Flarum\Event\ScopeHiddenDiscussionVisibility;
use Flarum\Settings\SettingsRepository;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
class DiscussionPolicy extends AbstractPolicy
{
/**
* {@inheritdoc}
*/
protected $model = Discussion::class;
/**
* @var SettingsRepository
*/
protected $settings;
/**
* @var Gate
*/
protected $gate;
/**
* @var Dispatcher
*/
protected $events;
/**
* @param SettingsRepository $settings
* @param Gate $gate
*/
public function __construct(SettingsRepository $settings, Gate $gate, Dispatcher $events)
{
$this->settings = $settings;
$this->gate = $gate;
$this->events = $events;
}
/**
* @param User $actor
* @param string $ability
* @return bool|null
*/
public function before(User $actor, $ability)
{
if ($actor->hasPermission('discussion.'.$ability)) {
return true;
}
}
/**
* @param User $actor
* @param Builder $query
*/
public function find(User $actor, Builder $query)
{
if (! $actor->hasPermission('discussion.hide')) {
$query->where(function ($query) use ($actor) {
$query->whereNull('discussions.hide_time')
->where('comments_count', '>', 0)
->orWhere('start_user_id', $actor->id);
$this->events->fire(
new ScopeHiddenDiscussionVisibility($query, $actor, 'discussion.hide')
);
});
}
}
/**
* @param User $actor
* @param Discussion $discussion
* @return bool|null
*/
public function rename(User $actor, Discussion $discussion)
{
if ($discussion->start_user_id == $actor->id) {
$allowRenaming = $this->settings->get('allow_renaming');
if ($allowRenaming === '-1'
|| ($allowRenaming === 'reply' && $discussion->participants_count <= 1)
|| ($discussion->start_time->diffInMinutes(new Carbon) < $allowRenaming)) {
return true;
}
}
}
/**
* @param User $actor
* @param Discussion $discussion
* @return bool|null
*/
public function delete(User $actor, Discussion $discussion)
{
return $this->rename($actor, $discussion);
}
}

345
src/Core/Access/Gate.php Normal file
View File

@@ -0,0 +1,345 @@
<?php
namespace Flarum\Core\Access;
use InvalidArgumentException;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
/**
* @author Taylor Otwell
*/
class Gate implements GateContract
{
/**
* The container instance.
*
* @var Container
*/
protected $container;
/**
* The user resolver callable.
*
* @var callable
*/
protected $userResolver;
/**
* All of the defined abilities.
*
* @var array
*/
protected $abilities = [];
/**
* All of the defined policies.
*
* @var array
*/
protected $policies = [];
/**
* All of the registered before callbacks.
*
* @var array
*/
protected $beforeCallbacks = [];
/**
* Create a new gate instance.
*
* @param Container $container
* @param callable $userResolver
* @param array $abilities
* @param array $policies
* @param array $beforeCallbacks
* @return void
*/
public function __construct(Container $container, callable $userResolver, array $abilities = [], array $policies = [], array $beforeCallbacks = [])
{
$this->policies = $policies;
$this->container = $container;
$this->abilities = $abilities;
$this->userResolver = $userResolver;
$this->beforeCallbacks = $beforeCallbacks;
}
/**
* Determine if a given ability has been defined.
*
* @param string $ability
* @return bool
*/
public function has($ability)
{
return isset($this->abilities[$ability]);
}
/**
* Define a new ability.
*
* @param string $ability
* @param callable|string $callback
* @return $this
*
* @throws \InvalidArgumentException
*/
public function define($ability, $callback)
{
if (is_callable($callback)) {
$this->abilities[$ability] = $callback;
} elseif (is_string($callback) && str_contains($callback, '@')) {
$this->abilities[$ability] = $this->buildAbilityCallback($callback);
} else {
throw new InvalidArgumentException("Callback must be a callable or a 'Class@method' string.");
}
return $this;
}
/**
* Create the ability callback for a callback string.
*
* @param string $callback
* @return \Closure
*/
protected function buildAbilityCallback($callback)
{
return function () use ($callback) {
list($class, $method) = explode('@', $callback);
return call_user_func_array([$this->resolvePolicy($class), $method], func_get_args());
};
}
/**
* Define a policy class for a given class type.
*
* @param string $class
* @param string $policy
* @return $this
*/
public function policy($class, $policy)
{
$this->policies[$class] = $policy;
return $this;
}
/**
* Register a callback to run before all Gate checks.
*
* @param callable $callback
* @return $this
*/
public function before(callable $callback)
{
$this->beforeCallbacks[] = $callback;
return $this;
}
/**
* Determine if the given ability should be granted for the current user.
*
* @param string $ability
* @param array|mixed $arguments
* @return bool
*/
public function allows($ability, $arguments = [])
{
return $this->check($ability, $arguments);
}
/**
* Determine if the given ability should be denied for the current user.
*
* @param string $ability
* @param array|mixed $arguments
* @return bool
*/
public function denies($ability, $arguments = [])
{
return ! $this->allows($ability, $arguments);
}
/**
* Determine if the given ability should be granted for the current user.
*
* @param string $ability
* @param array|mixed $arguments
* @return bool
*/
public function check($ability, $arguments = [])
{
if (! $user = $this->resolveUser()) {
return false;
}
$arguments = is_array($arguments) ? $arguments : [$arguments];
if (! is_null($result = $this->callBeforeCallbacks($user, $ability, $arguments))) {
return $result;
}
$callback = $this->resolveAuthCallback(
$user, $ability, $arguments
);
return call_user_func_array($callback, array_merge([$user], $arguments));
}
/**
* Call all of the before callbacks and return if a result is given.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param string $ability
* @param array $arguments
* @return bool|void
*/
protected function callBeforeCallbacks($user, $ability, array $arguments)
{
$arguments = array_merge([$user, $ability], $arguments);
foreach ($this->beforeCallbacks as $before) {
if (! is_null($result = call_user_func_array($before, $arguments))) {
return $result;
}
}
}
/**
* Resolve the callable for the given ability and arguments.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param string $ability
* @param array $arguments
* @return callable
*/
protected function resolveAuthCallback($user, $ability, array $arguments)
{
if ($this->firstArgumentCorrespondsToPolicy($arguments)) {
return $this->resolvePolicyCallback($user, $ability, $arguments);
} elseif (isset($this->abilities[$ability])) {
return $this->abilities[$ability];
} else {
return function () { return false; };
}
}
/**
* Determine if the first argument in the array corresponds to a policy.
*
* @param array $arguments
* @return bool
*/
protected function firstArgumentCorrespondsToPolicy(array $arguments)
{
if (! isset($arguments[0])) {
return false;
}
if (is_object($arguments[0])) {
return isset($this->policies[get_class($arguments[0])]);
}
return is_string($arguments[0]) && isset($this->policies[$arguments[0]]);
}
/**
* Resolve the callback for a policy check.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param string $ability
* @param array $arguments
* @return callable
*/
protected function resolvePolicyCallback($user, $ability, array $arguments)
{
return function () use ($user, $ability, $arguments) {
$instance = $this->getPolicyFor($arguments[0]);
if (method_exists($instance, 'before')) {
// We will prepend the user and ability onto the arguments so that the before
// callback can determine which ability is being called. Then we will call
// into the policy before methods with the arguments and get the result.
$beforeArguments = array_merge([$user, $ability], $arguments);
$result = call_user_func_array(
[$instance, 'before'], $beforeArguments
);
// If we recieved a non-null result from the before method, we will return it
// as the result of a check. This allows developers to override the checks
// in the policy and return a result for all rules defined in the class.
if (! is_null($result)) {
return $result;
}
}
if (! is_callable([$instance, $ability])) {
return false;
}
return call_user_func_array(
[$instance, $ability], array_merge([$user], $arguments)
);
};
}
/**
* Get a policy instance for a given class.
*
* @param object|string $class
* @return mixed
*
* @throws \InvalidArgumentException
*/
public function getPolicyFor($class)
{
if (is_object($class)) {
$class = get_class($class);
}
if (! isset($this->policies[$class])) {
throw new InvalidArgumentException("Policy not defined for [{$class}].");
}
return $this->resolvePolicy($this->policies[$class]);
}
/**
* Build a policy class instance of the given type.
*
* @param object|string $class
* @return mixed
*/
public function resolvePolicy($class)
{
return $this->container->make($class);
}
/**
* Get a guard instance for the given user.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|mixed $user
* @return static
*/
public function forUser($user)
{
return new static(
$this->container, function () use ($user) { return $user; }, $this->abilities, $this->policies, $this->beforeCallbacks
);
}
/**
* Resolve the user from the user resolver.
*
* @return mixed
*/
protected function resolveUser()
{
return call_user_func($this->userResolver);
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Access;
use Flarum\Core\Group;
use Flarum\Core\User;
class GroupPolicy extends AbstractPolicy
{
/**
* {@inheritdoc}
*/
protected $model = Group::class;
/**
* @param User $actor
* @param string $ability
* @return bool|null
*/
public function before(User $actor, $ability)
{
if ($actor->hasPermission('group.'.$ability)) {
return true;
}
}
}

View File

@@ -0,0 +1,121 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Access;
use Carbon\Carbon;
use Flarum\Core\Post;
use Flarum\Core\User;
use Flarum\Event\ScopePostVisibility;
use Flarum\Settings\SettingsRepository;
use Illuminate\Contracts\Events\Dispatcher;
class PostPolicy extends AbstractPolicy
{
/**
* {@inheritdoc}
*/
protected $model = Post::class;
/**
* @var SettingsRepository
*/
protected $settings;
/**
* @var Gate
*/
protected $gate;
/**
* @param SettingsRepository $settings
* @param Gate $gate
*/
public function __construct(SettingsRepository $settings, Gate $gate)
{
$this->settings = $settings;
$this->gate = $gate;
}
/**
* @inheritDoc
*/
public function subscribe(Dispatcher $events)
{
parent::subscribe($events);
$events->listen(ScopePostVisibility::class, [$this, 'scopePostVisibility']);
}
/**
* @param User $actor
* @param string $ability
* @param Post $post
* @return bool|null
*/
public function before(User $actor, $ability, Post $post)
{
if ($this->discussionAllows($actor, $ability, $post)) {
return true;
}
}
/**
* @param ScopePostVisibility $event
*/
public function scopePostVisibility(ScopePostVisibility $event)
{
// When fetching a discussion's posts: if the user doesn't have permission
// to moderate the discussion, then they can't see posts that have been
// hidden by someone other than themself.
if ($this->gate->forUser($event->actor)->denies('editPosts', $event->discussion)) {
$event->query->where(function ($query) use ($event) {
$query->whereNull('hide_time')
->orWhere('user_id', $event->actor->id);
});
}
}
/**
* @param User $actor
* @param Post $post
* @return bool|null
*/
public function edit(User $actor, Post $post)
{
if ($this->discussionAllows($actor, 'edit', $post)) {
return true;
}
// A post is allowed to be edited if the user has permission to moderate
// the discussion which it's in, or if they are the author and the post
// hasn't been deleted by someone else.
if ($post->user_id == $actor->id && (! $post->hide_time || $post->hide_user_id == $actor->id)) {
$allowEditing = $this->settings->get('allow_post_editing');
if ($allowEditing === '-1'
|| ($allowEditing === 'reply' && $post->number >= $post->discussion->last_post_number)
|| ($post->time->diffInMinutes(new Carbon) < $allowEditing)) {
return true;
}
}
}
/**
* @param User $actor
* @param string $ability
* @param Post $post
* @return bool
*/
protected function discussionAllows(User $actor, $ability, Post $post)
{
return $this->gate->forUser($actor)->allows($ability.'Posts', $post->discussion);
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Access;
use Flarum\Core\User;
class UserPolicy extends AbstractPolicy
{
/**
* {@inheritdoc}
*/
protected $model = User::class;
/**
* @param User $actor
* @param string $ability
* @return bool|null
*/
public function before(User $actor, $ability)
{
if ($actor->hasPermission('user.'.$ability)) {
return true;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -8,16 +8,16 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users;
namespace Flarum\Core;
use Flarum\Core\Model;
use Flarum\Core\Exceptions\InvalidConfirmationTokenException;
use Flarum\Database\AbstractModel;
use Flarum\Core\Exception\InvalidConfirmationTokenException;
use DateTime;
/**
* @todo document database columns with @property
*/
class AuthToken extends Model
class AuthToken extends AbstractModel
{
/**
* {@inheritdoc}

View File

@@ -8,7 +8,7 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users\Commands;
namespace Flarum\Core\Command;
class ConfirmEmail
{

View File

@@ -8,17 +8,16 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Users\UserRepository;
use Flarum\Events\UserWillBeSaved;
use Flarum\Core\Support\DispatchesEvents;
use Flarum\Core\Users\EmailToken;
use DateTime;
use Flarum\Core\EmailToken;
use Flarum\Core\Repository\UserRepository;
use Flarum\Core\Support\DispatchEventsTrait;
use Illuminate\Contracts\Events\Dispatcher;
class ConfirmEmailHandler
{
use DispatchesEvents;
use DispatchEventsTrait;
/**
* @var UserRepository
@@ -28,20 +27,19 @@ class ConfirmEmailHandler
/**
* @param UserRepository $users
*/
public function __construct(UserRepository $users)
public function __construct(Dispatcher $events, UserRepository $users)
{
$this->events = $events;
$this->users = $users;
}
/**
* @param ConfirmEmail $command
*
* @throws InvalidConfirmationTokenException
*
* @return \Flarum\Core\Users\User
* @return \Flarum\Core\User
*/
public function handle(ConfirmEmail $command)
{
/** @var EmailToken $token */
$token = EmailToken::validOrFail($command->token);
$user = $token->user;

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Groups\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Users\User;
use Flarum\Core\User;
class CreateGroup
{

View File

@@ -8,40 +8,39 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Groups\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Groups\Group;
use Flarum\Core\Forum;
use Flarum\Events\GroupWillBeSaved;
use Flarum\Core\Support\DispatchesEvents;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Core\Exception\PermissionDeniedException;
use Flarum\Core\Group;
use Flarum\Event\GroupWillBeSaved;
use Flarum\Core\Support\DispatchEventsTrait;
use Illuminate\Contracts\Events\Dispatcher;
class CreateGroupHandler
{
use DispatchesEvents;
use DispatchEventsTrait;
use AssertPermissionTrait;
/**
* @var Forum
* @param Dispatcher $events
*/
protected $forum;
/**
* @param Forum $forum
*/
public function __construct(Forum $forum)
public function __construct(Dispatcher $events)
{
$this->forum = $forum;
$this->events = $events;
}
/**
* @param CreateGroup $command
* @return Group
* @throws PermissionDeniedException
*/
public function handle(CreateGroup $command)
{
$actor = $command->actor;
$data = $command->data;
$this->forum->assertCan($actor, 'createGroup');
$this->assertCan($actor, 'createGroup');
$group = Group::build(
array_get($data, 'attributes.nameSingular'),
@@ -50,10 +49,13 @@ class CreateGroupHandler
array_get($data, 'attributes.icon')
);
event(new GroupWillBeSaved($group, $actor, $data));
$this->events->fire(
new GroupWillBeSaved($group, $actor, $data)
);
$group->save();
$this->dispatchEventsFor($group);
$this->dispatchEventsFor($group, $actor);
return $group;
}

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Users\User;
use Flarum\Core\User;
class DeleteAvatar
{

View File

@@ -8,16 +8,20 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users\Commands;
namespace Flarum\Core\Command;
use Flarum\Events\AvatarWillBeDeleted;
use Flarum\Core\Users\UserRepository;
use Flarum\Core\Support\DispatchesEvents;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Core\Exception\PermissionDeniedException;
use Flarum\Event\AvatarWillBeDeleted;
use Flarum\Core\Repository\UserRepository;
use Flarum\Core\Support\DispatchEventsTrait;
use Illuminate\Contracts\Events\Dispatcher;
use League\Flysystem\FilesystemInterface;
class DeleteAvatarHandler
{
use DispatchesEvents;
use DispatchEventsTrait;
use AssertPermissionTrait;
/**
* @var UserRepository
@@ -30,18 +34,21 @@ class DeleteAvatarHandler
protected $uploadDir;
/**
* @param Dispatcher $events
* @param UserRepository $users
* @param FilesystemInterface $uploadDir
*/
public function __construct(UserRepository $users, FilesystemInterface $uploadDir)
public function __construct(Dispatcher $events, UserRepository $users, FilesystemInterface $uploadDir)
{
$this->events = $events;
$this->users = $users;
$this->uploadDir = $uploadDir;
}
/**
* @param DeleteAvatar $command
* @return \Flarum\Core\Users\User
* @return \Flarum\Core\User
* @throws PermissionDeniedException
*/
public function handle(DeleteAvatar $command)
{
@@ -49,24 +56,24 @@ class DeleteAvatarHandler
$user = $this->users->findOrFail($command->userId);
// Make sure the current user is allowed to edit the user profile.
// This will let admins and the user themselves pass through, and
// throw an exception otherwise.
if ($actor->id !== $user->id) {
$user->assertCan($actor, 'edit');
$this->assertCan($actor, 'edit', $user);
}
$avatarPath = $user->avatar_path;
$user->changeAvatarPath(null);
event(new AvatarWillBeDeleted($user, $actor));
$this->events->fire(
new AvatarWillBeDeleted($user, $actor)
);
$user->save();
if ($this->uploadDir->has($avatarPath)) {
$this->uploadDir->delete($avatarPath);
}
$user->save();
$this->dispatchEventsFor($user);
$this->dispatchEventsFor($user, $actor);
return $user;
}

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Discussions\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Users\User;
use Flarum\Core\User;
class DeleteDiscussion
{

View File

@@ -0,0 +1,63 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Command;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Core\Exception\PermissionDeniedException;
use Flarum\Core\Repository\DiscussionRepository;
use Flarum\Core\Support\DispatchEventsTrait;
use Flarum\Event\DiscussionWillBeDeleted;
use Illuminate\Contracts\Events\Dispatcher;
class DeleteDiscussionHandler
{
use DispatchEventsTrait;
use AssertPermissionTrait;
/**
* @var DiscussionRepository
*/
protected $discussions;
/**
* @param Dispatcher $events
* @param DiscussionRepository $discussions
*/
public function __construct(Dispatcher $events, DiscussionRepository $discussions)
{
$this->events = $events;
$this->discussions = $discussions;
}
/**
* @param DeleteDiscussion $command
* @return \Flarum\Core\Discussion
* @throws PermissionDeniedException
*/
public function handle(DeleteDiscussion $command)
{
$actor = $command->actor;
$discussion = $this->discussions->findOrFail($command->discussionId, $actor);
$this->assertCan($actor, 'delete', $discussion);
$this->events->fire(
new DiscussionWillBeDeleted($discussion, $actor, $command->data)
);
$discussion->delete();
$this->dispatchEventsFor($discussion, $actor);
return $discussion;
}
}

View File

@@ -8,10 +8,10 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Groups\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Groups\Group;
use Flarum\Core\Users\User;
use Flarum\Core\Group;
use Flarum\Core\User;
class DeleteGroup
{

View File

@@ -0,0 +1,62 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Command;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Core\Exception\PermissionDeniedException;
use Flarum\Core\Repository\GroupRepository;
use Flarum\Event\GroupWillBeDeleted;
use Flarum\Core\Support\DispatchEventsTrait;
use Illuminate\Contracts\Events\Dispatcher;
class DeleteGroupHandler
{
use DispatchEventsTrait;
use AssertPermissionTrait;
/**
* @var GroupRepository
*/
protected $groups;
/**
* @param GroupRepository $groups
*/
public function __construct(Dispatcher $events, GroupRepository $groups)
{
$this->groups = $groups;
$this->events = $events;
}
/**
* @param DeleteGroup $command
* @return \Flarum\Core\Group
* @throws PermissionDeniedException
*/
public function handle(DeleteGroup $command)
{
$actor = $command->actor;
$group = $this->groups->findOrFail($command->groupId, $actor);
$this->assertCan($actor, 'delete', $group);
$this->events->fire(
new GroupWillBeDeleted($group, $actor, $command->data)
);
$group->delete();
$this->dispatchEventsFor($group, $actor);
return $group;
}
}

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Posts\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Users\User;
use Flarum\Core\User;
class DeletePost
{

View File

@@ -0,0 +1,63 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Command;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Core\Exception\PermissionDeniedException;
use Flarum\Core\Repository\PostRepository;
use Flarum\Event\PostWillBeDeleted;
use Flarum\Core\Support\DispatchEventsTrait;
use Illuminate\Contracts\Events\Dispatcher;
class DeletePostHandler
{
use DispatchEventsTrait;
use AssertPermissionTrait;
/**
* @var PostRepository
*/
protected $posts;
/**
* @param Dispatcher $events
* @param PostRepository $posts
*/
public function __construct(Dispatcher $events, PostRepository $posts)
{
$this->events = $events;
$this->posts = $posts;
}
/**
* @param DeletePost $command
* @return \Flarum\Core\Post
* @throws PermissionDeniedException
*/
public function handle(DeletePost $command)
{
$actor = $command->actor;
$post = $this->posts->findOrFail($command->postId, $actor);
$this->assertCan($actor, 'delete', $post);
$this->events->fire(
new PostWillBeDeleted($post, $actor, $command->data)
);
$post->delete();
$this->dispatchEventsFor($post, $actor);
return $post;
}
}

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Users\User;
use Flarum\Core\User;
class DeleteUser
{

View File

@@ -0,0 +1,62 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Command;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Core\Exception\PermissionDeniedException;
use Flarum\Core\Repository\UserRepository;
use Flarum\Event\UserWillBeDeleted;
use Flarum\Core\Support\DispatchEventsTrait;
use Illuminate\Contracts\Events\Dispatcher;
class DeleteUserHandler
{
use DispatchEventsTrait;
use AssertPermissionTrait;
/**
* @var UserRepository
*/
protected $users;
/**
* @param Dispatcher $events
* @param UserRepository $users
*/
public function __construct(Dispatcher $events, UserRepository $users)
{
$this->events = $events;
$this->users = $users;
}
/**
* @param DeleteUser $command
* @return \Flarum\Core\User
* @throws PermissionDeniedException
*/
public function handle(DeleteUser $command)
{
$actor = $command->actor;
$user = $this->users->findOrFail($command->userId, $actor);
$this->assertCan($actor, 'delete', $user);
$this->events->fire(
new UserWillBeDeleted($user, $actor, $command->data)
);
$user->delete();
$this->dispatchEventsFor($user, $actor);
return $user;
}
}

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Discussions\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Users\User;
use Flarum\Core\User;
class EditDiscussion
{
@@ -24,7 +24,7 @@ class EditDiscussion
/**
* The user performing the action.
*
* @var \Flarum\Core\Users\User
* @var \Flarum\Core\User
*/
public $actor;
@@ -37,7 +37,7 @@ class EditDiscussion
/**
* @param integer $discussionId The ID of the discussion to edit.
* @param \Flarum\Core\Users\User $actor The user performing the action.
* @param \Flarum\Core\User $actor The user performing the action.
* @param array $data The attributes to update on the discussion.
*/
public function __construct($discussionId, User $actor, array $data)

View File

@@ -8,15 +8,19 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Discussions\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Discussions\DiscussionRepository;
use Flarum\Events\DiscussionWillBeSaved;
use Flarum\Core\Support\DispatchesEvents;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Core\Exception\PermissionDeniedException;
use Flarum\Core\Repository\DiscussionRepository;
use Flarum\Core\Support\DispatchEventsTrait;
use Flarum\Event\DiscussionWillBeSaved;
use Illuminate\Contracts\Events\Dispatcher;
class EditDiscussionHandler
{
use DispatchesEvents;
use DispatchEventsTrait;
use AssertPermissionTrait;
/**
* @var DiscussionRepository
@@ -24,17 +28,19 @@ class EditDiscussionHandler
protected $discussions;
/**
* @param Dispatcher $events
* @param DiscussionRepository $discussions
*/
public function __construct(DiscussionRepository $discussions)
public function __construct(Dispatcher $events, DiscussionRepository $discussions)
{
$this->events = $events;
$this->discussions = $discussions;
}
/**
* @param EditDiscussion $command
* @return \Flarum\Core\Discussions\Discussion
* @throws \Flarum\Core\Exceptions\PermissionDeniedException
* @return \Flarum\Core\Discussion
* @throws PermissionDeniedException
*/
public function handle(EditDiscussion $command)
{
@@ -45,12 +51,13 @@ class EditDiscussionHandler
$discussion = $this->discussions->findOrFail($command->discussionId, $actor);
if (isset($attributes['title'])) {
$discussion->assertCan($actor, 'rename');
$discussion->rename($attributes['title'], $actor);
$this->assertCan($actor, 'rename', $discussion);
$discussion->rename($attributes['title']);
}
if (isset($attributes['isHidden'])) {
$discussion->assertCan($actor, 'hide');
$this->assertCan($actor, 'hide', $discussion);
if ($attributes['isHidden']) {
$discussion->hide($actor);
@@ -59,11 +66,13 @@ class EditDiscussionHandler
}
}
event(new DiscussionWillBeSaved($discussion, $actor, $data));
$this->events->fire(
new DiscussionWillBeSaved($discussion, $actor, $data)
);
$discussion->save();
$this->dispatchEventsFor($discussion);
$this->dispatchEventsFor($discussion, $actor);
return $discussion;
}

View File

@@ -8,10 +8,10 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Groups\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Groups\Group;
use Flarum\Core\Users\User;
use Flarum\Core\Group;
use Flarum\Core\User;
class EditGroup
{

View File

@@ -8,16 +8,20 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Groups\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Groups\Group;
use Flarum\Core\Groups\GroupRepository;
use Flarum\Events\GroupWillBeSaved;
use Flarum\Core\Support\DispatchesEvents;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Core\Exception\PermissionDeniedException;
use Flarum\Core\Group;
use Flarum\Core\Repository\GroupRepository;
use Flarum\Event\GroupWillBeSaved;
use Flarum\Core\Support\DispatchEventsTrait;
use Illuminate\Contracts\Events\Dispatcher;
class EditGroupHandler
{
use DispatchesEvents;
use DispatchEventsTrait;
use AssertPermissionTrait;
/**
* @var GroupRepository
@@ -25,17 +29,19 @@ class EditGroupHandler
protected $groups;
/**
* @param Dispatcher $events
* @param GroupRepository $groups
*/
public function __construct(GroupRepository $groups)
public function __construct(Dispatcher $events, GroupRepository $groups)
{
$this->events = $events;
$this->groups = $groups;
}
/**
* @param EditGroup $command
* @return Group
* @throws \Flarum\Core\Exceptions\PermissionDeniedException
* @throws PermissionDeniedException
*/
public function handle(EditGroup $command)
{
@@ -44,7 +50,7 @@ class EditGroupHandler
$group = $this->groups->findOrFail($command->groupId, $actor);
$group->assertCan($actor, 'edit');
$this->assertCan($actor, 'edit', $group);
$attributes = array_get($data, 'attributes', []);
@@ -60,10 +66,13 @@ class EditGroupHandler
$group->icon = $attributes['icon'];
}
event(new GroupWillBeSaved($group, $actor, $data));
$this->events->fire(
new GroupWillBeSaved($group, $actor, $data)
);
$group->save();
$this->dispatchEventsFor($group);
$this->dispatchEventsFor($group, $actor);
return $group;
}

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Posts\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Users\User;
use Flarum\Core\User;
class EditPost
{

View File

@@ -8,16 +8,19 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Posts\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Posts\PostRepository;
use Flarum\Events\PostWillBeSaved;
use Flarum\Core\Support\DispatchesEvents;
use Flarum\Core\Posts\CommentPost;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Core\Repository\PostRepository;
use Flarum\Event\PostWillBeSaved;
use Flarum\Core\Support\DispatchEventsTrait;
use Flarum\Core\Post\CommentPost;
use Illuminate\Contracts\Events\Dispatcher;
class EditPostHandler
{
use DispatchesEvents;
use DispatchEventsTrait;
use AssertPermissionTrait;
/**
* @var PostRepository
@@ -25,17 +28,19 @@ class EditPostHandler
protected $posts;
/**
* @param Dispatcher $events
* @param PostRepository $posts
*/
public function __construct(PostRepository $posts)
public function __construct(Dispatcher $events, PostRepository $posts)
{
$this->events = $events;
$this->posts = $posts;
}
/**
* @param EditPost $command
* @return \Flarum\Core\Posts\Post
* @throws \Flarum\Core\Exceptions\PermissionDeniedException
* @return \Flarum\Core\Post
* @throws \Flarum\Core\Exception\PermissionDeniedException
*/
public function handle(EditPost $command)
{
@@ -48,13 +53,13 @@ class EditPostHandler
$attributes = array_get($data, 'attributes', []);
if (isset($attributes['content'])) {
$post->assertCan($actor, 'edit');
$this->assertCan($actor, 'edit', $post);
$post->revise($attributes['content'], $actor);
}
if (isset($attributes['isHidden'])) {
$post->assertCan($actor, 'edit');
$this->assertCan($actor, 'edit', $post);
if ($attributes['isHidden']) {
$post->hide($actor);
@@ -64,11 +69,13 @@ class EditPostHandler
}
}
event(new PostWillBeSaved($post, $actor, $data));
$this->events->fire(
new PostWillBeSaved($post, $actor, $data)
);
$post->save();
$this->dispatchEventsFor($post);
$this->dispatchEventsFor($post, $actor);
return $post;
}

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Users\User;
use Flarum\Core\User;
class EditUser
{

View File

@@ -8,18 +8,20 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Users\User;
use Flarum\Core\Users\UserRepository;
use Flarum\Events\UserWillBeSaved;
use Flarum\Events\UserGroupsWereChanged;
use Flarum\Core\Support\DispatchesEvents;
use Flarum\Core\Exceptions\PermissionDeniedException;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Core\User;
use Flarum\Core\Repository\UserRepository;
use Flarum\Event\UserWillBeSaved;
use Flarum\Event\UserGroupsWereChanged;
use Flarum\Core\Support\DispatchEventsTrait;
use Illuminate\Contracts\Events\Dispatcher;
class EditUserHandler
{
use DispatchesEvents;
use DispatchEventsTrait;
use AssertPermissionTrait;
/**
* @var UserRepository
@@ -27,17 +29,19 @@ class EditUserHandler
protected $users;
/**
* @param Dispatcher $events
* @param UserRepository $users
*/
public function __construct(UserRepository $users)
public function __construct(Dispatcher $events, UserRepository $users)
{
$this->events = $events;
$this->users = $users;
}
/**
* @param EditUser $command
* @return User
* @throws \Flarum\Core\Exceptions\PermissionDeniedException
* @throws \Flarum\Core\Exception\PermissionDeniedException
*/
public function handle(EditUser $command)
{
@@ -45,13 +49,15 @@ class EditUserHandler
$data = $command->data;
$user = $this->users->findOrFail($command->userId, $actor);
$canEdit = $actor->can('edit', $user);
$isSelf = $actor->id === $user->id;
$attributes = array_get($data, 'attributes', []);
$relationships = array_get($data, 'relationships', []);
if (isset($attributes['username'])) {
$user->assertCan($actor, 'edit');
$this->assertPermission($canEdit);
$user->rename($attributes['username']);
}
@@ -59,31 +65,31 @@ class EditUserHandler
if ($isSelf) {
$user->requestEmailChange($attributes['email']);
} else {
$user->assertCan($actor, 'edit');
$this->assertPermission($canEdit);
$user->changeEmail($attributes['email']);
}
}
if (isset($attributes['password'])) {
$user->assertCan($actor, 'edit');
$this->assertPermission($canEdit);
$user->changePassword($attributes['password']);
}
if (isset($attributes['bio'])) {
if (! $isSelf) {
$user->assertCan($actor, 'edit');
$this->assertPermission($canEdit);
}
$user->changeBio($attributes['bio']);
}
if (! empty($attributes['readTime'])) {
$this->assert($isSelf);
$this->assertPermission($isSelf);
$user->markAllAsRead();
}
if (! empty($attributes['preferences'])) {
$this->assert($isSelf);
$this->assertPermission($isSelf);
foreach ($attributes['preferences'] as $k => $v) {
$user->setPreference($k, $v);
@@ -91,7 +97,7 @@ class EditUserHandler
}
if (isset($relationships['groups']['data']) && is_array($relationships['groups']['data'])) {
$user->assertCan($actor, 'edit');
$this->assertPermission($canEdit);
$newGroupIds = [];
foreach ($relationships['groups']['data'] as $group) {
@@ -100,25 +106,23 @@ class EditUserHandler
}
}
$user->raise(new UserGroupsWereChanged($user, $user->groups()->get()->all()));
$user->raise(
new UserGroupsWereChanged($user, $user->groups()->get()->all())
);
User::saved(function ($user) use ($newGroupIds) {
$user->afterSave(function (User $user) use ($newGroupIds) {
$user->groups()->sync($newGroupIds);
});
}
event(new UserWillBeSaved($user, $actor, $data));
$this->events->fire(
new UserWillBeSaved($user, $actor, $data)
);
$user->save();
$this->dispatchEventsFor($user);
$this->dispatchEventsFor($user, $actor);
return $user;
}
protected function assert($true)
{
if (! $true) {
throw new PermissionDeniedException;
}
}
}

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Posts\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Users\User;
use Flarum\Core\User;
class PostReply
{

View File

@@ -8,17 +8,20 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Posts\Commands;
namespace Flarum\Core\Command;
use Flarum\Events\PostWillBeSaved;
use Flarum\Core\Discussions\DiscussionRepository;
use Flarum\Core\Posts\CommentPost;
use Flarum\Core\Support\DispatchesEvents;
use Flarum\Core\Notifications\NotificationSyncer;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Event\PostWillBeSaved;
use Flarum\Core\Repository\DiscussionRepository;
use Flarum\Core\Post\CommentPost;
use Flarum\Core\Support\DispatchEventsTrait;
use Flarum\Core\Notification\NotificationSyncer;
use Illuminate\Contracts\Events\Dispatcher;
class PostReplyHandler
{
use DispatchesEvents;
use DispatchEventsTrait;
use AssertPermissionTrait;
/**
* @var DiscussionRepository
@@ -31,11 +34,16 @@ class PostReplyHandler
protected $notifications;
/**
* @param Dispatcher $events
* @param DiscussionRepository $discussions
* @param NotificationSyncer $notifications
*/
public function __construct(DiscussionRepository $discussions, NotificationSyncer $notifications)
{
public function __construct(
Dispatcher $events,
DiscussionRepository $discussions,
NotificationSyncer $notifications
) {
$this->events = $events;
$this->discussions = $discussions;
$this->notifications = $notifications;
}
@@ -43,7 +51,7 @@ class PostReplyHandler
/**
* @param PostReply $command
* @return CommentPost
* @throws \Flarum\Core\Exceptions\PermissionDeniedException
* @throws \Flarum\Core\Exception\PermissionDeniedException
*/
public function handle(PostReply $command)
{
@@ -56,23 +64,25 @@ class PostReplyHandler
// it, check if they have permission to reply.
$discussion = $this->discussions->findOrFail($command->discussionId, $actor);
$discussion->assertCan($actor, 'reply');
$this->assertCan($actor, 'reply', $discussion);
// Create a new Post entity, persist it, and dispatch domain events.
// Before persistence, though, fire an event to give plugins an
// opportunity to alter the post entity based on data in the command.
$post = CommentPost::reply(
$command->discussionId,
$discussion->id,
array_get($command->data, 'attributes.content'),
$actor->id
);
event(new PostWillBeSaved($post, $actor, $command->data));
$this->events->fire(
new PostWillBeSaved($post, $actor, $command->data)
);
$post->save();
$this->notifications->onePerUser(function () use ($post) {
$this->dispatchEventsFor($post);
$this->notifications->onePerUser(function () use ($post, $actor) {
$this->dispatchEventsFor($post, $actor);
});
return $post;

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Notifications\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Users\User;
use Flarum\Core\User;
class ReadAllNotifications
{

View File

@@ -8,13 +8,16 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Notifications\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Notifications\Notification;
use Flarum\Core\Notifications\NotificationRepository;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Core\Notification;
use Flarum\Core\Repository\NotificationRepository;
class ReadAllNotificationsHandler
{
use AssertPermissionTrait;
/**
* @var NotificationRepository
*/
@@ -30,16 +33,13 @@ class ReadAllNotificationsHandler
/**
* @param ReadAllNotifications $command
*
* @return void
* @throws \Flarum\Core\Exception\PermissionDeniedException
*/
public function handle(ReadAllNotifications $command)
{
$actor = $command->actor;
if ($actor->isGuest()) {
throw new PermissionDeniedException;
}
$this->assertRegistered($actor);
$this->notifications->markAllAsRead($actor);
}

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Discussions\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Users\User;
use Flarum\Core\User;
class ReadDiscussion
{

View File

@@ -8,16 +8,18 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Discussions\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Discussions\DiscussionRepository;
use Flarum\Events\DiscussionStateWillBeSaved;
use Flarum\Core\Exceptions\PermissionDeniedException;
use Flarum\Core\Support\DispatchesEvents;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Core\Repository\DiscussionRepository;
use Flarum\Core\Support\DispatchEventsTrait;
use Flarum\Event\DiscussionStateWillBeSaved;
use Illuminate\Contracts\Events\Dispatcher;
class ReadDiscussionHandler
{
use DispatchesEvents;
use DispatchEventsTrait;
use AssertPermissionTrait;
/**
* @var DiscussionRepository
@@ -25,32 +27,34 @@ class ReadDiscussionHandler
protected $discussions;
/**
* @param Dispatcher $events
* @param DiscussionRepository $discussions
*/
public function __construct(DiscussionRepository $discussions)
public function __construct(Dispatcher $events, DiscussionRepository $discussions)
{
$this->events = $events;
$this->discussions = $discussions;
}
/**
* @param ReadDiscussion $command
* @return \Flarum\Core\Discussions\DiscussionState
* @throws \Flarum\Core\Exceptions\PermissionDeniedException
* @return \Flarum\Core\DiscussionState
* @throws \Flarum\Core\Exception\PermissionDeniedException
*/
public function handle(ReadDiscussion $command)
{
$actor = $command->actor;
if (! $actor->exists) {
throw new PermissionDeniedException;
}
$this->assertRegistered($actor);
$discussion = $this->discussions->findOrFail($command->discussionId, $actor);
$state = $discussion->stateFor($actor);
$state->read($command->readNumber);
event(new DiscussionStateWillBeSaved($state));
$this->events->fire(
new DiscussionStateWillBeSaved($state)
);
$state->save();

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Notifications\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Users\User;
use Flarum\Core\User;
class ReadNotification
{

View File

@@ -8,26 +8,25 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Notifications\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Notifications\Notification;
use Flarum\Core\Exceptions\PermissionDeniedException;
use Flarum\Core\Support\DispatchesEvents;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Core\Notification;
class ReadNotificationHandler
{
use AssertPermissionTrait;
/**
* @param ReadNotification $command
* @return Notification
* @throws \Flarum\Core\Exceptions\PermissionDeniedException
* @return \Flarum\Core\Notification
* @throws \Flarum\Core\Exception\PermissionDeniedException
*/
public function handle(ReadNotification $command)
{
$actor = $command->actor;
if ($actor->isGuest()) {
throw new PermissionDeniedException;
}
$this->assertRegistered($actor);
$notification = Notification::where('user_id', $actor->id)->findOrFail($command->notificationId);

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Users\User;
use Flarum\Core\User;
class RegisterUser
{

View File

@@ -8,19 +8,21 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Users\User;
use Flarum\Core\Users\AuthToken;
use Flarum\Events\UserWillBeSaved;
use Flarum\Core\Support\DispatchesEvents;
use Flarum\Core\Settings\SettingsRepository;
use Flarum\Core\Exceptions\PermissionDeniedException;
use DateTime;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Core\User;
use Flarum\Core\AuthToken;
use Flarum\Event\UserWillBeSaved;
use Flarum\Core\Support\DispatchEventsTrait;
use Flarum\Settings\SettingsRepository;
use Flarum\Core\Exception\PermissionDeniedException;
use Illuminate\Contracts\Events\Dispatcher;
class RegisterUserHandler
{
use DispatchesEvents;
use DispatchEventsTrait;
use AssertPermissionTrait;
/**
* @var SettingsRepository
@@ -28,21 +30,21 @@ class RegisterUserHandler
protected $settings;
/**
* @param Dispatcher $events
* @param SettingsRepository $settings
*/
public function __construct(SettingsRepository $settings)
public function __construct(Dispatcher $events, SettingsRepository $settings)
{
$this->events = $events;
$this->settings = $settings;
}
/**
* @param RegisterUser $command
*
* @throws PermissionDeniedException if signup is closed and the actor is
* not an administrator.
* @throws \Flarum\Core\Exceptions\InvalidConfirmationTokenException if an
* @throws \Flarum\Core\Exception\InvalidConfirmationTokenException if an
* email confirmation token is provided but is invalid.
*
* @return User
*/
public function handle(RegisterUser $command)
@@ -50,8 +52,8 @@ class RegisterUserHandler
$actor = $command->actor;
$data = $command->data;
if (! $this->settings->get('allow_sign_up') && ! $actor->isAdmin()) {
throw new PermissionDeniedException;
if (! $this->settings->get('allow_sign_up')) {
$this->assertAdmin($actor);
}
$username = array_get($data, 'attributes.username');
@@ -66,11 +68,7 @@ class RegisterUserHandler
$password = $password ?: str_random(20);
}
$user = User::register(
$username,
$email,
$password
);
$user = User::register($username, $email, $password);
// If a valid authentication token was provided, then we will assign
// the attributes associated with it to the user's account. If this
@@ -86,7 +84,9 @@ class RegisterUserHandler
}
}
event(new UserWillBeSaved($user, $actor, $data));
$this->events->fire(
new UserWillBeSaved($user, $actor, $data)
);
$user->save();
@@ -94,7 +94,7 @@ class RegisterUserHandler
$token->delete();
}
$this->dispatchEventsFor($user);
$this->dispatchEventsFor($user, $actor);
return $user;
}

View File

@@ -8,7 +8,7 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users\Commands;
namespace Flarum\Core\Command;
class RequestPasswordReset
{

View File

@@ -8,11 +8,11 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Settings\SettingsRepository;
use Flarum\Core\Users\PasswordToken;
use Flarum\Core\Users\UserRepository;
use Flarum\Settings\SettingsRepository;
use Flarum\Core\PasswordToken;
use Flarum\Core\Repository\UserRepository;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Mail\Message;
use Illuminate\Database\Eloquent\ModelNotFoundException;
@@ -57,7 +57,7 @@ class RequestPasswordResetHandler
/**
* @param RequestPasswordReset $command
* @return \Flarum\Core\Users\User
* @return \Flarum\Core\User
* @throws ModelNotFoundException
*/
public function handle(RequestPasswordReset $command)
@@ -71,12 +71,9 @@ class RequestPasswordResetHandler
$token = PasswordToken::generate($user->id);
$token->save();
// TODO: Need to use UrlGenerator, but since this is part of core we
// don't know that the forum routes will be loaded. Should the reset
// password route be part of core??
$data = [
'username' => $user->username,
'url' => Core::url().'/reset/'.$token->id,
'url' => $this->url->toRoute('resetPassword', ['token' => $token->id]),
'forumTitle' => $this->settings->get('forum_title'),
];

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Discussions\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Users\User;
use Flarum\Core\User;
class StartDiscussion
{

View File

@@ -8,50 +8,48 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Discussions\Commands;
namespace Flarum\Core\Command;
use Flarum\Events\DiscussionWillBeSaved;
use Flarum\Core\Forum;
use Illuminate\Contracts\Bus\Dispatcher;
use Flarum\Core\Discussions\Discussion;
use Flarum\Core\Posts\Commands\PostReply;
use Flarum\Core\Support\DispatchesEvents;
use Exception;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Core\Discussion;
use Flarum\Core\Support\DispatchEventsTrait;
use Flarum\Event\DiscussionWillBeSaved;
use Illuminate\Contracts\Bus\Dispatcher as BusDispatcher;
use Illuminate\Contracts\Events\Dispatcher as EventDispatcher;
class StartDiscussionHandler
{
use DispatchesEvents;
use DispatchEventsTrait;
use AssertPermissionTrait;
/**
* @var Dispatcher
* @var BusDispatcher
*/
protected $bus;
/**
* @var Forum
* @param EventDispatcher $events
* @param BusDispatcher $bus
* @internal param Forum $forum
*/
protected $forum;
/**
* @param Dispatcher $bus
* @param Forum $forum
*/
public function __construct(Dispatcher $bus, Forum $forum)
public function __construct(EventDispatcher $events, BusDispatcher $bus)
{
$this->events = $events;
$this->bus = $bus;
$this->forum = $forum;
}
/**
* @param StartDiscussion $command
* @return mixed
* @throws Exception
*/
public function handle(StartDiscussion $command)
{
$actor = $command->actor;
$data = $command->data;
$this->forum->assertCan($actor, 'startDiscussion');
$this->assertCan($actor, 'startDiscussion');
// Create a new Discussion entity, persist it, and dispatch domain
// events. Before persistance, though, fire an event to give plugins
@@ -62,7 +60,9 @@ class StartDiscussionHandler
$actor
);
event(new DiscussionWillBeSaved($discussion, $actor, $data));
$this->events->fire(
new DiscussionWillBeSaved($discussion, $actor, $data)
);
$discussion->save();
@@ -84,7 +84,7 @@ class StartDiscussionHandler
$discussion->setRawAttributes($post->discussion->getAttributes(), true);
$discussion->setStartPost($post);
$this->dispatchEventsFor($discussion);
$this->dispatchEventsFor($discussion, $actor);
$discussion->save();

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users\Commands;
namespace Flarum\Core\Command;
use Flarum\Core\Users\User;
use Flarum\Core\User;
use Psr\Http\Message\UploadedFileInterface;
class UploadAvatar

View File

@@ -8,11 +8,13 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users\Commands;
namespace Flarum\Core\Command;
use Flarum\Events\AvatarWillBeSaved;
use Flarum\Core\Users\UserRepository;
use Flarum\Core\Support\DispatchesEvents;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Event\AvatarWillBeSaved;
use Flarum\Core\Repository\UserRepository;
use Flarum\Core\Support\DispatchEventsTrait;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Str;
use League\Flysystem\Adapter\Local;
use League\Flysystem\Filesystem;
@@ -22,7 +24,8 @@ use Intervention\Image\ImageManager;
class UploadAvatarHandler
{
use DispatchesEvents;
use DispatchEventsTrait;
use AssertPermissionTrait;
/**
* @var UserRepository
@@ -35,19 +38,21 @@ class UploadAvatarHandler
protected $uploadDir;
/**
* @param Dispatcher $events
* @param UserRepository $users
* @param FilesystemInterface $uploadDir
*/
public function __construct(UserRepository $users, FilesystemInterface $uploadDir)
public function __construct(Dispatcher $events, UserRepository $users, FilesystemInterface $uploadDir)
{
$this->events = $events;
$this->users = $users;
$this->uploadDir = $uploadDir;
}
/**
* @param UploadAvatar $command
* @return \Flarum\Core\Users\User
* @throws \Flarum\Core\Exceptions\PermissionDeniedException
* @return \Flarum\Core\User
* @throws \Flarum\Core\Exception\PermissionDeniedException
*/
public function handle(UploadAvatar $command)
{
@@ -55,20 +60,19 @@ class UploadAvatarHandler
$user = $this->users->findOrFail($command->userId);
// Make sure the current user is allowed to edit the user profile.
// This will let admins and the user themselves pass through, and
// throw an exception otherwise.
if ($actor->id !== $user->id) {
$user->assertCan($actor, 'edit');
$this->assertCan($actor, 'edit', $user);
}
$tmpFile = tempnam(sys_get_temp_dir(), 'avatar');
$command->file->moveTo($tmpFile);
$manager = new ImageManager();
$manager = new ImageManager;
$manager->make($tmpFile)->fit(100, 100)->save();
event(new AvatarWillBeSaved($user, $actor, $tmpFile));
$this->events->fire(
new AvatarWillBeSaved($user, $actor, $tmpFile)
);
$mount = new MountManager([
'source' => new Filesystem(new Local(pathinfo($tmpFile, PATHINFO_DIRNAME))),
@@ -86,7 +90,8 @@ class UploadAvatarHandler
$mount->move("source://".pathinfo($tmpFile, PATHINFO_BASENAME), "target://$uploadName");
$user->save();
$this->dispatchEventsFor($user);
$this->dispatchEventsFor($user, $actor);
return $user;
}

View File

@@ -10,17 +10,55 @@
namespace Flarum\Core;
use Flarum\Core\Users\User;
use Flarum\Events\ModelAllow;
use Flarum\Support\ServiceProvider;
use Flarum\Extend;
use Flarum\Core\Access\Gate;
use Flarum\Core\Post\CommentPost;
use Flarum\Event\ConfigurePostTypes;
use Flarum\Event\ConfigureUserPreferences;
use Flarum\Event\GetPermission;
use Flarum\Foundation\AbstractServiceProvider;
use Illuminate\Contracts\Container\Container;
use RuntimeException;
class CoreServiceProvider extends ServiceProvider
class CoreServiceProvider extends AbstractServiceProvider
{
/**
* Bootstrap the application events.
*
* @return void
* {@inheritdoc}
*/
public function register()
{
$this->app->singleton('flarum.gate', function ($app) {
return new Gate($app, function () {
throw new RuntimeException('You must set the gate user with forUser()');
});
});
$this->app->alias('flarum.gate', 'Illuminate\Contracts\Auth\Access\Gate');
$this->app->alias('flarum.gate', 'Flarum\Core\Access\Gate');
$this->registerAvatarsFilesystem();
$this->app->register('Flarum\Core\Notification\NotificationServiceProvider');
$this->app->register('Flarum\Core\Search\SearchServiceProvider');
$this->app->register('Flarum\Formatter\FormatterServiceProvider');
}
protected function registerAvatarsFilesystem()
{
$avatarsFilesystem = function (Container $app) {
return $app->make('Illuminate\Contracts\Filesystem\Factory')->disk('flarum-avatars')->getDriver();
};
$this->app->when('Flarum\Core\Command\UploadAvatarHandler')
->needs('League\Flysystem\FilesystemInterface')
->give($avatarsFilesystem);
$this->app->when('Flarum\Core\Command\DeleteAvatarHandler')
->needs('League\Flysystem\FilesystemInterface')
->give($avatarsFilesystem);
}
/**
* {@inheritdoc}
*/
public function boot()
{
@@ -30,32 +68,76 @@ class CoreServiceProvider extends ServiceProvider
return get_class($command).'Handler@handle';
});
$events = $this->app->make('events');
$events->listen(ModelAllow::class, function (ModelAllow $event) {
if ($event->model instanceof Forum &&
$event->actor->hasPermission('forum.'.$event->action)) {
$this->app->make('flarum.gate')->before(function (User $actor, $ability, $model = null) {
if ($actor->hasPermission($ability)) {
return true;
}
return $this->app->make('events')->until(
new GetPermission($actor, $ability, [$model])
);
});
$this->registerPostTypes();
CommentPost::setFormatter($this->app->make('flarum.formatter'));
User::setHasher($this->app->make('hash'));
User::setGate($this->app->make('flarum.gate'));
$this->validateModelWith(User::class, 'Flarum\Core\Validator\UserValidator');
$this->validateModelWith(Group::class, 'Flarum\Core\Validator\GroupValidator');
$this->validateModelWith(Post::class, 'Flarum\Core\Validator\PostValidator');
$events = $this->app->make('events');
$events->subscribe('Flarum\Core\Listener\DiscussionMetadataUpdater');
$events->subscribe('Flarum\Core\Listener\UserMetadataUpdater');
$events->subscribe('Flarum\Core\Listener\EmailConfirmationMailer');
$events->subscribe('Flarum\Core\Listener\DiscussionRenamedNotifier');
$events->subscribe('Flarum\Core\Access\DiscussionPolicy');
$events->subscribe('Flarum\Core\Access\GroupPolicy');
$events->subscribe('Flarum\Core\Access\PostPolicy');
$events->subscribe('Flarum\Core\Access\UserPolicy');
$events->listen(ConfigureUserPreferences::class, [$this, 'configureUserPreferences']);
}
public function registerPostTypes()
{
$models = [
'Flarum\Core\Post\CommentPost',
'Flarum\Core\Post\DiscussionRenamedPost'
];
$this->app->make('events')->fire(
new ConfigurePostTypes($models)
);
foreach ($models as $model) {
Post::setModel($model::$type, $model);
}
}
/**
* Register the service provider.
*
* @return void
* @param ConfigureUserPreferences $event
*/
public function register()
public function configureUserPreferences(ConfigureUserPreferences $event)
{
$this->app->singleton('flarum.forum', 'Flarum\Core\Forum');
$event->add('discloseOnline', 'boolval', true);
$event->add('indexProfile', 'boolval', true);
$event->add('locale');
}
// FIXME: probably use Illuminate's AggregateServiceProvider
// functionality, because it includes the 'provides' stuff.
$this->app->register('Flarum\Core\Discussions\DiscussionsServiceProvider');
$this->app->register('Flarum\Core\Formatter\FormatterServiceProvider');
$this->app->register('Flarum\Core\Groups\GroupsServiceProvider');
$this->app->register('Flarum\Core\Notifications\NotificationsServiceProvider');
$this->app->register('Flarum\Core\Posts\PostsServiceProvider');
$this->app->register('Flarum\Core\Users\UsersServiceProvider');
/**
* @param string $model
* @param string $validator
*/
protected function validateModelWith($model, $validator)
{
$model::saving(function ($model) use ($validator) {
$this->app->make($validator)->assertValid($model);
});
}
}

View File

@@ -1,64 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core;
use Flarum\Core;
use Flarum\Support\ServiceProvider;
use Illuminate\Database\ConnectionResolver;
use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Database\Eloquent\Model;
use PDO;
use Flarum\Migrations\DatabaseMigrationRepository;
class DatabaseServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('flarum.db', function () {
$factory = new ConnectionFactory($this->app);
$connection = $factory->make($this->app->make('flarum.config')['database']);
$connection->setEventDispatcher($this->app->make('Illuminate\Contracts\Events\Dispatcher'));
$connection->setFetchMode(PDO::FETCH_CLASS);
return $connection;
});
$this->app->alias('flarum.db', 'Illuminate\Database\ConnectionInterface');
$this->app->singleton('Illuminate\Database\ConnectionResolverInterface', function () {
$resolver = new ConnectionResolver([
'flarum' => $this->app->make('flarum.db'),
]);
$resolver->setDefaultConnection('flarum');
return $resolver;
});
$this->app->alias('Illuminate\Database\ConnectionResolverInterface', 'db');
if (Core::isInstalled()) {
$this->app->booting(function () {
$resolver = $this->app->make('Illuminate\Database\ConnectionResolverInterface');
Model::setConnectionResolver($resolver);
Model::setEventDispatcher($this->app->make('events'));
});
}
$this->app->singleton('Flarum\Migrations\MigrationRepositoryInterface', function ($app) {
return new DatabaseMigrationRepository($app['db'], 'migrations');
});
}
}

View File

@@ -8,58 +8,56 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Discussions;
namespace Flarum\Core;
use Flarum\Core\Model;
use Flarum\Events\DiscussionWasDeleted;
use Flarum\Events\DiscussionWasStarted;
use Flarum\Events\DiscussionWasRenamed;
use Flarum\Events\DiscussionWasHidden;
use Flarum\Events\DiscussionWasRestored;
use Flarum\Events\PostWasDeleted;
use Flarum\Events\ScopePostVisibility;
use Flarum\Core\Posts\Post;
use Flarum\Core\Posts\MergeablePost;
use Flarum\Core\Users\Guest;
use Flarum\Core\Users\User;
use Flarum\Core\Support\EventGenerator;
use Flarum\Core\Support\Locked;
use Flarum\Core\Support\VisibleScope;
use Flarum\Core\Support\ValidatesBeforeSave;
use Flarum\Core\Post;
use Flarum\Core\Post\MergeableInterface;
use Flarum\Core\Support\EventGeneratorTrait;
use Flarum\Core\Support\ScopeVisibilityTrait;
use Flarum\Database\AbstractModel;
use Flarum\Event\DiscussionWasDeleted;
use Flarum\Event\DiscussionWasHidden;
use Flarum\Event\DiscussionWasRenamed;
use Flarum\Event\DiscussionWasRestored;
use Flarum\Event\DiscussionWasStarted;
use Flarum\Event\PostWasDeleted;
use Flarum\Event\ScopePostVisibility;
/**
* @todo document database columns with @property
* @property int $id
* @property string $title
* @property int $comments_count
* @property int $participants_count
* @property int $number_index
* @property \Carbon\Carbon $start_time
* @property int|null $start_user_id
* @property int|null $start_post_id
* @property \Carbon\Carbon|null $last_time
* @property int|null $last_user_id
* @property int|null $last_post_id
* @property int|null $last_post_number
* @property \Carbon\Carbon|null $hide_time
* @property int|null $hide_user_id
* @property DiscussionState|null $state
* @property \Illuminate\Database\Eloquent\Collection $posts
* @property \Illuminate\Database\Eloquent\Collection $comments
* @property \Illuminate\Database\Eloquent\Collection $participants
* @property Post|null $startPost
* @property User|null $startUser
* @property Post|null $lastPost
* @property User|null $lastUser
* @property \Illuminate\Database\Eloquent\Collection $readers
*/
class Discussion extends Model
class Discussion extends AbstractModel
{
use EventGenerator;
use Locked;
use VisibleScope;
use ValidatesBeforeSave;
use EventGeneratorTrait;
use ScopeVisibilityTrait;
/**
* {@inheritdoc}
*/
protected $table = 'discussions';
/**
* The validation rules for this model.
*
* @var array
*/
protected $rules = [
'title' => 'required|max:80',
'start_time' => 'required|date',
'comments_count' => 'integer',
'participants_count' => 'integer',
'start_user_id' => 'integer',
'start_post_id' => 'integer',
'last_time' => 'date',
'last_user_id' => 'integer',
'last_post_id' => 'integer',
'last_post_number' => 'integer'
];
/**
* An array of posts that have been modified during this request.
*
@@ -134,16 +132,15 @@ class Discussion extends Model
* Rename the discussion. Raises the DiscussionWasRenamed event.
*
* @param string $title
* @param User $user
* @return $this
*/
public function rename($title, User $user)
public function rename($title)
{
if ($this->title !== $title) {
$oldTitle = $this->title;
$this->title = $title;
$this->raise(new DiscussionWasRenamed($this, $user, $oldTitle));
$this->raise(new DiscussionWasRenamed($this, $oldTitle));
}
return $this;
@@ -261,12 +258,12 @@ class Discussion extends Model
* DiscussionRenamedPost, and delete if the title has been reverted
* completely.)
*
* @param MergeablePost $post The post to save.
* @param MergeableInterface $post The post to save.
* @return Post The resulting post. It may or may not be the same post as
* was originally intended to be saved. It also may not exist, if the
* merge logic resulted in deletion.
*/
public function mergePost(MergeablePost $post)
public function mergePost(MergeableInterface $post)
{
$lastPost = $this->posts()->latest('time')->first();
@@ -292,7 +289,7 @@ class Discussion extends Model
*/
public function posts()
{
return $this->hasMany('Flarum\Core\Posts\Post');
return $this->hasMany('Flarum\Core\Post');
}
/**
@@ -304,11 +301,13 @@ class Discussion extends Model
*/
public function postsVisibleTo(User $user)
{
$query = $this->posts()->getQuery();
$relation = $this->posts();
event(new ScopePostVisibility($this, $query, $user));
static::$dispatcher->fire(
new ScopePostVisibility($this, $relation->getQuery(), $user)
);
return $query;
return $relation;
}
/**
@@ -342,7 +341,7 @@ class Discussion extends Model
*/
public function startPost()
{
return $this->belongsTo('Flarum\Core\Posts\Post', 'start_post_id');
return $this->belongsTo('Flarum\Core\Post', 'start_post_id');
}
/**
@@ -352,7 +351,7 @@ class Discussion extends Model
*/
public function startUser()
{
return $this->belongsTo('Flarum\Core\Users\User', 'start_user_id');
return $this->belongsTo('Flarum\Core\User', 'start_user_id');
}
/**
@@ -362,7 +361,7 @@ class Discussion extends Model
*/
public function lastPost()
{
return $this->belongsTo('Flarum\Core\Posts\Post', 'last_post_id');
return $this->belongsTo('Flarum\Core\Post', 'last_post_id');
}
/**
@@ -372,7 +371,7 @@ class Discussion extends Model
*/
public function lastUser()
{
return $this->belongsTo('Flarum\Core\Users\User', 'last_user_id');
return $this->belongsTo('Flarum\Core\User', 'last_user_id');
}
/**
@@ -382,7 +381,7 @@ class Discussion extends Model
*/
public function readers()
{
return $this->belongsToMany('Flarum\Core\Users\User', 'users_discussions');
return $this->belongsToMany('Flarum\Core\User', 'users_discussions');
}
/**
@@ -401,7 +400,7 @@ class Discussion extends Model
{
$user = $user ?: static::$stateUser;
return $this->hasOne('Flarum\Core\Discussions\DiscussionState')->where('user_id', $user ? $user->id : null);
return $this->hasOne('Flarum\Core\DiscussionState')->where('user_id', $user ? $user->id : null);
}
/**
@@ -409,7 +408,7 @@ class Discussion extends Model
* exist.
*
* @param User $user
* @return \Flarum\Core\Discussions\DiscussionState
* @return \Flarum\Core\DiscussionState
*/
public function stateFor(User $user)
{

View File

@@ -8,11 +8,11 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Discussions;
namespace Flarum\Core;
use Flarum\Events\DiscussionWasRead;
use Flarum\Core\Model;
use Flarum\Core\Support\EventGenerator;
use Flarum\Event\DiscussionWasRead;
use Flarum\Database\AbstractModel;
use Flarum\Core\Support\EventGeneratorTrait;
use Illuminate\Database\Eloquent\Builder;
/**
@@ -22,11 +22,16 @@ use Illuminate\Database\Eloquent\Builder;
* be used to store other information, if the appropriate columns are added to
* the database, like a user's subscription status for a discussion.
*
* @todo document database columns with @property
* @property int $user_id
* @property int $discussion_id
* @property \Carbon\Carbon|null $read_time
* @property int|null $read_number
* @property Discussion $discussion
* @property \Flarum\Core\User $user
*/
class DiscussionState extends Model
class DiscussionState extends AbstractModel
{
use EventGenerator;
use EventGeneratorTrait;
/**
* {@inheritdoc}
@@ -64,7 +69,7 @@ class DiscussionState extends Model
*/
public function discussion()
{
return $this->belongsTo('Flarum\Core\Discussions\Discussion', 'discussion_id');
return $this->belongsTo('Flarum\Core\Discussion', 'discussion_id');
}
/**
@@ -74,7 +79,7 @@ class DiscussionState extends Model
*/
public function user()
{
return $this->belongsTo('Flarum\Core\Users\User', 'user_id');
return $this->belongsTo('Flarum\Core\User', 'user_id');
}
/**

View File

@@ -1,54 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Discussions\Commands;
use Flarum\Core\Discussions\DiscussionRepository;
use Flarum\Events\DiscussionWillBeDeleted;
use Flarum\Core\Support\DispatchesEvents;
class DeleteDiscussionHandler
{
use DispatchesEvents;
/**
* @var \Flarum\Core\Discussions\DiscussionRepository
*/
protected $discussions;
/**
* @param \Flarum\Core\Discussions\DiscussionRepository $discussions
*/
public function __construct(DiscussionRepository $discussions)
{
$this->discussions = $discussions;
}
/**
* @param \Flarum\Core\Discussions\Commands\DeleteDiscussion $command
* @return \Flarum\Core\Discussions\Discussion
*/
public function handle(DeleteDiscussion $command)
{
$actor = $command->actor;
$discussion = $this->discussions->findOrFail($command->discussionId, $actor);
$discussion->assertCan($actor, 'delete');
event(new DiscussionWillBeDeleted($discussion, $actor, $command->data));
$discussion->delete();
$this->dispatchEventsFor($discussion);
return $discussion;
}
}

View File

@@ -1,103 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Discussions;
use Flarum\Core\Search\GambitManager;
use Flarum\Core\Users\User;
use Flarum\Events\ModelAllow;
use Flarum\Events\ScopeModelVisibility;
use Flarum\Events\RegisterDiscussionGambits;
use Flarum\Events\ScopeHiddenDiscussionVisibility;
use Flarum\Support\ServiceProvider;
use Flarum\Extend;
use Illuminate\Contracts\Container\Container;
use Carbon\Carbon;
class DiscussionsServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application events.
*
* @return void
*/
public function boot()
{
Discussion::setValidator($this->app->make('validator'));
$events = $this->app->make('events');
$settings = $this->app->make('Flarum\Core\Settings\SettingsRepository');
$events->subscribe('Flarum\Core\Discussions\Listeners\DiscussionMetadataUpdater');
$events->listen(ModelAllow::class, function (ModelAllow $event) use ($settings) {
if ($event->model instanceof Discussion) {
if ($event->actor->hasPermission('discussion.'.$event->action)) {
return true;
}
if (($event->action === 'rename' || $event->action === 'delete') &&
$event->model->start_user_id == $event->actor->id) {
$allowRenaming = $settings->get('allow_renaming');
if ($allowRenaming === '-1' ||
($allowRenaming === 'reply' && $event->model->participants_count <= 1) ||
($event->model->start_time->diffInMinutes(Carbon::now()) < $allowRenaming)) {
return true;
}
}
}
});
$events->listen(ScopeModelVisibility::class, function (ScopeModelVisibility $event) {
if ($event->model instanceof Discussion) {
$user = $event->actor;
if (! $user->hasPermission('discussion.hide')) {
$event->query->where(function ($query) use ($user) {
$query->whereNull('discussions.hide_time')
->where('comments_count', '>', 0)
->orWhere('start_user_id', $user->id);
event(new ScopeHiddenDiscussionVisibility($query, $user, 'discussion.hide'));
});
}
}
});
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->bind(
'Flarum\Core\Discussions\Search\Fulltext\Driver',
'Flarum\Core\Discussions\Search\Fulltext\MySqlFulltextDriver'
);
$this->app->when('Flarum\Core\Discussions\Search\DiscussionSearcher')
->needs('Flarum\Core\Search\GambitManager')
->give(function (Container $app) {
$gambits = new GambitManager($app);
$gambits->setFulltextGambit('Flarum\Core\Discussions\Search\Gambits\FulltextGambit');
$gambits->add('Flarum\Core\Discussions\Search\Gambits\AuthorGambit');
$gambits->add('Flarum\Core\Discussions\Search\Gambits\HiddenGambit');
$gambits->add('Flarum\Core\Discussions\Search\Gambits\UnreadGambit');
event(new RegisterDiscussionGambits($gambits));
return $gambits;
});
}
}

View File

@@ -8,16 +8,16 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users;
namespace Flarum\Core;
use Flarum\Core\Model;
use Flarum\Core\Exceptions\InvalidConfirmationTokenException;
use Flarum\Database\AbstractModel;
use Flarum\Core\Exception\InvalidConfirmationTokenException;
use DateTime;
/**
* @todo document database columns with @property
*/
class EmailToken extends Model
class EmailToken extends AbstractModel
{
/**
* {@inheritdoc}
@@ -63,7 +63,7 @@ class EmailToken extends Model
*/
public function user()
{
return $this->belongsTo('Flarum\Core\Users\User');
return $this->belongsTo('Flarum\Core\User');
}
/**
@@ -71,10 +71,8 @@ class EmailToken extends Model
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $id
*
* @throws InvalidConfirmationTokenException
*
* @return static
* @throws InvalidConfirmationTokenException
*/
public function scopeValidOrFail($query, $id)
{

View File

@@ -8,11 +8,12 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Exceptions;
namespace Flarum\Core\Exception;
use Exception;
use Tobscure\JsonApi\Exception\JsonApiSerializableInterface;
class InvalidConfirmationTokenException extends Exception implements JsonApiSerializable
class InvalidConfirmationTokenException extends Exception implements JsonApiSerializableInterface
{
/**
* {@inheritdoc}

View File

@@ -8,11 +8,12 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Exceptions;
namespace Flarum\Core\Exception;
use Exception;
use Tobscure\JsonApi\Exception\JsonApiSerializableInterface;
class PermissionDeniedException extends Exception implements JsonApiSerializable
class PermissionDeniedException extends Exception implements JsonApiSerializableInterface
{
/**
* Return the HTTP status code to be used for this exception.

View File

@@ -8,11 +8,12 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Exceptions;
namespace Flarum\Core\Exception;
use Exception;
use Tobscure\JsonApi\Exception\JsonApiSerializableInterface;
class ValidationException extends Exception implements JsonApiSerializable
class ValidationException extends Exception implements JsonApiSerializableInterface
{
protected $messages;

View File

@@ -1,29 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Exceptions;
interface JsonApiSerializable
{
/**
* Return the HTTP status code to be used for this exception.
*
* @return int
*/
public function getStatusCode();
/**
* Return an array of errors, formatted as JSON-API error objects.
*
* @see http://jsonapi.org/format/#error-objects
* @return array
*/
public function getErrors();
}

View File

@@ -1,131 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Formatter;
use Illuminate\Contracts\Cache\Repository;
use s9e\TextFormatter\Configurator;
use s9e\TextFormatter\Unparser;
use Flarum\Events\FormatterConfigurator;
use Flarum\Events\FormatterParser;
use Flarum\Events\FormatterRenderer;
use Flarum\Core\Posts\CommentPost;
class Formatter
{
protected $cache;
public function __construct(Repository $cache)
{
$this->cache = $cache;
}
protected function getConfigurator()
{
$configurator = new Configurator;
$configurator->rootRules->enableAutoLineBreaks();
$configurator->rendering->engine = 'PHP';
$configurator->rendering->engine->cacheDir = storage_path() . '/app';
$configurator->Escaper;
$configurator->Autoemail;
$configurator->Autolink;
$configurator->tags->onDuplicate('replace');
event(new FormatterConfigurator($configurator));
$dom = $configurator->tags['URL']->template->asDOM();
foreach ($dom->getElementsByTagName('a') as $a) {
$a->setAttribute('target', '_blank');
$a->setAttribute('rel', 'nofollow');
}
$dom->saveChanges();
return $configurator;
}
public function flush()
{
$this->cache->forget('flarum.formatter.parser');
$this->cache->forget('flarum.formatter.renderer');
}
protected function getComponent($key)
{
$cacheKey = 'flarum.formatter.' . $key;
return $this->cache->rememberForever($cacheKey, function () use ($key) {
return $this->getConfigurator()->finalize()[$key];
});
}
protected function getParser($context = null)
{
$parser = $this->getComponent('parser');
$parser->registeredVars['context'] = $context;
event(new FormatterParser($parser, $context));
return $parser;
}
protected function getRenderer($context = null)
{
spl_autoload_register(function ($class) {
if (file_exists($file = storage_path() . '/app/' . $class . '.php')) {
include $file;
}
});
$renderer = $this->getComponent('renderer');
event(new FormatterRenderer($renderer, $context));
return $renderer;
}
public function getJS()
{
$configurator = $this->getConfigurator();
$configurator->enableJavaScript();
$configurator->javascript->exportMethods = ['preview'];
$minifier = $configurator->javascript->setMinifier('ClosureCompilerService');
$minifier->keepGoing = true;
$minifier->cacheDir = storage_path() . '/app';
return $configurator->finalize([
'returnParser' => false,
'returnRenderer' => false
])['js'];
}
public function parse($text, $context = null)
{
$parser = $this->getParser($context);
return $parser->parse($text);
}
public function render($xml, $context = null)
{
$renderer = $this->getRenderer($context);
return $renderer->render($xml);
}
public function unparse($xml)
{
return Unparser::unparse($xml);
}
}

View File

@@ -1,36 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Formatter;
use Flarum\Support\ServiceProvider;
use Flarum\Extend;
class FormatterServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application events.
*
* @return void
*/
public function boot()
{
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('flarum.formatter', 'Flarum\Core\Formatter\Formatter');
}
}

View File

@@ -1,24 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core;
use Flarum\Core\Support\Locked;
use Flarum\Core;
class Forum extends Model
{
use Locked;
public function getTitleAttribute()
{
return Core::config('forum_title');
}
}

View File

@@ -8,42 +8,35 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Groups;
namespace Flarum\Core;
use Flarum\Core\Model;
use Flarum\Core\Support\EventGeneratorTrait;
use Flarum\Core\Support\Locked;
use Flarum\Core\Support\VisibleScope;
use Flarum\Core\Support\EventGenerator;
use Flarum\Core\Support\ValidatesBeforeSave;
use Flarum\Events\GroupWasDeleted;
use Flarum\Events\GroupWasCreated;
use Flarum\Events\GroupWasRenamed;
use Flarum\Core\Support\ScopeVisibilityTrait;
use Flarum\Database\AbstractModel;
use Flarum\Event\GroupWasCreated;
use Flarum\Event\GroupWasDeleted;
use Flarum\Event\GroupWasRenamed;
/**
* @todo document database columns with @property
* @property int $id
* @property string $name_singular
* @property string $name_plural
* @property string|null $color
* @property string|null $icon
* @property \Illuminate\Database\Eloquent\Collection $users
* @property \Illuminate\Database\Eloquent\Collection $permissions
*/
class Group extends Model
class Group extends AbstractModel
{
use ValidatesBeforeSave;
use EventGenerator;
use Locked;
use VisibleScope;
use EventGeneratorTrait;
use ScopeVisibilityTrait;
/**
* {@inheritdoc}
*/
protected $table = 'groups';
/**
* The validation rules for this model.
*
* @var array
*/
protected $rules = [
'name_singular' => 'required',
'name_plural' => 'required'
];
/**
* The ID of the administrator group.
*/
@@ -68,7 +61,7 @@ class Group extends Model
{
parent::boot();
static::deleted(function ($group) {
static::deleted(function (Group $group) {
$group->raise(new GroupWasDeleted($group));
$group->permissions()->delete();
@@ -110,6 +103,8 @@ class Group extends Model
$this->name_singular = $nameSingular;
$this->name_plural = $namePlural;
$this->raise(new GroupWasRenamed($this));
return $this;
}
@@ -120,7 +115,7 @@ class Group extends Model
*/
public function users()
{
return $this->belongsToMany('Flarum\Core\Users\User', 'users_groups');
return $this->belongsToMany('Flarum\Core\User', 'users_groups');
}
/**
@@ -130,6 +125,6 @@ class Group extends Model
*/
public function permissions()
{
return $this->hasMany('Flarum\Core\Groups\Permission');
return $this->hasMany('Flarum\Core\Permission');
}
}

View File

@@ -1,55 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Groups\Commands;
use Flarum\Core\Groups\Group;
use Flarum\Core\Groups\GroupRepository;
use Flarum\Events\GroupWillBeDeleted;
use Flarum\Core\Support\DispatchesEvents;
class DeleteGroupHandler
{
use DispatchesEvents;
/**
* @var GroupRepository
*/
protected $groups;
/**
* @param GroupRepository $groups
*/
public function __construct(GroupRepository $groups)
{
$this->groups = $groups;
}
/**
* @param DeleteGroup $command
* @return Group
* @throws \Flarum\Core\Exceptions\PermissionDeniedException
*/
public function handle(DeleteGroup $command)
{
$actor = $command->actor;
$group = $this->groups->findOrFail($command->groupId, $actor);
$group->assertCan($actor, 'delete');
event(new GroupWillBeDeleted($group, $actor, $command->data));
$group->delete();
$this->dispatchEventsFor($group);
return $group;
}
}

View File

@@ -1,38 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Groups;
use Flarum\Events\ModelAllow;
use Flarum\Support\ServiceProvider;
use Illuminate\Contracts\Container\Container;
class GroupsServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application events.
*
* @return void
*/
public function boot()
{
Group::setValidator($this->app->make('validator'));
$events = $this->app->make('events');
$events->listen(ModelAllow::class, function (ModelAllow $event) {
if ($event->model instanceof Group) {
if ($event->actor->hasPermission('group.'.$event->action)) {
return true;
}
}
});
}
}

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users;
namespace Flarum\Core;
use Flarum\Core\Groups\Group;
use Flarum\Core\Group;
class Guest extends User
{

View File

@@ -8,13 +8,13 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Discussions\Listeners;
namespace Flarum\Core\Listener;
use Flarum\Core\Posts\Post;
use Flarum\Events\PostWasPosted;
use Flarum\Events\PostWasDeleted;
use Flarum\Events\PostWasHidden;
use Flarum\Events\PostWasRestored;
use Flarum\Core\Post;
use Flarum\Event\PostWasPosted;
use Flarum\Event\PostWasDeleted;
use Flarum\Event\PostWasHidden;
use Flarum\Event\PostWasRestored;
use Illuminate\Contracts\Events\Dispatcher;
class DiscussionMetadataUpdater
@@ -46,7 +46,7 @@ class DiscussionMetadataUpdater
}
/**
* @param \Flarum\Events\PostWasDeleted $event
* @param \Flarum\Event\PostWasDeleted $event
*/
public function whenPostWasDeleted(PostWasDeleted $event)
{

View File

@@ -8,12 +8,12 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Notifications\Listeners;
namespace Flarum\Core\Listener;
use Flarum\Events\DiscussionWasRenamed;
use Flarum\Core\Posts\DiscussionRenamedPost;
use Flarum\Core\Notifications\DiscussionRenamedBlueprint;
use Flarum\Core\Notifications\NotificationSyncer;
use Flarum\Event\DiscussionWasRenamed;
use Flarum\Core\Post\DiscussionRenamedPost;
use Flarum\Core\Notification\DiscussionRenamedBlueprint;
use Flarum\Core\Notification\NotificationSyncer;
use Illuminate\Contracts\Events\Dispatcher;
class DiscussionRenamedNotifier
@@ -40,7 +40,7 @@ class DiscussionRenamedNotifier
}
/**
* @param \Flarum\Events\DiscussionWasRenamed $event
* @param \Flarum\Event\DiscussionWasRenamed $event
*/
public function whenDiscussionWasRenamed(DiscussionWasRenamed $event)
{

View File

@@ -8,14 +8,15 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users\Listeners;
namespace Flarum\Core\Listener;
use Flarum\Core\Settings\SettingsRepository;
use Flarum\Events\UserWasRegistered;
use Flarum\Events\UserEmailChangeWasRequested;
use Flarum\Core;
use Flarum\Core\Users\EmailToken;
use Flarum\Core\Users\User;
use Flarum\Core\EmailToken;
use Flarum\Core\User;
use Flarum\Event\UserEmailChangeWasRequested;
use Flarum\Event\UserWasRegistered;
use Flarum\Forum\UrlGenerator;
use Flarum\Settings\SettingsRepository;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Mail\Message;
@@ -31,15 +32,21 @@ class EmailConfirmationMailer
* @var Mailer
*/
protected $mailer;
/**
* @var UrlGenerator
*/
private $url;
/**
* @param SettingsRepository $settings
* @param \Flarum\Settings\SettingsRepository $settings
* @param Mailer $mailer
* @param UrlGenerator $url
*/
public function __construct(SettingsRepository $settings, Mailer $mailer)
public function __construct(SettingsRepository $settings, Mailer $mailer, UrlGenerator $url)
{
$this->settings = $settings;
$this->mailer = $mailer;
$this->url = $url;
}
/**
@@ -52,7 +59,7 @@ class EmailConfirmationMailer
}
/**
* @param \Flarum\Events\UserWasRegistered $event
* @param \Flarum\Event\UserWasRegistered $event
*/
public function whenUserWasRegistered(UserWasRegistered $event)
{
@@ -71,7 +78,7 @@ class EmailConfirmationMailer
}
/**
* @param \Flarum\Events\UserEmailChangeWasRequested $event
* @param \Flarum\Event\UserEmailChangeWasRequested $event
*/
public function whenUserEmailChangeWasRequested(UserEmailChangeWasRequested $event)
{
@@ -110,12 +117,12 @@ class EmailConfirmationMailer
{
$token = $this->generateToken($user, $email);
// TODO: Need to use UrlGenerator, but since this is part of core we
// TODO: Need to use AbstractUrlGenerator, but since this is part of core we
// don't know that the forum routes will be loaded. Should the confirm
// email route be part of core??
return [
'username' => $user->username,
'url' => Core::url().'/confirm/'.$token->id,
'url' => $this->url->toRoute('confirmEmail', ['token' => $token->id]),
'forumTitle' => $this->settings->get('forum_title')
];
}

View File

@@ -8,16 +8,16 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users\Listeners;
namespace Flarum\Core\Listener;
use Flarum\Core\Posts\Post;
use Flarum\Core\Discussions\Discussion;
use Flarum\Events\PostWasPosted;
use Flarum\Events\PostWasDeleted;
use Flarum\Events\PostWasHidden;
use Flarum\Events\PostWasRestored;
use Flarum\Events\DiscussionWasStarted;
use Flarum\Events\DiscussionWasDeleted;
use Flarum\Core\Post;
use Flarum\Core\Discussion;
use Flarum\Event\PostWasPosted;
use Flarum\Event\PostWasDeleted;
use Flarum\Event\PostWasHidden;
use Flarum\Event\PostWasRestored;
use Flarum\Event\DiscussionWasStarted;
use Flarum\Event\DiscussionWasDeleted;
use Illuminate\Contracts\Events\Dispatcher;
class UserMetadataUpdater
@@ -44,7 +44,7 @@ class UserMetadataUpdater
}
/**
* @param PostWasDeleted $event
* @param \Flarum\Event\PostWasDeleted $event
*/
public function whenPostWasDeleted(PostWasDeleted $event)
{
@@ -60,7 +60,7 @@ class UserMetadataUpdater
}
/**
* @param \Flarum\Events\PostWasRestored $event
* @param \Flarum\Event\PostWasRestored $event
*/
public function whenPostWasRestored(PostWasRestored $event)
{
@@ -76,7 +76,7 @@ class UserMetadataUpdater
}
/**
* @param \Flarum\Events\DiscussionWasDeleted $event
* @param \Flarum\Event\DiscussionWasDeleted $event
*/
public function whenDiscussionWasDeleted(DiscussionWasDeleted $event)
{

View File

@@ -1,142 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core;
use Flarum\Events\ModelDates;
use Flarum\Events\ModelRelationship;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Database\Eloquent\Relations\Relation;
use LogicException;
/**
* Base model class, building on Eloquent.
*
* Adds the ability for custom relations to be added to a model during runtime.
* These relations behave in the same way that you would expect; they can be
* queried, eager loaded, and accessed as an attribute.
*
* Also has a scope method `whereVisibleTo` that scopes a query to only include
* records that the user has permission to see.
*/
abstract class Model extends Eloquent
{
/**
* Indicates if the model should be timestamped. Turn off by default.
*
* @var boolean
*/
public $timestamps = false;
/**
* An array of callbacks to be run once after the model is saved.
*
* @var callable[]
*/
public $afterSaveCallbacks = [];
/**
* {@inheritdoc}
*/
public static function boot()
{
parent::boot();
static::saved(function (Model $model) {
foreach ($model->afterSaveCallbacks as $callback) {
$callback($model);
}
$model->afterSaveCallbacks = [];
});
}
/**
* Get the attributes that should be converted to dates.
*
* @return array
*/
public function getDates()
{
static $dates = [];
$class = get_class($this);
if (! isset($dates[$class])) {
event(new ModelDates($this, $this->dates));
$dates[$class] = $this->dates;
}
return $dates[$class];
}
/**
* Get an attribute from the model. If nothing is found, attempt to load
* a custom relation method with this key.
*
* @param string $key
* @return mixed
*/
public function getAttribute($key)
{
if (! is_null($value = parent::getAttribute($key))) {
return $value;
}
// If a custom relation with this key has been set up, then we will load
// and return results from the query and hydrate the relationship's
// value on the "relationships" array.
if (! $this->relationLoaded($key) && ($relation = $this->getCustomRelation($key))) {
if (! $relation instanceof Relation) {
throw new LogicException('Relationship method must return an object of type '
. 'Illuminate\Database\Eloquent\Relations\Relation');
}
return $this->relations[$key] = $relation->getResults();
}
}
/**
* Get a custom relation object.
*
* @param string $name
* @return mixed
*/
protected function getCustomRelation($name)
{
return static::$dispatcher->until(
new ModelRelationship($this, $name)
);
}
/**
* Register a callback to be run once after the model is saved.
*
* @param callable $callback
* @return void
*/
public function afterSave($callback)
{
$this->afterSaveCallbacks[] = $callback;
}
/**
* @inheritdoc
*/
public function __call($method, $arguments)
{
if ($relation = $this->getCustomRelation($method)) {
return $relation;
}
return parent::__call($method, $arguments);
}
}

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Notifications;
namespace Flarum\Core;
use Flarum\Core\Model;
use Flarum\Database\AbstractModel;
/**
* Models a notification record in the database.
@@ -26,9 +26,20 @@ use Flarum\Core\Model;
* someone renamed a user's discussion. Its subject is a discussion, of which
* the ID is stored in the `subject_id` column.
*
* @todo document database columns with @property
* @property int $id
* @property int $user_id
* @property int|null $sender_id
* @property string $type
* @property int|null $subject_id
* @property mixed|null $data
* @property \Carbon\Carbon $time
* @property bool $is_read
* @property bool $is_deleted
* @property \Flarum\Core\User|null $user
* @property \Flarum\Core\User|null $sender
* @property \Flarum\Database\AbstractModel|null $subject
*/
class Notification extends Model
class Notification extends AbstractModel
{
/**
* {@inheritdoc}
@@ -44,7 +55,7 @@ class Notification extends Model
* A map of notification types and the model classes to use for their
* subjects. For example, the 'discussionRenamed' notification type, which
* represents that a user's discussion was renamed, has the subject model
* class 'Flarum\Core\Discussions\Discussion'.
* class 'Flarum\Core\Discussion'.
*
* @var array
*/
@@ -101,7 +112,7 @@ class Notification extends Model
*/
public function user()
{
return $this->belongsTo('Flarum\Core\Users\User', 'user_id');
return $this->belongsTo('Flarum\Core\User', 'user_id');
}
/**
@@ -111,7 +122,7 @@ class Notification extends Model
*/
public function sender()
{
return $this->belongsTo('Flarum\Core\Users\User', 'sender_id');
return $this->belongsTo('Flarum\Core\User', 'sender_id');
}
/**

View File

@@ -8,26 +8,26 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Notifications;
namespace Flarum\Core\Notification;
/**
* A notification Blueprint, when instantiated, represents a notification about
* A notification BlueprintInterface, when instantiated, represents a notification about
* something. The blueprint is used by the NotificationSyncer to commit the
* notification to the database.
*/
interface Blueprint
interface BlueprintInterface
{
/**
* Get the user that sent the notification.
*
* @return \Flarum\Core\Users\User|null
* @return \Flarum\Core\User|null
*/
public function getSender();
/**
* Get the model that is the subject of this activity.
*
* @return \Flarum\Core\Model|null
* @return \Flarum\Database\AbstractModel|null
*/
public function getSubject();

View File

@@ -8,11 +8,11 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Notifications;
namespace Flarum\Core\Notification;
use Flarum\Core\Posts\DiscussionRenamedPost;
use Flarum\Core\Post\DiscussionRenamedPost;
class DiscussionRenamedBlueprint implements Blueprint
class DiscussionRenamedBlueprint implements BlueprintInterface
{
/**
* @var DiscussionRenamedPost
@@ -64,6 +64,6 @@ class DiscussionRenamedBlueprint implements Blueprint
*/
public static function getSubjectModel()
{
return 'Flarum\Core\Discussions\Discussion';
return 'Flarum\Core\Discussion';
}
}

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Notifications;
namespace Flarum\Core\Notification;
interface MailableBlueprint
interface MailableInterface
{
/**
* Get the name of the view to construct a notification email with.

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Notifications;
namespace Flarum\Core\Notification;
use Flarum\Core\Users\User;
use Flarum\Core\User;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Mail\Message;
@@ -30,10 +30,10 @@ class NotificationMailer
}
/**
* @param MailableBlueprint $blueprint
* @param MailableInterface $blueprint
* @param User $user
*/
public function send(MailableBlueprint $blueprint, User $user)
public function send(MailableInterface $blueprint, User $user)
{
$this->mailer->send(
$blueprint->getEmailView(),

View File

@@ -8,42 +8,37 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Notifications;
namespace Flarum\Core\Notification;
use Flarum\Core\Users\User;
use Flarum\Events\RegisterNotificationTypes;
use Flarum\Support\ServiceProvider;
use Flarum\Core\Notification;
use Flarum\Core\User;
use Flarum\Event\ConfigureNotificationTypes;
use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Extend;
use ReflectionClass;
class NotificationsServiceProvider extends ServiceProvider
class NotificationServiceProvider extends AbstractServiceProvider
{
/**
* Bootstrap the application events.
*
* @return void
* {@inheritdoc}
*/
public function boot()
{
$events = $this->app->make('events');
$events->subscribe('Flarum\Core\Notifications\Listeners\DiscussionRenamedNotifier');
$this->registerNotificationTypes();
}
/**
* Register notification types.
*
* @return void
*/
public function registerNotificationTypes()
{
$blueprints = [
'Flarum\Core\Notifications\DiscussionRenamedBlueprint' => ['alert']
'Flarum\Core\Notification\DiscussionRenamedBlueprint' => ['alert']
];
event(new RegisterNotificationTypes($blueprints));
$this->app->make('events')->fire(
new ConfigureNotificationTypes($blueprints)
);
foreach ($blueprints as $blueprint => $enabled) {
Notification::setSubjectModel(
@@ -57,7 +52,7 @@ class NotificationsServiceProvider extends ServiceProvider
in_array('alert', $enabled)
);
if ((new ReflectionClass($blueprint))->implementsInterface('Flarum\Core\Notifications\MailableBlueprint')) {
if ((new ReflectionClass($blueprint))->implementsInterface('Flarum\Core\Notification\MailableInterface')) {
User::addPreference(
User::getNotificationPreferenceKey($type, 'email'),
'boolval',
@@ -66,13 +61,4 @@ class NotificationsServiceProvider extends ServiceProvider
}
}
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
}
}

View File

@@ -8,10 +8,12 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Notifications;
namespace Flarum\Core\Notification;
use Flarum\Events\NotificationWillBeSent;
use Flarum\Core\Users\User;
use Flarum\Core\Notification;
use Flarum\Core\Repository\NotificationRepository;
use Flarum\Event\NotificationWillBeSent;
use Flarum\Core\User;
use Carbon\Carbon;
/**
@@ -63,11 +65,11 @@ class NotificationSyncer
* visible to anyone else. If it is being made visible for the first time,
* attempt to send the user an email.
*
* @param Blueprint $blueprint
* @param BlueprintInterface $blueprint
* @param User[] $users
* @return void
*/
public function sync(Blueprint $blueprint, array $users)
public function sync(BlueprintInterface $blueprint, array $users)
{
$attributes = $this->getAttributes($blueprint);
@@ -118,10 +120,10 @@ class NotificationSyncer
/**
* Delete a notification for all users.
*
* @param Blueprint $blueprint
* @param BlueprintInterface $blueprint
* @return void
*/
public function delete(Blueprint $blueprint)
public function delete(BlueprintInterface $blueprint)
{
Notification::where($this->getAttributes($blueprint))->update(['is_deleted' => true]);
}
@@ -129,10 +131,10 @@ class NotificationSyncer
/**
* Restore a notification for all users.
*
* @param Blueprint $blueprint
* @param BlueprintInterface $blueprint
* @return void
*/
public function restore(Blueprint $blueprint)
public function restore(BlueprintInterface $blueprint)
{
Notification::where($this->getAttributes($blueprint))->update(['is_deleted' => false]);
}
@@ -158,10 +160,10 @@ class NotificationSyncer
* Create a notification record and send an email (depending on user
* preference) from a blueprint to a list of recipients.
*
* @param Blueprint $blueprint
* @param BlueprintInterface $blueprint
* @param User[] $recipients
*/
protected function sendNotifications(Blueprint $blueprint, array $recipients)
protected function sendNotifications(BlueprintInterface $blueprint, array $recipients)
{
$now = Carbon::now('utc')->toDateTimeString();
@@ -178,7 +180,7 @@ class NotificationSyncer
}, $recipients)
);
if ($blueprint instanceof MailableBlueprint) {
if ($blueprint instanceof MailableInterface) {
$this->mailNotifications($blueprint, $recipients);
}
}
@@ -186,10 +188,10 @@ class NotificationSyncer
/**
* Mail a notification to a list of users.
*
* @param MailableBlueprint $blueprint
* @param MailableInterface $blueprint
* @param User[] $recipients
*/
protected function mailNotifications(MailableBlueprint $blueprint, array $recipients)
protected function mailNotifications(MailableInterface $blueprint, array $recipients)
{
foreach ($recipients as $user) {
if ($user->shouldEmail($blueprint::getType())) {
@@ -213,10 +215,10 @@ class NotificationSyncer
* Construct an array of attributes to be stored in a notification record in
* the database, given a notification blueprint.
*
* @param Blueprint $blueprint
* @param BlueprintInterface $blueprint
* @return array
*/
protected function getAttributes(Blueprint $blueprint)
protected function getAttributes(BlueprintInterface $blueprint)
{
return [
'type' => $blueprint::getType(),

View File

@@ -8,14 +8,14 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users;
namespace Flarum\Core;
use Flarum\Core\Model;
use Flarum\Database\AbstractModel;
/**
* @todo document database columns with @property
*/
class PasswordToken extends Model
class PasswordToken extends AbstractModel
{
/**
* {@inheritdoc}
@@ -58,6 +58,6 @@ class PasswordToken extends Model
*/
public function user()
{
return $this->belongsTo('Flarum\Core\Users\User');
return $this->belongsTo('Flarum\Core\User');
}
}

View File

@@ -8,15 +8,15 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Groups;
namespace Flarum\Core;
use Flarum\Core\Model;
use Flarum\Database\AbstractModel;
use Illuminate\Database\Eloquent\Builder;
/**
* @todo document database columns with @property
*/
class Permission extends Model
class Permission extends AbstractModel
{
/**
* {@inheritdoc}
@@ -30,7 +30,7 @@ class Permission extends Model
*/
public function group()
{
return $this->belongsTo('Flarum\Core\Groups\Group', 'group_id');
return $this->belongsTo('Flarum\Core\Group', 'group_id');
}
/**

View File

@@ -8,50 +8,46 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Posts;
namespace Flarum\Core;
use DomainException;
use Flarum\Events\PostWasDeleted;
use Flarum\Core\Model;
use Flarum\Core\Users\User;
use Flarum\Core\Post\RegisteredTypesScope;
use Flarum\Event\PostWasDeleted;
use Flarum\Database\AbstractModel;
use Flarum\Core\User;
use Flarum\Core\Support\Locked;
use Flarum\Core\Support\VisibleScope;
use Flarum\Core\Support\EventGenerator;
use Flarum\Core\Support\ValidatesBeforeSave;
use Flarum\Core\Support\ScopeVisibilityTrait;
use Flarum\Core\Support\EventGeneratorTrait;
use Flarum\Core\Support\ValidateBeforeSaveTrait;
use Illuminate\Database\Eloquent\Builder;
/**
* @todo document database columns with @property
* @property int $id
* @property int $discussion_id
* @property int $number
* @property \Carbon\Carbon $time
* @property int|null $user_id
* @property string|null $type
* @property string|null $content
* @property \Carbon\Carbon|null $edit_time
* @property int|null $edit_user_id
* @property \Carbon\Carbon|null $hide_time
* @property int|null $hide_user_id
* @property \Flarum\Core\Discussion|null $discussion
* @property User|null $user
* @property User|null $editUser
* @property User|null $hideUser
*/
class Post extends Model
class Post extends AbstractModel
{
use EventGenerator;
use Locked;
use VisibleScope;
use ValidatesBeforeSave;
use EventGeneratorTrait;
use ScopeVisibilityTrait;
/**
* {@inheritdoc}
*/
protected $table = 'posts';
/**
* The validation rules for this model.
*
* @var array
*/
protected $rules = [
'discussion_id' => 'required|integer',
'time' => 'required|date',
'content' => 'required|max:65535',
'number' => 'integer',
'user_id' => 'integer',
'edit_time' => 'date',
'edit_user_id' => 'integer',
'hide_time' => 'date',
'hide_user_id' => 'integer',
];
/**
* {@inheritdoc}
*/
@@ -133,7 +129,7 @@ class Post extends Model
*/
public function discussion()
{
return $this->belongsTo('Flarum\Core\Discussions\Discussion', 'discussion_id');
return $this->belongsTo('Flarum\Core\Discussion', 'discussion_id');
}
/**
@@ -143,7 +139,7 @@ class Post extends Model
*/
public function user()
{
return $this->belongsTo('Flarum\Core\Users\User', 'user_id');
return $this->belongsTo('Flarum\Core\User', 'user_id');
}
/**
@@ -153,7 +149,7 @@ class Post extends Model
*/
public function editUser()
{
return $this->belongsTo('Flarum\Core\Users\User', 'edit_user_id');
return $this->belongsTo('Flarum\Core\User', 'edit_user_id');
}
/**
@@ -163,7 +159,7 @@ class Post extends Model
*/
public function hideUser()
{
return $this->belongsTo('Flarum\Core\Users\User', 'hide_user_id');
return $this->belongsTo('Flarum\Core\User', 'hide_user_id');
}
/**

View File

@@ -8,9 +8,11 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Posts;
namespace Flarum\Core\Post;
abstract class EventPost extends Post
use Flarum\Core\Post;
abstract class AbstractEventPost extends Post
{
/**
* Unserialize the content attribute from the database's JSON value.

View File

@@ -8,18 +8,22 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Posts;
namespace Flarum\Core\Post;
use DomainException;
use Flarum\Core\Formatter\Formatter;
use Flarum\Events\PostWasPosted;
use Flarum\Events\PostWasRevised;
use Flarum\Events\PostWasHidden;
use Flarum\Events\PostWasRestored;
use Flarum\Core\Users\User;
use Flarum\Core\Post;
use Flarum\Formatter\Formatter;
use Flarum\Event\PostWasPosted;
use Flarum\Event\PostWasRevised;
use Flarum\Event\PostWasHidden;
use Flarum\Event\PostWasRestored;
use Flarum\Core\User;
/**
* A standard comment in a discussion.
*
* @property string $parsed_content
* @property string $content_html
*/
class CommentPost extends Post
{
@@ -31,7 +35,7 @@ class CommentPost extends Post
/**
* The text formatter instance.
*
* @var Formatter
* @var \Flarum\Formatter\Formatter
*/
protected static $formatter;
@@ -161,7 +165,7 @@ class CommentPost extends Post
/**
* Get the text formatter instance.
*
* @return Formatter
* @return \Flarum\Formatter\Formatter
*/
public static function getFormatter()
{
@@ -171,7 +175,7 @@ class CommentPost extends Post
/**
* Set the text formatter instance.
*
* @param Formatter $formatter
* @param \Flarum\Formatter\Formatter $formatter
*/
public static function setFormatter(Formatter $formatter)
{

View File

@@ -8,7 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Posts;
namespace Flarum\Core\Post;
use Flarum\Core\Post;
/**
* A post which indicates that a discussion's title was changed.
@@ -16,7 +18,7 @@ namespace Flarum\Core\Posts;
* The content is stored as a sequential array containing the old title and the
* new title.
*/
class DiscussionRenamedPost extends EventPost implements MergeablePost
class DiscussionRenamedPost extends AbstractEventPost implements MergeableInterface
{
/**
* @inheritdoc

View File

@@ -8,7 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Posts;
namespace Flarum\Core\Post;
use Flarum\Core\Post;
/**
* A post that has the ability to be merged into an adjacent post.
@@ -17,7 +19,7 @@ namespace Flarum\Core\Posts;
* if a "discussion renamed" post is posted immediately after another
* "discussion renamed" post, then the new one will be merged into the old one.
*/
interface MergeablePost
interface MergeableInterface
{
/**
* Save the model, given that it is going to appear immediately after the

View File

@@ -8,7 +8,7 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Posts;
namespace Flarum\Core\Post;
use Illuminate\Database\Eloquent\ScopeInterface;
use Illuminate\Database\Eloquent\Builder;

View File

@@ -1,54 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Posts\Commands;
use Flarum\Core\Posts\PostRepository;
use Flarum\Events\PostWillBeDeleted;
use Flarum\Core\Support\DispatchesEvents;
class DeletePostHandler
{
use DispatchesEvents;
/**
* @var PostRepository
*/
protected $posts;
/**
* @param PostRepository $posts
*/
public function __construct(PostRepository $posts)
{
$this->posts = $posts;
}
/**
* @param DeletePost $command
* @return \Flarum\Core\Posts\Post
*/
public function handle(DeletePost $command)
{
$actor = $command->actor;
$post = $this->posts->findOrFail($command->postId, $actor);
$post->assertCan($actor, 'delete');
event(new PostWillBeDeleted($post, $actor, $command->data));
$post->delete();
$this->dispatchEventsFor($post);
return $post;
}
}

View File

@@ -1,118 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Core\Posts;
use Flarum\Core\Discussions\Discussion;
use Flarum\Core\Users\User;
use Flarum\Events\ModelAllow;
use Flarum\Events\RegisterPostTypes;
use Flarum\Events\ScopePostVisibility;
use Flarum\Support\ServiceProvider;
use Flarum\Extend;
use Carbon\Carbon;
class PostsServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application events.
*
* @return void
*/
public function boot()
{
Post::setValidator($this->app->make('validator'));
CommentPost::setFormatter($this->app->make('flarum.formatter'));
$this->registerPostTypes();
$events = $this->app->make('events');
$settings = $this->app->make('Flarum\Core\Settings\SettingsRepository');
$events->listen(ModelAllow::class, function (ModelAllow $event) use ($settings) {
if ($event->model instanceof Post) {
$post = $event->model;
$action = $event->action;
$actor = $event->actor;
if ($action === 'view' &&
(! $post->hide_time || $post->user_id == $actor->id || $post->can($actor, 'edit'))) {
return true;
}
// A post is allowed to be edited if the user has permission to moderate
// the discussion which it's in, or if they are the author and the post
// hasn't been deleted by someone else.
if ($action === 'edit') {
if ($post->discussion->can($actor, 'editPosts')) {
return true;
}
if ($post->user_id == $actor->id && (! $post->hide_time || $post->hide_user_id == $actor->id)) {
$allowEditing = $settings->get('allow_post_editing');
if ($allowEditing === '-1' ||
($allowEditing === 'reply' && $event->model->number >= $event->model->discussion->last_post_number) ||
($event->model->time->diffInMinutes(Carbon::now()) < $allowEditing)) {
return true;
}
}
}
if ($post->discussion->can($actor, $action.'Posts')) {
return true;
}
}
});
// When fetching a discussion's posts: if the user doesn't have permission
// to moderate the discussion, then they can't see posts that have been
// hidden by someone other than themself.
$events->listen(ScopePostVisibility::class, function (ScopePostVisibility $event) {
$user = $event->actor;
if (! $event->discussion->can($user, 'editPosts')) {
$event->query->where(function ($query) use ($user) {
$query->whereNull('hide_time')
->orWhere('user_id', $user->id);
});
}
});
}
/**
* Register post types.
*
* @return void
*/
public function registerPostTypes()
{
$models = [
'Flarum\Core\Posts\CommentPost',
'Flarum\Core\Posts\DiscussionRenamedPost'
];
event(new RegisterPostTypes($models));
foreach ($models as $model) {
Post::setModel($model::$type, $model);
}
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
}
}

View File

@@ -8,10 +8,11 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Discussions;
namespace Flarum\Core\Repository;
use Flarum\Core\Discussion;
use Illuminate\Database\Eloquent\Builder;
use Flarum\Core\Users\User;
use Flarum\Core\User;
use Illuminate\Database\Query\Expression;
class DiscussionRepository
@@ -32,7 +33,7 @@ class DiscussionRepository
*
* @param integer $id
* @param User $user
* @return \Flarum\Core\Discussions\Discussion
* @return \Flarum\Core\Discussion
*/
public function findOrFail($id, User $user = null)
{

View File

@@ -8,9 +8,10 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Groups;
namespace Flarum\Core\Repository;
use Flarum\Core\Users\User;
use Flarum\Core\Group;
use Flarum\Core\User;
use Illuminate\Database\Eloquent\Builder;
class GroupRepository

View File

@@ -8,9 +8,10 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Notifications;
namespace Flarum\Core\Repository;
use Flarum\Core\Users\User;
use Flarum\Core\Notification;
use Flarum\Core\User;
class NotificationRepository
{

View File

@@ -8,33 +8,13 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Posts;
namespace Flarum\Core\Repository;
use Illuminate\Database\Eloquent\Builder;
use Flarum\Core\Post;
use Flarum\Event\ScopePostVisibility;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Flarum\Core\Users\User;
use Flarum\Core\Discussions\Discussion;
use Flarum\Core\Discussions\Search\Fulltext\Driver;
// TODO: In some cases, the use of a post repository incurs extra query expense,
// because for every post retrieved we need to check if the discussion it's in
// is visible. Especially when retrieving a discussion's posts, we can end up
// with an inefficient chain of queries like this:
// 1. Api\Discussions\ShowAction: get discussion (will exit if not visible)
// 2. Discussion@postsVisibleTo: get discussion tags (for post visibility purposes)
// 3. Discussion@postsVisibleTo: get post IDs
// 4. EloquentPostRepository@getIndexForNumber: get discussion
// 5. EloquentPostRepository@getIndexForNumber: get discussion tags (for post visibility purposes)
// 6. EloquentPostRepository@getIndexForNumber: get post index for number
// 7. EloquentPostRepository@findWhere: get post IDs for discussion to check for discussion visibility
// 8. EloquentPostRepository@findWhere: get post IDs in visible discussions
// 9. EloquentPostRepository@findWhere: get posts
// 10. EloquentPostRepository@findWhere: eager load discussion onto posts
// 11. EloquentPostRepository@findWhere: get discussion tags to filter visible posts
// 12. Api\Discussions\ShowAction: eager load users
// 13. Api\Discussions\ShowAction: eager load groups
// 14. Api\Discussions\ShowAction: eager load mentions
// 14. Serializers\DiscussionSerializer: load discussion-user state
use Flarum\Core\User;
use Flarum\Core\Discussion;
class PostRepository
{
@@ -43,8 +23,8 @@ class PostRepository
* user, or throw an exception.
*
* @param integer $id
* @param \Flarum\Core\Users\User $actor
* @return \Flarum\Core\Posts\Post
* @param \Flarum\Core\User $actor
* @return \Flarum\Core\Post
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
@@ -64,13 +44,13 @@ class PostRepository
* are visible to a certain user, and/or using other criteria.
*
* @param array $where
* @param \Flarum\Core\Users\User|null $actor
* @param \Flarum\Core\User|null $actor
* @param array $sort
* @param integer $count
* @param integer $start
* @return \Illuminate\Database\Eloquent\Collection
*/
public function findWhere($where = [], User $actor = null, $sort = [], $count = null, $start = 0)
public function findWhere(array $where = [], User $actor = null, $sort = [], $count = null, $start = 0)
{
$query = Post::where($where)
->skip($start)
@@ -90,14 +70,24 @@ class PostRepository
* certain user.
*
* @param array $ids
* @param \Flarum\Core\Users\User|null $actor
* @param \Flarum\Core\User|null $actor
* @return \Illuminate\Database\Eloquent\Collection
*/
public function findByIds(array $ids, User $actor = null)
{
$visibleIds = $this->filterDiscussionVisibleTo($ids, $actor);
$discussions = $this->getDiscussionsForPosts($ids, $actor);
$posts = Post::with('discussion')->whereIn('id', $visibleIds)->get();
$posts = Post::whereIn('id', $ids)
->where(function ($query) use ($discussions, $actor) {
foreach ($discussions as $discussion) {
$query->orWhere(function ($query) use ($discussion, $actor) {
$query->where('discussion_id', $discussion->id);
event(new ScopePostVisibility($discussion, $query, $actor));
});
}
})
->get();
$posts = $posts->sort(function ($a, $b) use ($ids) {
$aPos = array_search($a->id, $ids);
@@ -110,7 +100,7 @@ class PostRepository
return $aPos < $bPos ? -1 : 1;
});
return $this->filterVisibleTo($posts, $actor);
return $posts;
}
/**
@@ -120,7 +110,7 @@ class PostRepository
*
* @param integer $discussionId
* @param integer $number
* @param \Flarum\Core\Users\User|null $actor
* @param \Flarum\Core\User|null $actor
* @return integer
*/
public function getIndexForNumber($discussionId, $number, User $actor = null)
@@ -142,29 +132,12 @@ class PostRepository
return $query->count();
}
protected function filterDiscussionVisibleTo($ids, User $actor)
protected function getDiscussionsForPosts($postIds, User $actor)
{
// For each post ID, we need to make sure that the discussion it's in
// is visible to the user.
if ($actor) {
$ids = Discussion::join('posts', 'discussions.id', '=', 'posts.discussion_id')
->whereIn('posts.id', $ids)
->whereVisibleTo($actor)
->get(['posts.id'])
->lists('id');
}
return $ids;
}
protected function filterVisibleTo($posts, User $actor)
{
if ($actor) {
$posts = $posts->filter(function ($post) use ($actor) {
return $post->can($actor, 'view');
});
}
return $posts;
return Discussion::whereIn('id', function ($query) use ($postIds) {
$query->select('discussion_id')->from('posts')->whereIn('id', $postIds);
})
->whereVisibleTo($actor)
->get();
}
}

View File

@@ -8,8 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Users;
namespace Flarum\Core\Repository;
use Flarum\Core\User;
use Illuminate\Database\Eloquent\Builder;
class UserRepository

View File

@@ -10,7 +10,7 @@
namespace Flarum\Core\Search;
abstract class RegexGambit implements Gambit
abstract class AbstractRegexGambit implements GambitInterface
{
/**
* The regex pattern to match the bit against.
@@ -22,7 +22,7 @@ abstract class RegexGambit implements Gambit
/**
* {@inheritdoc}
*/
public function apply(Search $search, $bit)
public function apply(AbstractSearch $search, $bit)
{
if ($matches = $this->match($bit)) {
list($negate) = array_splice($matches, 1, 1);
@@ -49,11 +49,11 @@ abstract class RegexGambit implements Gambit
/**
* Apply conditions to the search, given that the gambit was matched.
*
* @param Search $search The search object.
* @param AbstractSearch $search The search object.
* @param array $matches An array of matches from the search bit.
* @param bool $negate Whether or not the bit was negated, and thus whether
* or not the conditions should be negated.
* @return mixed
*/
abstract protected function conditions(Search $search, array $matches, $negate);
abstract protected function conditions(AbstractSearch $search, array $matches, $negate);
}

View File

@@ -10,7 +10,7 @@
namespace Flarum\Core\Search;
use Flarum\Core\Users\User;
use Flarum\Core\User;
use Illuminate\Database\Query\Builder;
/**
@@ -18,7 +18,7 @@ use Illuminate\Database\Query\Builder;
* the search query, the user performing the search, the fallback sort order,
* and a log of which gambits have been used.
*/
abstract class Search
abstract class AbstractSearch
{
/**
* @var Builder
@@ -36,7 +36,7 @@ abstract class Search
protected $defaultSort = [];
/**
* @var Gambit[]
* @var GambitInterface[]
*/
protected $activeGambits = [];
@@ -97,7 +97,7 @@ abstract class Search
/**
* Get a list of the gambits that are active in this search.
*
* @return Gambit[]
* @return GambitInterface[]
*/
public function getActiveGambits()
{
@@ -107,10 +107,10 @@ abstract class Search
/**
* Add a gambit as being active in this search.
*
* @param Gambit $gambit
* @param GambitInterface $gambit
* @return void
*/
public function addActiveGambit(Gambit $gambit)
public function addActiveGambit(GambitInterface $gambit)
{
$this->activeGambits[] = $gambit;
}

View File

@@ -10,15 +10,15 @@
namespace Flarum\Core\Search;
trait AppliesParametersToSearch
trait ApplySearchParametersTrait
{
/**
* Apply sort criteria to a discussion search.
*
* @param Search $search
* @param AbstractSearch $search
* @param array $sort
*/
protected function applySort(Search $search, array $sort = null)
protected function applySort(AbstractSearch $search, array $sort = null)
{
$sort = $sort ?: $search->getDefaultSort();
@@ -34,10 +34,10 @@ trait AppliesParametersToSearch
}
/**
* @param Search $search
* @param AbstractSearch $search
* @param int $offset
*/
protected function applyOffset(Search $search, $offset)
protected function applyOffset(AbstractSearch $search, $offset)
{
if ($offset > 0) {
$search->getQuery()->skip($offset);
@@ -45,10 +45,10 @@ trait AppliesParametersToSearch
}
/**
* @param Search $search
* @param AbstractSearch $search
* @param int|null $limit
*/
protected function applyLimit(Search $search, $limit)
protected function applyLimit(AbstractSearch $search, $limit)
{
if ($limit > 0) {
$search->getQuery()->take($limit);

View File

@@ -8,16 +8,16 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Discussions\Search;
namespace Flarum\Core\Search\Discussion;
use Flarum\Core\Search\Search;
use Flarum\Core\Search\AbstractSearch;
/**
* An object which represents the internal state of a search for discussions:
* the search query, the user performing the search, the fallback sort order,
* relevant post information, and a log of which gambits have been used.
*/
class DiscussionSearch extends Search
class DiscussionSearch extends AbstractSearch
{
/**
* {@inheritdoc}

View File

@@ -8,16 +8,15 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Discussions\Search;
namespace Flarum\Core\Search\Discussion;
use Flarum\Core\Discussions\Discussion;
use Flarum\Core\Search\AppliesParametersToSearch;
use Flarum\Core\Discussion;
use Flarum\Core\Search\ApplySearchParametersTrait;
use Flarum\Core\Search\SearchCriteria;
use Flarum\Core\Search\SearcherInterface;
use Flarum\Core\Search\GambitManager;
use Flarum\Core\Discussions\DiscussionRepository;
use Flarum\Core\Posts\PostRepository;
use Flarum\Events\DiscussionSearchWillBePerformed;
use Flarum\Core\Repository\DiscussionRepository;
use Flarum\Core\Repository\PostRepository;
use Flarum\Event\DiscussionSearchWillBePerformed;
use Flarum\Core\Search\SearchResults;
use Illuminate\Database\Eloquent\Collection;
@@ -27,7 +26,7 @@ use Illuminate\Database\Eloquent\Collection;
*/
class DiscussionSearcher
{
use AppliesParametersToSearch;
use ApplySearchParametersTrait;
/**
* @var GambitManager
@@ -82,6 +81,7 @@ class DiscussionSearcher
$this->applyOffset($search, $offset);
$this->applyLimit($search, $limit + 1);
// TODO: inject dispatcher
event(new DiscussionSearchWillBePerformed($search, $criteria));
// Execute the search query and retrieve the results. We get one more
@@ -89,7 +89,9 @@ class DiscussionSearcher
// results. If there are, we will get rid of that extra result.
$discussions = $query->get();
if ($areMoreResults = ($limit > 0 && $discussions->count() > $limit)) {
$areMoreResults = $limit > 0 && $discussions->count() > $limit;
if ($areMoreResults) {
$discussions->pop();
}

View File

@@ -8,9 +8,9 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Discussions\Search\Fulltext;
namespace Flarum\Core\Search\Discussion\Fulltext;
interface Driver
interface DriverInterface
{
/**
* Return an array of arrays of post IDs, grouped by discussion ID, which

View File

@@ -8,11 +8,11 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Discussions\Search\Fulltext;
namespace Flarum\Core\Search\Discussion\Fulltext;
use Flarum\Core\Posts\Post;
use Flarum\Core\Post;
class MySqlFulltextDriver implements Driver
class MySqlFulltextDriver implements DriverInterface
{
/**
* {@inheritdoc}

View File

@@ -8,15 +8,15 @@
* file that was distributed with this source code.
*/
namespace Flarum\Core\Discussions\Search\Gambits;
namespace Flarum\Core\Search\Discussion\Gambit;
use Flarum\Core\Discussions\Search\DiscussionSearch;
use Flarum\Core\Users\UserRepository;
use Flarum\Core\Search\RegexGambit;
use Flarum\Core\Search\Search;
use Flarum\Core\Search\Discussion\DiscussionSearch;
use Flarum\Core\Repository\UserRepository;
use Flarum\Core\Search\AbstractRegexGambit;
use Flarum\Core\Search\AbstractSearch;
use LogicException;
class AuthorGambit extends RegexGambit
class AuthorGambit extends AbstractRegexGambit
{
/**
* {@inheritdoc}
@@ -29,7 +29,7 @@ class AuthorGambit extends RegexGambit
protected $users;
/**
* @param UserRepository $users
* @param \Flarum\Core\Repository\UserRepository $users
*/
public function __construct(UserRepository $users)
{
@@ -39,7 +39,7 @@ class AuthorGambit extends RegexGambit
/**
* {@inheritdoc}
*/
protected function conditions(Search $search, array $matches, $negate)
protected function conditions(AbstractSearch $search, array $matches, $negate)
{
if (! $search instanceof DiscussionSearch) {
throw new LogicException('This gambit can only be applied on a DiscussionSearch');

Some files were not shown because too many files have changed in this diff Show More