1
0
mirror of https://github.com/flarum/core.git synced 2025-08-08 17:36:38 +02:00

Upgrade to L5 + huge refactor + more. closes #2

New stuff:
- Signup + email confirmation.
- Updated authentication strategy with remember cookies. closes #5
- New search system with some example gambits! This is cool - check out
the source. Fulltext drivers will be implemented as decorators
overriding the EloquentPostRepository’s findByContent method.
- Lay down the foundation for bootstrapping the Ember app.
- Update Web layer’s asset manager to properly publish CSS/JS files.
- Console commands to run installation migrations and seeds.

Refactoring:
- New structure: move models, repositories, commands, and events into
their own namespaces, rather than grouping by entity.
- All events are classes.
- Use L5 middleware and command bus implementations.
- Clearer use of repositories and the Active Record pattern.
Repositories are used only for retrieval of ActiveRecord objects, and
then save/delete operations are called directly on those ActiveRecords.
This way, we don’t over-abstract at the cost of Eloquent magic, but
testing is still easy.
- Refactor of Web layer so that it uses the Actions routing
architecture.
- “Actor” concept instead of depending on Laravel’s Auth.
- General cleanup!
This commit is contained in:
Toby Zerner
2015-02-24 20:33:18 +10:30
parent 0e4e44c358
commit 2c46888db5
266 changed files with 5562 additions and 4658 deletions

View File

@@ -0,0 +1,89 @@
<?php namespace Flarum\Api\Actions;
class ApiParams
{
protected $params;
public function __construct(array $params)
{
$this->params = $params;
}
public function get($key, $default = null)
{
return array_get($this->params, $key, $default);
}
public function range($key, $default = null, $min = null, $max = null)
{
$value = (int) $this->get($key, $default);
if (! is_null($min)) {
$value = max($value, $min);
}
if (! is_null($max)) {
$value = min($value, $max);
}
return $value;
}
public function included($available)
{
$requested = explode(',', $this->get('include'));
return array_intersect((array) $available, $requested);
}
// public function explodeIds($ids)
// {
// return array_unique(array_map('intval', array_filter(explode(',', $ids))));
// }
public function in($key, $options)
{
$value = $this->get($key);
if (array_key_exists($key, $options)) {
return $options[$key];
}
if (! in_array($value, $options)) {
$value = reset($options);
}
return $value;
}
public function sort($options)
{
$criteria = (string) $this->get('sort', '');
$order = null;
if ($criteria && $criteria[0] == '-') {
$order = 'desc';
$criteria = substr($criteria, 1);
}
if (! in_array($criteria, $options)) {
$criteria = reset($options);
}
if ($criteria && ! $order) {
$order = 'asc';
}
return [
'field' => $criteria,
'order' => $order,
'string' => ($order == 'desc' ? '-' : '').$criteria
];
}
public function start()
{
return $this->range('start', 0, 0);
}
public function count($default, $max = 100)
{
return $this->range('count', $default, 1, $max);
}
}

View File

@@ -0,0 +1,130 @@
<?php namespace Flarum\Api\Actions;
use Illuminate\Http\Request;
use Illuminate\Contracts\Bus\Dispatcher;
use Tobscure\JsonApi\Document;
use Flarum\Core\Support\Actor;
use Flarum\Api\Events\CommandWillBeDispatched;
use Flarum\Api\Events\WillRespondWithDocument;
use Flarum\Web\Actions\Action;
use Config;
use App;
use Response;
abstract class BaseAction extends Action
{
abstract protected function run(ApiParams $params);
public function __construct(Actor $actor, Dispatcher $bus)
{
$this->actor = $actor;
$this->bus = $bus;
}
public function handle(Request $request, $routeParams = [])
{
$this->registerErrorHandlers(); // @todo convert to middleware and add to route group?
$params = array_merge($request->all(), $routeParams);
return $this->call($params);
}
public function call($params = [])
{
$params = new ApiParams($params);
return $this->run($params);
}
public function hydrate($object, $params)
{
foreach ($params as $k => $v) {
$object->$k = $v;
}
}
protected function dispatch($command, $params)
{
$this->event(new CommandWillBeDispatched($command, $params));
return $this->bus->dispatch($command);
}
protected function event($event)
{
event($event);
}
public function document()
{
return new Document;
}
protected function buildUrl($route, $params = [], $input = [])
{
$url = route('flarum.api.'.$route, $params);
$queryString = $input ? '?'.http_build_query($input) : '';
return $url.$queryString;
}
protected function respondWithoutContent($statusCode = 204, $headers = [])
{
return Response::make('', $statusCode, $headers);
}
protected function respondWithArray($array, $statusCode = 200, $headers = [])
{
return Response::json($array, $statusCode, $headers);
}
protected function respondWithDocument($document, $statusCode = 200, $headers = [])
{
$headers['Content-Type'] = 'application/vnd.api+json';
$this->event(new WillRespondWithDocument($document, $statusCode, $headers));
return $this->respondWithArray($document->toArray(), $statusCode, $headers);
}
protected function registerErrorHandlers()
{
// if (! Config::get('app.debug')) {
// App::error(function ($exception, $code) {
// return $this->respondWithError('ApplicationError', $code);
// });
// }
// App::error(function (ModelNotFoundException $exception) {
// return $this->respondWithError('ResourceNotFound', 404);
// });
// App::error(function (ValidationFailureException $exception) {
// $errors = [];
// foreach ($exception->getErrors()->getMessages() as $field => $messages) {
// $errors[] = [
// 'code' => 'ValidationFailure',
// 'detail' => implode("\n", $messages),
// 'path' => $field
// ];
// }
// return $this->respondWithErrors($errors, 422);
// });
}
protected function respondWithErrors($errors, $httpCode = 500)
{
return Response::json(['errors' => $errors], $httpCode);
}
protected function respondWithError($error, $httpCode = 500, $detail = null)
{
$error = ['code' => $error];
if ($detail) {
$error['detail'] = $detail;
}
return $this->respondWithErrors([$error], $httpCode);
}
}

View File

@@ -1,45 +1,41 @@
<?php namespace Flarum\Api\Actions\Discussions;
use Event;
use Flarum\Core\Discussions\Commands\StartDiscussionCommand;
use Flarum\Core\Discussions\Commands\ReadDiscussionCommand;
use Flarum\Core\Users\User;
use Flarum\Api\Actions\Base;
use Flarum\Core\Commands\StartDiscussionCommand;
use Flarum\Core\Commands\ReadDiscussionCommand;
use Flarum\Api\Actions\BaseAction;
use Flarum\Api\Actions\ApiParams;
use Flarum\Api\Serializers\DiscussionSerializer;
class Create extends Base
class CreateAction extends BaseAction
{
/**
* Start a new discussion.
*
* @return Response
*/
protected function run()
protected function run(ApiParams $params)
{
// By default, the only required attributes of a discussion are the
// title and the content. We'll extract these from the request data
// title and the content. We'll extract these from the rbaseequest data
// and pass them through to the StartDiscussionCommand.
$title = $this->input('discussions.title');
$content = $this->input('discussions.content');
$user = User::current();
$command = new StartDiscussionCommand($title, $content, $user);
$title = $params->get('discussions.title');
$content = $params->get('discussions.content');
$user = $this->actor->getUser();
Event::fire('Flarum.Api.Actions.Discussions.Create.WillExecuteCommand', [$command, $this->document]);
$discussion = $this->commandBus->execute($command);
$command = new StartDiscussionCommand($title, $content, $user, app('flarum.forum'));
$discussion = $this->dispatch($command, $params);
// After creating the discussion, we assume that the user has seen all
// of the posts in the discussion; thus, we will mark the discussion
// as read if they are logged in.
if ($user->exists) {
$command = new ReadDiscussionCommand($discussion->id, $user, 1);
$this->commandBus->execute($command);
$this->dispatch($command, $params);
}
$serializer = new DiscussionSerializer(['posts']);
$this->document->setPrimaryElement($serializer->resource($discussion));
$document = $this->document()->setPrimaryElement($serializer->resource($discussion));
return $this->respondWithDocument();
return $this->respondWithDocument($document);
}
}

View File

@@ -0,0 +1,23 @@
<?php namespace Flarum\Api\Actions\Discussions;
use Flarum\Core\Commands\DeleteDiscussionCommand;
use Flarum\Api\Actions\BaseAction;
use Flarum\Api\Actions\ApiParams;
class DeleteAction extends BaseAction
{
/**
* Delete a discussion.
*
* @return Response
*/
protected function run(ApiParams $params)
{
$discussionId = $params->get('id');
$command = new DeleteDiscussionCommand($discussionId, $this->actor->getUser());
$this->dispatch($command, $params);
return $this->respondWithoutContent();
}
}

View File

@@ -0,0 +1,81 @@
<?php namespace Flarum\Api\Actions\Discussions;
use Flarum\Core\Search\Discussions\DiscussionSearchCriteria;
use Flarum\Core\Search\Discussions\DiscussionSearcher;
use Flarum\Core\Support\Actor;
use Flarum\Api\Actions\BaseAction;
use Flarum\Api\Actions\ApiParams;
use Flarum\Api\Serializers\DiscussionSerializer;
class IndexAction extends BaseAction
{
/**
* The discussion searcher.
*
* @var DiscussionSearcher
*/
protected $searcher;
/**
* Instantiate the action.
*
* @param DiscussionSearcher $searcher
*/
public function __construct(Actor $actor, DiscussionSearcher $searcher)
{
$this->actor = $actor;
$this->searcher = $searcher;
}
/**
* Show a list of discussions.
*
* @todo custom rate limit for this function? determined by if $key was valid?
* @return Response
*/
protected function run(ApiParams $params)
{
$query = $params->get('q');
$start = $params->start();
$include = $params->included(['startPost', 'lastPost', 'relevantPosts']);
$count = $params->count(20, 50);
$sort = $params->sort(['', 'lastPost', 'replies', 'created']);
$relations = array_merge(['startUser', 'lastUser'], $include);
// Set up the discussion finder with our search criteria, and get the
// requested range of results with the necessary relations loaded.
$criteria = new DiscussionSearchCriteria($this->actor->getUser(), $query, $sort['field'], $sort['order']);
$load = array_merge($relations, ['state']);
$results = $this->searcher->search($criteria, $count, $start, $load);
$document = $this->document();
if (($total = $results->getTotal()) !== null) {
$document->addMeta('total', $total);
}
// If there are more results, then we need to construct a URL to the
// next results page and add that to the metadata. We do this by
// compacting all of the valid query parameters which have been
// specified.
if ($results->areMoreResults()) {
$start += $count;
$include = implode(',', $include);
$sort = $sort['string'];
$input = array_filter(compact('query', 'sort', 'start', 'count', 'include'));
$moreUrl = $this->buildUrl('discussions.index', [], $input);
} else {
$moreUrl = '';
}
$document->addMeta('moreUrl', $moreUrl);
// Finally, we can set up the discussion serializer and use it to create
// a collection of discussion results.
$serializer = new DiscussionSerializer($relations);
$document->setPrimaryElement($serializer->collection($results->getDiscussions()));
return $this->respondWithDocument($document);
}
}

View File

@@ -1,15 +1,24 @@
<?php namespace Flarum\Api\Actions\Discussions;
use Flarum\Core\Discussions\Discussion;
use Flarum\Core\Posts\PostRepository;
use Flarum\Api\Actions\Base;
use Flarum\Core\Support\Actor;
use Flarum\Core\Repositories\DiscussionRepositoryInterface as DiscussionRepository;
use Flarum\Core\Repositories\PostRepositoryInterface as PostRepository;
use Flarum\Api\Actions\BaseAction;
use Flarum\Api\Actions\ApiParams;
use Flarum\Api\Actions\Posts\GetsPostsForDiscussion;
use Flarum\Api\Serializers\DiscussionSerializer;
class Show extends Base
class ShowAction extends BaseAction
{
use GetsPostsForDiscussion;
/**
* The discussion repository.
*
* @var DiscussionRepository
*/
protected $discussions;
/**
* The post repository.
*
@@ -22,8 +31,10 @@ class Show extends Base
*
* @param PostRepository $posts
*/
public function __construct(PostRepository $posts)
public function __construct(Actor $actor, DiscussionRepository $discussions, PostRepository $posts)
{
$this->actor = $actor;
$this->discussions = $discussions;
$this->posts = $posts;
}
@@ -32,15 +43,15 @@ class Show extends Base
*
* @return Response
*/
protected function run()
protected function run(ApiParams $params)
{
$include = $this->included(['startPost', 'lastPost', 'posts']);
$include = $params->included(['startPost', 'lastPost', 'posts']);
$discussion = Discussion::whereCanView()->findOrFail($this->param('id'));
$discussion = $this->discussions->findOrFail($params->get('id'), $this->actor->getUser());
if (in_array('posts', $include)) {
$relations = ['user', 'user.groups', 'editUser', 'hideUser'];
$discussion->posts = $this->getPostsForDiscussion($this->posts, $discussion->id, $relations);
$discussion->posts = $this->getPostsForDiscussion($params, $discussion->id)->load($relations);
$include = array_merge($include, array_map(function ($relation) {
return 'posts.'.$relation;
@@ -52,8 +63,8 @@ class Show extends Base
// relations, we will specify that we want the 'posts' relation to be
// linked so that a list of post IDs will show up in the response.
$serializer = new DiscussionSerializer($include, ['posts']);
$this->document->setPrimaryElement($serializer->resource($discussion));
$document = $this->document()->setPrimaryElement($serializer->resource($discussion));
return $this->respondWithDocument();
return $this->respondWithDocument($document);
}
}

View File

@@ -0,0 +1,54 @@
<?php namespace Flarum\Api\Actions\Discussions;
use Flarum\Core\Commands\EditDiscussionCommand;
use Flarum\Core\Commands\ReadDiscussionCommand;
use Flarum\Core\Exceptions\PermissionDeniedException;
use Flarum\Api\Actions\BaseAction;
use Flarum\Api\Actions\ApiParams;
use Flarum\Api\Serializers\DiscussionSerializer;
class UpdateAction extends BaseAction
{
/**
* Edit a discussion. Allows renaming the discussion, and updating its read
* state with regards to the current user.
*
* @return Response
*/
protected function run(ApiParams $params)
{
$discussionId = $params->get('id');
$user = $this->actor->getUser();
// First, we will run the EditDiscussionCommand. This will update the
// discussion's direct properties; by default, this is just the title.
// As usual, however, we will fire an event to allow plugins to update
// additional properties.
if ($data = array_except($params->get('discussions'), ['readNumber'])) {
$command = new EditDiscussionCommand($discussionId, $user);
$this->hydrate($command, $params->get('discussions'));
$discussion = $this->dispatch($command, $params);
}
// Next, if a read number was specified in the request, we will run the
// ReadDiscussionCommand.
//
// @todo Currently, if the user doesn't have permission to edit a
// discussion, they're unable to update their readNumber because a
// PermissionsDeniedException is thrown by the
// EditDiscussionCommand above. So this needs to be extracted into
// its own endpoint.
if ($readNumber = $params->get('discussions.readNumber')) {
$command = new ReadDiscussionCommand($discussionId, $user, $readNumber);
$this->dispatch($command, $params);
}
// Presumably, the discussion was updated successfully. (One of the command
// handlers would have thrown an exception if not.) We set this
// discussion as our document's primary element.
$serializer = new DiscussionSerializer(['addedPosts', 'addedPosts.user']);
$document = $this->document()->setPrimaryElement($serializer->resource($discussion));
return $this->respondWithDocument($document);
}
}

View File

@@ -1,50 +1,47 @@
<?php namespace Flarum\Api\Actions\Posts;
use Event;
use Flarum\Core\Posts\Commands\PostReplyCommand;
use Flarum\Core\Discussions\Commands\ReadDiscussionCommand;
use Flarum\Core\Users\User;
use Flarum\Api\Actions\Base;
use Flarum\Core\Commands\PostReplyCommand;
use Flarum\Core\Commands\ReadDiscussionCommand;
use Flarum\Api\Actions\ApiParams;
use Flarum\Api\Actions\BaseAction;
use Flarum\Api\Serializers\PostSerializer;
class Create extends Base
class CreateAction extends BaseAction
{
/**
* Reply to a discussion.
*
* @return Response
*/
protected function run()
protected function run(ApiParams $params)
{
$user = User::current();
$user = $this->actor->getUser();
// We've received a request to post a reply. By default, the only
// required attributes of a post is the ID of the discussion to post in,
// the post content, and the author's user account. Let's set up a
// command with this information. We also fire an event to allow plugins
// to add data to the command.
$discussionId = $this->input('posts.links.discussion');
$content = $this->input('posts.content');
$discussionId = $params->get('posts.links.discussion');
$content = $params->get('posts.content');
$command = new PostReplyCommand($discussionId, $content, $user);
Event::fire('Flarum.Api.Actions.Posts.Create.WillExecuteCommand', [$command]);
$post = $this->commandBus->execute($command);
$post = $this->dispatch($command, $params);
// After replying, we assume that the user has seen all of the posts
// in the discussion; thus, we will mark the discussion as read if
// they are logged in.
if ($user->exists) {
$command = new ReadDiscussionCommand($discussionId, $user, $post->number);
$this->commandBus->execute($command);
$this->dispatch($command, $params);
}
// Presumably, the post was created successfully. (The command handler
// would have thrown an exception if not.) We set this post as our
// document's primary element.
$serializer = new PostSerializer;
$this->document->setPrimaryElement($serializer->resource($post));
$document = $this->document()->setPrimaryElement($serializer->resource($post));
return $this->respondWithDocument(201);
return $this->respondWithDocument($document, 201);
}
}

View File

@@ -0,0 +1,23 @@
<?php namespace Flarum\Api\Actions\Posts;
use Flarum\Core\Commands\DeletePostCommand;
use Flarum\Api\Actions\ApiParams;
use Flarum\Api\Actions\BaseAction;
class DeleteAction extends BaseAction
{
/**
* Delete a post.
*
* @return Response
*/
protected function run(ApiParams $params)
{
$postId = $params->get('id');
$command = new DeletePostCommand($postId, $this->actor->getUser());
$this->dispatch($command, $params);
return $this->respondWithoutContent();
}
}

View File

@@ -0,0 +1,31 @@
<?php namespace Flarum\Api\Actions\Posts;
use Flarum\Core\Repositories\PostRepositoryInterface;
use Flarum\Core\Models\User;
use Flarum\Api\Actions\ApiParams;
trait GetsPostsForDiscussion
{
protected function getPostsForDiscussion(ApiParams $params, $discussionId)
{
$sort = $params->sort(['time']);
$count = $params->count(20, 50);
$user = $this->actor->getUser();
if (($near = $params->get('near')) > 1) {
$start = $this->posts->getIndexForNumber($discussionId, $near, $user);
$start = max(0, $start - $count / 2);
} else {
$start = 0;
}
return $this->posts->findByDiscussion(
$discussionId,
$user,
$sort['field'],
$sort['order'] ?: 'asc',
$count,
$start
);
}
}

View File

@@ -1,29 +1,31 @@
<?php namespace Flarum\Api\Actions\Posts;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Flarum\Core\Posts\Post;
use Flarum\Core\Posts\PostRepository;
use Flarum\Api\Actions\Base;
use Flarum\Core\Repositories\PostRepositoryInterface;
use Flarum\Core\Support\Actor;
use Flarum\Api\Actions\BaseAction;
use Flarum\Api\Actions\ApiParams;
use Flarum\Api\Serializers\PostSerializer;
class Index extends Base
class IndexAction extends BaseAction
{
use GetsPostsForDiscussion;
/**
* The post repository.
*
* @var PostRepository
* @var Post
*/
protected $posts;
/**
* Instantiate the action.
*
* @param PostRepository $posts
* @param Post $posts
*/
public function __construct(PostRepository $posts)
public function __construct(Actor $actor, PostRepositoryInterface $posts)
{
$this->actor = $actor;
$this->posts = $posts;
}
@@ -32,16 +34,17 @@ class Index extends Base
*
* @return Response
*/
protected function run()
protected function run(ApiParams $params)
{
$postIds = (array) $this->input('ids');
$postIds = (array) $params->get('ids');
$include = ['user', 'user.groups', 'editUser', 'hideUser'];
$user = $this->actor->getUser();
if (count($postIds)) {
$posts = $this->posts->findMany($postIds, $include);
$posts = $this->posts->findByIds($postIds, $user);
} else {
$discussionId = $this->input('discussions');
$posts = $this->getPostsForDiscussion($this->posts, $discussionId, $include);
$discussionId = $params->get('discussions');
$posts = $this->getPostsForDiscussion($params, $discussionId, $user);
}
if (! count($posts)) {
@@ -52,8 +55,8 @@ class Index extends Base
// a post resource or collection, depending on how many posts were
// requested.
$serializer = new PostSerializer($include);
$this->document->setPrimaryElement($serializer->collection($posts));
$document = $this->document()->setPrimaryElement($serializer->collection($posts->load($include)));
return $this->respondWithDocument();
return $this->respondWithDocument($document);
}
}

View File

@@ -0,0 +1,42 @@
<?php namespace Flarum\Api\Actions\Posts;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Flarum\Core\Repositories\PostRepositoryInterface;
use Flarum\Core\Support\Actor;
use Flarum\Api\Actions\BaseAction;
use Flarum\Api\Actions\ApiParams;
use Flarum\Api\Serializers\PostSerializer;
class ShowAction extends BaseAction
{
protected $posts;
public function __construct(Actor $actor, PostRepositoryInterface $posts)
{
$this->actor = $actor;
$this->posts = $posts;
}
/**
* Show a single post by ID.
*
* @return Response
*/
protected function run(ApiParams $params)
{
$id = $params->get('id');
$posts = $this->posts->findOrFail($id, $this->actor->getUser());
$include = $params->included(['discussion', 'replyTo']);
$relations = array_merge(['user', 'editUser', 'hideUser'], $include);
$posts->load($relations);
// Finally, we can set up the post serializer and use it to create
// a post resource or collection, depending on how many posts were
// requested.
$serializer = new PostSerializer($relations);
$document = $this->document()->setPrimaryElement($serializer->resource($posts->first()));
return $this->respondWithDocument($document);
}
}

View File

@@ -1,39 +1,34 @@
<?php namespace Flarum\Api\Actions\Posts;
use Event;
use Flarum\Core\Posts\Commands\EditPostCommand;
use Flarum\Core\Users\User;
use Flarum\Api\Actions\Base;
use Flarum\Core\Commands\EditPostCommand;
use Flarum\Api\Actions\ApiParams;
use Flarum\Api\Actions\BaseAction;
use Flarum\Api\Serializers\PostSerializer;
class Update extends Base
class UpdateAction extends BaseAction
{
/**
* Edit a post. Allows revision of content, and hiding/unhiding.
*
* @return Response
*/
protected function run()
protected function run(ApiParams $params)
{
$postId = $this->param('id');
$postId = $params->get('id');
// EditPost is a single command because we don't want to allow partial
// updates (i.e. if we were to run one command and then another, if the
// second one failed, the first one would still have succeeded.)
$command = new EditPostCommand($postId, User::current());
$this->fillCommandWithInput($command, 'posts');
Event::fire('Flarum.Api.Actions.Posts.Update.WillExecuteCommand', [$command]);
$post = $this->commandBus->execute($command);
$command = new EditPostCommand($postId, $this->actor->getUser());
$this->hydrate($command, $params->get('posts'));
$post = $this->dispatch($command, $params);
// Presumably, the post was updated successfully. (The command handler
// would have thrown an exception if not.) We set this post as our
// document's primary element.
$serializer = new PostSerializer;
$this->document->setPrimaryElement($serializer->resource($post));
$document = $this->document()->setPrimaryElement($serializer->resource($post));
return $this->respondWithDocument();
return $this->respondWithDocument($document);
}
}

View File

@@ -0,0 +1,43 @@
<?php namespace Flarum\Api\Actions;
use Illuminate\Http\JsonResponse;
use Illuminate\Contracts\Bus\Dispatcher;
use Flarum\Core\Commands\GenerateAccessTokenCommand;
use Flarum\Core\Repositories\UserRepositoryInterface;
use Flarum\Api\Actions\BaseAction;
class TokenAction extends BaseAction
{
protected $users;
public function __construct(UserRepositoryInterface $users, Dispatcher $bus)
{
$this->users = $users;
$this->bus = $bus;
}
/**
* Log in and return a token.
*
* @return Response
*/
public function run(ApiParams $params)
{
$identification = $params->get('identification');
$password = $params->get('password');
$user = $this->users->findByIdentification($identification);
if (! $user || ! $user->checkPassword($password)) {
return $this->respondWithError('invalidLogin', 401);
}
$command = new GenerateAccessTokenCommand($user->id);
$token = $this->dispatch($command, $params);
return new JsonResponse([
'token' => $token->id,
'userId' => $user->id
]);
}
}

View File

@@ -1,39 +1,36 @@
<?php namespace Flarum\Api\Actions\Users;
use Event;
use Flarum\Core\Users\Commands\RegisterUserCommand;
use Flarum\Core\Users\User;
use Flarum\Api\Actions\Base;
use Flarum\Core\Commands\RegisterUserCommand;
use Flarum\Api\Actions\ApiParams;
use Flarum\Api\Actions\BaseAction;
use Flarum\Api\Serializers\UserSerializer;
class Create extends Base
class CreateAction extends BaseAction
{
/**
* Register a user.
*
* @return Response
*/
protected function run()
protected function run(ApiParams $params)
{
// We've received a request to register a user. By default, the only
// required attributes of a user is the username, email, and password.
// Let's set up a command with this information. We also fire an event
// to allow plugins to add data to the command.
$username = $this->input('users.username');
$email = $this->input('users.email');
$password = $this->input('users.password');
$command = new RegisterUserCommand($username, $email, $password, User::current());
$username = $params->get('users.username');
$email = $params->get('users.email');
$password = $params->get('users.password');
Event::fire('Flarum.Api.Actions.Users.Create.WillExecuteCommand', [$command]);
$user = $this->commandBus->execute($command);
$command = new RegisterUserCommand($username, $email, $password, $this->actor->getUser());
$this->dispatch($command, $params);
// Presumably, the user was created successfully. (The command handler
// would have thrown an exception if not.) We set this post as our
// document's primary element.
$serializer = new UserSerializer;
$this->document->setPrimaryElement($serializer->resource($user));
$document = $this->document()->setPrimaryElement($serializer->resource($user));
return $this->respondWithDocument(201);
return $this->respondWithDocument($document, 201);
}
}

View File

@@ -0,0 +1,23 @@
<?php namespace Flarum\Api\Actions\Users;
use Flarum\Core\Commands\DeleteUserCommand;
use Flarum\Api\Actions\ApiParams;
use Flarum\Api\Actions\BaseAction;
class DeleteAction extends BaseAction
{
/**
* Delete a user.
*
* @return Response
*/
protected function run(ApiParams $params)
{
$userId = $params->get('id');
$command = new DeleteUserCommand($userId, $this->actor->getUser());
$this->dispatch($command, $params);
return $this->respondWithoutContent();
}
}

View File

@@ -5,11 +5,11 @@ use Flarum\Core\Users\UserFinder;
use Flarum\Api\Actions\Base;
use Flarum\Api\Serializers\UserSerializer;
class Index extends Base
class IndexAction extends BaseAction
{
/**
* The user finder.
*
*
* @var UserFinder
*/
protected $finder;

View File

@@ -0,0 +1,38 @@
<?php namespace Flarum\Api\Actions\Users;
use Flarum\Core\Repositories\UserRepositoryInterface;
use Flarum\Core\Support\Actor;
use Flarum\Api\Actions\ApiParams;
use Flarum\Api\Actions\BaseAction;
use Flarum\Api\Serializers\UserSerializer;
class ShowAction extends BaseAction
{
protected $actor;
protected $users;
public function __construct(Actor $actor, UserRepositoryInterface $users)
{
$this->actor = $actor;
$this->users = $users;
}
/**
* Show a single user.
*
* @return Response
*/
public function run(ApiParams $params)
{
$user = $this->users->findOrFail($params->get('id'), $this->actor->getUser());
// Set up the user serializer, which we will use to create the
// document's primary resource. We will specify that we want the
// 'groups' relation to be included by default.
$serializer = new UserSerializer(['groups']);
$document = $this->document()->setPrimaryElement($serializer->resource($user));
return $this->respondWithDocument($document);
}
}

View File

@@ -1,13 +1,11 @@
<?php namespace Flarum\Api\Actions\Users;
use Event;
use Flarum\Core\Users\Commands\EditUserCommand;
use Flarum\Core\Users\User;
use Flarum\Api\Actions\Base;
use Flarum\Core\Commands\EditUserCommand;
use Flarum\Api\Actions\ApiParams;
use Flarum\Api\Actions\BaseAction;
use Flarum\Api\Serializers\UserSerializer;
class Update extends Base
class UpdateAction extends BaseAction
{
/**
* Edit a user. Allows renaming the user, changing their email, and setting
@@ -15,26 +13,23 @@ class Update extends Base
*
* @return Response
*/
protected function run()
protected function run(ApiParams $params)
{
$userId = $this->param('id');
$userId = $params->get('id');
// EditUser is a single command because we don't want to allow partial
// updates (i.e. if we were to run one command and then another, if the
// second one failed, the first one would still have succeeded.)
$command = new EditUserCommand($userId, User::current());
$this->fillCommandWithInput($command, 'users');
Event::fire('Flarum.Api.Actions.Users.Update.WillExecuteCommand', [$command]);
$user = $this->commandBus->execute($command);
$command = new EditUserCommand($userId, $this->actor->getUser());
$this->hydrate($command, $params->get('users'));
$this->dispatch($command);
// Presumably, the user was updated successfully. (The command handler
// would have thrown an exception if not.) We set this user as our
// document's primary element.
$serializer = new UserSerializer;
$this->document->setPrimaryElement($serializer->resource($user));
$document = $this->document()->setPrimaryElement($serializer->resource($user));
return $this->respondWithDocument();
return $this->respondWithDocument($document);
}
}

View File

@@ -0,0 +1,29 @@
<?php namespace Flarum\Api;
use Illuminate\Support\ServiceProvider;
use Flarum\Api\Serializers\BaseSerializer;
class ApiServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application events.
*
* @return void
*/
public function boot()
{
include __DIR__.'/routes.php';
BaseSerializer::setActor($this->app['Flarum\Core\Support\Actor']);
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('Flarum\Core\Support\Actor');
}
}

View File

@@ -0,0 +1,14 @@
<?php namespace Flarum\Api\Events;
class CommandWillBeDispatched
{
public $command;
public $params;
public function __construct($command, $params)
{
$this->command = $command;
$this->params = $params;
}
}

View File

@@ -0,0 +1,17 @@
<?php namespace Flarum\Api\Events;
class SerializeAttributes
{
public $serializer;
public $model;
public $attributes;
public function __construct($serializer, $model, &$attributes)
{
$this->serializer = $serializer;
$this->model = $model;
$this->attributes = $attributes;
}
}

View File

@@ -0,0 +1,23 @@
<?php namespace Flarum\Api\Events;
class SerializeRelationship
{
public $serializer;
public $model;
public $type;
public $name;
public $relations;
public function __construct($serializer, $model, $type, $name, $relations)
{
$this->serializer = $serializer;
$this->model = $model;
$this->type = $type;
$this->name = $name;
$this->relations = $relations;
}
}

View File

@@ -0,0 +1,17 @@
<?php namespace Flarum\Api\Events;
class WillRespondWithDocument
{
public $document;
public $statusCode;
public $headers;
public function __construct($document, &$statusCode, &$headers)
{
$this->document = $document;
$this->statusCode = $statusCode;
$this->headers = $headers;
}
}

View File

@@ -0,0 +1,29 @@
<?php namespace Flarum\Api\Middleware;
use Flarum\Core\Models\AccessToken;
use Flarum\Core\Support\Actor;
use Closure;
class LoginWithHeaderMiddleware
{
protected $actor;
protected $prefix = 'Token ';
public function __construct(Actor $actor)
{
$this->actor = $actor;
}
public function handle($request, Closure $next)
{
$header = $request->headers->get('authorization');
if (starts_with($header, $this->prefix) &&
($token = substr($header, strlen($this->prefix))) &&
($accessToken = AccessToken::where('id', $token)->first())) {
$this->actor->setUser($accessToken->user);
}
return $next($request);
}
}

View File

@@ -0,0 +1,62 @@
<?php namespace Flarum\Api\Serializers;
use Tobscure\JsonApi\SerializerAbstract;
use Flarum\Api\Events\SerializeAttributes;
use Flarum\Api\Events\SerializeRelationship;
use Flarum\Core\Support\Actor;
/**
* A base serializer to call Flarum events at common serialization points.
*/
abstract class BaseSerializer extends SerializerAbstract
{
/**
* The actor who is requesting the serialized objects.
*
* @var \Flarum\Core\Support\Actor
*/
protected static $actor;
/**
* Set the actor who is requesting the serialized objects.
*
* @param \Flarum\Core\Support\Actor $actor
* @return void
*/
public static function setActor(Actor $actor)
{
static::$actor = $actor;
}
/**
* Fire an event to allow custom serialization of attributes.
*
* @param mixed $model The model to serialize.
* @param array $attributes Attributes that have already been serialized.
* @return array
*/
protected function attributesEvent($model, $attributes = [])
{
event(new SerializeAttributes($this, $model, $attributes));
return $attributes;
}
/**
* Fire an event to allow for custom links and includes.
*
* @param string $name
* @param array $arguments
* @return void
*/
public function __call($name, $arguments)
{
if ($link = starts_with($name, 'link') || starts_with($name, 'include')) {
$model = isset($arguments[0]) ? $arguments[0] : null;
$relations = isset($arguments[1]) ? $arguments[1] : null;
$type = $link ? 'link' : 'include';
$name = substr($name, strlen($type));
return event(new SerializeRelationship($this, $model, $type, $name, $relations), null, true);
}
}
}

View File

@@ -1,15 +1,9 @@
<?php namespace Flarum\Api\Serializers;
use Flarum\Core\Discussions\Discussion;
use Flarum\Core\Models\Discussion;
class DiscussionBasicSerializer extends BaseSerializer
{
/**
* The name to use for Flarum events.
* @var string
*/
protected static $eventName = 'DiscussionBasic';
/**
* The resource type.
* @var string
@@ -18,7 +12,7 @@ class DiscussionBasicSerializer extends BaseSerializer
/**
* Serialize attributes of a Discussion model for JSON output.
*
*
* @param Discussion $discussion The Discussion model to serialize.
* @return array
*/
@@ -35,7 +29,7 @@ class DiscussionBasicSerializer extends BaseSerializer
/**
* Get the URL templates where this resource and its related resources can
* be accessed.
*
*
* @return array
*/
protected function href()

View File

@@ -1,16 +1,10 @@
<?php namespace Flarum\Api\Serializers;
use Flarum\Core\Discussions\Discussion;
use Flarum\Core\Discussions\DiscussionState;
use Flarum\Core\Models\Discussion;
use Flarum\Core\Models\DiscussionState;
class DiscussionSerializer extends DiscussionBasicSerializer
{
/**
* The name to use for Flarum events.
* @var string
*/
protected static $eventName = 'Discussion';
/**
* Default relations to include.
* @var array
@@ -27,22 +21,25 @@ class DiscussionSerializer extends DiscussionBasicSerializer
{
$attributes = parent::attributes($discussion);
$state = $discussion->state;
$user = static::$actor->getUser();
$state = $discussion->stateFor($user);
$attributes += [
'commentsCount' => (int) $discussion->comments_count,
'startTime' => $discussion->start_time->toRFC3339String(),
'lastTime' => $discussion->last_time ? $discussion->last_time->toRFC3339String() : null,
'lastPostNumber' => $discussion->last_post_number,
'canReply' => $discussion->permission('reply'),
'canEdit' => $discussion->permission('edit'),
'canDelete' => $discussion->permission('delete'),
'canReply' => $discussion->can($user, 'reply'),
'canEdit' => $discussion->can($user, 'edit'),
'canDelete' => $discussion->can($user, 'delete'),
'readTime' => $state && $state->read_time ? $state->read_time->toRFC3339String() : null,
'readNumber' => $state ? (int) $state->read_number : 0
];
return $this->attributesEvent($discussion, $attributes);
$this->attributesEvent($discussion, $attributes);
return $attributes;
}
/**
@@ -53,7 +50,9 @@ class DiscussionSerializer extends DiscussionBasicSerializer
*/
public function linkPosts(Discussion $discussion)
{
return (new PostBasicSerializer)->collection($discussion->posts()->whereCanView()->orderBy('time', 'asc')->ids());
$user = static::$actor->getUser();
return (new PostBasicSerializer)->collection($discussion->posts()->whereCan($user, 'view')->orderBy('time', 'asc')->ids());
}
/**

View File

@@ -1,6 +1,6 @@
<?php namespace Flarum\Api\Serializers;
use Flarum\Core\Groups\Group;
use Flarum\Core\Models\Group;
class GroupSerializer extends BaseSerializer
{

View File

@@ -1,6 +1,6 @@
<?php namespace Flarum\Api\Serializers;
use Flarum\Core\Posts\Post;
use Flarum\Core\Models\Post;
class PostBasicSerializer extends BaseSerializer
{

View File

@@ -1,16 +1,10 @@
<?php namespace Flarum\Api\Serializers;
use Flarum\Core\Posts\Post;
use Flarum\Core\Users\User;
use Flarum\Core\Models\Post;
use Flarum\Core\Models\User;
class PostSerializer extends PostBasicSerializer
{
/**
* The name to use for Flarum events.
* @var string
*/
protected static $eventName = 'Post';
/**
* Default relations to link.
* @var array
@@ -32,7 +26,7 @@ class PostSerializer extends PostBasicSerializer
protected function attributes(Post $post)
{
$attributes = parent::attributes($post);
$user = User::current();
$user = static::$actor->getUser();
unset($attributes['content']);

View File

@@ -1,6 +1,6 @@
<?php namespace Flarum\Api\Serializers;
use Flarum\Core\Users\User;
use Flarum\Core\Models\User;
class UserBasicSerializer extends BaseSerializer
{
@@ -18,7 +18,7 @@ class UserBasicSerializer extends BaseSerializer
/**
* Serialize attributes of a User model for JSON output.
*
*
* @param User $user The User model to serialize.
* @return array
*/
@@ -36,7 +36,7 @@ class UserBasicSerializer extends BaseSerializer
/**
* Get the URL templates where this resource and its related resources can
* be accessed.
*
*
* @return array
*/
protected function href()

View File

@@ -1,6 +1,6 @@
<?php namespace Flarum\Api\Serializers;
use Flarum\Core\Users\User;
use Flarum\Core\Models\User;
class UserSerializer extends UserBasicSerializer
{
@@ -18,7 +18,7 @@ class UserSerializer extends UserBasicSerializer
/**
* Serialize attributes of a User model for JSON output.
*
*
* @param User $user The User model to serialize.
* @return array
*/
@@ -26,22 +26,38 @@ class UserSerializer extends UserBasicSerializer
{
$attributes = parent::attributes($user);
$actorUser = static::$actor->getUser();
$canEdit = $user->can($actorUser, 'edit');
$attributes += [
'joinTime' => $user->join_time ? $user->join_time->toRFC3339String() : null,
'lastSeenTime' => $user->last_seen_time ? $user->last_seen_time->toRFC3339String() : null,
'readTime' => $user->read_time ? $user->read_time->toRFC3339String() : null,
'discussionsCount' => (int) $user->discussions_count,
'postsCount' => (int) $user->posts_count,
'canEdit' => $user->permission('edit'),
'canDelete' => $user->permission('delete'),
'canEdit' => $canEdit,
'canDelete' => $user->can($actorUser, 'delete'),
];
if ($canEdit) {
$attributes += [
'isActivated' => $user->is_activated,
'email' => $user->email,
'isConfirmed' => $user->is_confirmed
];
}
if ($user->id === $actorUser->id) {
$attributes += [
'readTime' => $user->read_time ? $user->read_time->toRFC3339String() : null,
];
}
return $this->attributesEvent($user, $attributes);
}
/**
* Get a collection containing a user's groups.
*
*
* @param User $user
* @param array $relations
* @return Tobscure\JsonApi\Collection

View File

@@ -2,36 +2,18 @@
$action = function ($class) {
return function () use ($class) {
$action = App::make($class);
$request = app('request');
$parameters = Route::current()->parameters();
$action = $this->app->make($class);
$request = $this->app['request']->instance();
$parameters = $this->app['router']->current()->parameters();
return $action->handle($request, $parameters);
};
};
// @todo refactor into a unit-testable class
Route::filter('attemptLogin', function($route, $request) {
$prefix = 'Token ';
if (starts_with($request->headers->get('authorization'), $prefix)) {
$token = substr($request->headers->get('authorization'), strlen($prefix));
if ($user = Flarum\Core\Users\User::where('token', $token)->first()) {
Auth::setUser($user);
}
}
});
Route::group(['prefix' => 'api', 'middleware' => 'Flarum\Api\Middleware\LoginWithHeaderMiddleware'], function () use ($action) {
Route::group(['prefix' => 'api', 'before' => 'attemptLogin'], function () use ($action) {
/*
|--------------------------------------------------------------------------
| Auth
|--------------------------------------------------------------------------
*/
// Login
Route::post('auth/login', [
'as' => 'flarum.api.auth.login',
'uses' => $action('Flarum\Api\Actions\Auth\Login')
Route::post('token', [
'as' => 'flarum.api.token',
'uses' => $action('Flarum\Api\Actions\TokenAction')
]);
/*
@@ -43,31 +25,31 @@ Route::group(['prefix' => 'api', 'before' => 'attemptLogin'], function () use ($
// List users
Route::get('users', [
'as' => 'flarum.api.users.index',
'uses' => $action('Flarum\Api\Actions\Users\Index')
'uses' => $action('Flarum\Api\Actions\Users\IndexAction')
]);
// Register a user
Route::post('users', [
'as' => 'flarum.api.users.create',
'uses' => $action('Flarum\Api\Actions\Users\Create')
'uses' => $action('Flarum\Api\Actions\Users\CreateAction')
]);
// Get a single user
Route::get('users/{id}', [
'as' => 'flarum.api.users.show',
'uses' => $action('Flarum\Api\Actions\Users\Show')
'uses' => $action('Flarum\Api\Actions\Users\ShowAction')
]);
// Edit a user
Route::put('users/{id}', [
'as' => 'flarum.api.users.update',
'uses' => $action('Flarum\Api\Actions\Users\Update')
'uses' => $action('Flarum\Api\Actions\Users\UpdateAction')
]);
// Delete a user
Route::delete('users/{id}', [
'as' => 'flarum.api.users.delete',
'uses' => $action('Flarum\Api\Actions\Users\Delete')
'uses' => $action('Flarum\Api\Actions\Users\DeleteAction')
]);
/*
@@ -79,13 +61,13 @@ Route::group(['prefix' => 'api', 'before' => 'attemptLogin'], function () use ($
// List activity
Route::get('activity', [
'as' => 'flarum.api.activity.index',
'uses' => $action('Flarum\Api\Actions\Activity\Index')
'uses' => $action('Flarum\Api\Actions\Activity\IndexAction')
]);
// List notifications for the current user
Route::get('notifications', [
'as' => 'flarum.api.notifications.index',
'uses' => $action('Flarum\Api\Actions\Notifications\Index')
'uses' => $action('Flarum\Api\Actions\Notifications\IndexAction')
]);
/*
@@ -97,31 +79,31 @@ Route::group(['prefix' => 'api', 'before' => 'attemptLogin'], function () use ($
// List discussions
Route::get('discussions', [
'as' => 'flarum.api.discussions.index',
'uses' => $action('Flarum\Api\Actions\Discussions\Index')
'uses' => $action('Flarum\Api\Actions\Discussions\IndexAction')
]);
// Create a discussion
Route::post('discussions', [
'as' => 'flarum.api.discussions.create',
'uses' => $action('Flarum\Api\Actions\Discussions\Create')
'uses' => $action('Flarum\Api\Actions\Discussions\CreateAction')
]);
// Show a single discussion
Route::get('discussions/{id}', [
'as' => 'flarum.api.discussions.show',
'uses' => $action('Flarum\Api\Actions\Discussions\Show')
'uses' => $action('Flarum\Api\Actions\Discussions\ShowAction')
]);
// Edit a discussion
Route::put('discussions/{id}', [
'as' => 'flarum.api.discussions.update',
'uses' => $action('Flarum\Api\Actions\Discussions\Update')
'uses' => $action('Flarum\Api\Actions\Discussions\UpdateAction')
]);
// Delete a discussion
Route::delete('discussions/{id}', [
'as' => 'flarum.api.discussions.delete',
'uses' => $action('Flarum\Api\Actions\Discussions\Delete')
'uses' => $action('Flarum\Api\Actions\Discussions\DeleteAction')
]);
/*
@@ -133,32 +115,32 @@ Route::group(['prefix' => 'api', 'before' => 'attemptLogin'], function () use ($
// List posts, usually for a discussion
Route::get('posts', [
'as' => 'flarum.api.posts.index',
'uses' => $action('Flarum\Api\Actions\Posts\Index')
'uses' => $action('Flarum\Api\Actions\Posts\IndexAction')
]);
// Create a post
// @todo consider 'discussions/{id}/links/posts'?
Route::post('posts', [
'as' => 'flarum.api.posts.create',
'uses' => $action('Flarum\Api\Actions\Posts\Create')
'uses' => $action('Flarum\Api\Actions\Posts\CreateAction')
]);
// Show a single or multiple posts by ID
Route::get('posts/{id}', [
'as' => 'flarum.api.posts.show',
'uses' => $action('Flarum\Api\Actions\Posts\Show')
'uses' => $action('Flarum\Api\Actions\Posts\ShowAction')
]);
// Edit a post
Route::put('posts/{id}', [
'as' => 'flarum.api.posts.update',
'uses' => $action('Flarum\Api\Actions\Posts\Update')
'uses' => $action('Flarum\Api\Actions\Posts\UpdateAction')
]);
// Delete a post
Route::delete('posts/{id}', [
'as' => 'flarum.api.posts.delete',
'uses' => $action('Flarum\Api\Actions\Posts\Delete')
'uses' => $action('Flarum\Api\Actions\Posts\DeleteAction')
]);
/*
@@ -170,31 +152,31 @@ Route::group(['prefix' => 'api', 'before' => 'attemptLogin'], function () use ($
// List groups
Route::get('groups', [
'as' => 'flarum.api.groups.index',
'uses' => $action('Flarum\Api\Actions\Groups\Index')
'uses' => $action('Flarum\Api\Actions\Groups\IndexAction')
]);
// Create a group
Route::post('groups', [
'as' => 'flarum.api.groups.create',
'uses' => $action('Flarum\Api\Actions\Groups\Create')
'uses' => $action('Flarum\Api\Actions\Groups\CreateAction')
]);
// Show a single group
Route::get('groups/{id}', [
'as' => 'flarum.api.groups.show',
'uses' => $action('Flarum\Api\Actions\Groups\Show')
'uses' => $action('Flarum\Api\Actions\Groups\ShowAction')
]);
// Edit a group
Route::put('groups/{id}', [
'as' => 'flarum.api.groups.update',
'uses' => $action('Flarum\Api\Actions\Groups\Update')
'uses' => $action('Flarum\Api\Actions\Groups\UpdateAction')
]);
// Delete a group
Route::delete('groups/{id}', [
'as' => 'flarum.api.groups.delete',
'uses' => $action('Flarum\Api\Actions\Groups\Delete')
'uses' => $action('Flarum\Api\Actions\Groups\DeleteAction')
]);
});

View File

@@ -0,0 +1,22 @@
<?php namespace Flarum\Console;
use Illuminate\Support\ServiceProvider;
class ConsoleServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application events.
*
* @return void
*/
public function boot()
{
$this->commands('Flarum\Console\InstallCommand');
$this->commands('Flarum\Console\SeedCommand');
}
public function register()
{
}
}

View File

@@ -0,0 +1,76 @@
<?php namespace Flarum\Console;
use Illuminate\Console\Command;
use Illuminate\Foundation\Application;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
class InstallCommand extends Command {
/**
* The console command name.
*
* @var string
*/
protected $name = 'flarum:install';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Run Flarum\'s installation migrations and seeds.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct(Application $app)
{
parent::__construct();
$this->app = $app;
}
/**
* Execute the console command.
*
* @return mixed
*/
public function fire()
{
$path = str_replace($this->laravel['path.base'].'/', '', __DIR__.'/../../migrations');
$this->call('migrate', ['--path' => $path]);
$this->call('db:seed', ['--class' => 'Flarum\Core\Seeders\ConfigTableSeeder']);
$this->call('db:seed', ['--class' => 'Flarum\Core\Seeders\GroupsTableSeeder']);
$this->call('db:seed', ['--class' => 'Flarum\Core\Seeders\PermissionsTableSeeder']);
}
/**
* Get the console command arguments.
*
* @return array
*/
protected function getArguments()
{
return [
// ['example', InputArgument::REQUIRED, 'An example argument.'],
];
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return [
// ['example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null],
];
}
}

View File

@@ -0,0 +1,71 @@
<?php namespace Flarum\Console;
use Illuminate\Console\Command;
use Illuminate\Foundation\Application;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
class SeedCommand extends Command {
/**
* The console command name.
*
* @var string
*/
protected $name = 'flarum:seed';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Seed Flarum\'s database with fake data.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct(Application $app)
{
parent::__construct();
$this->app = $app;
}
/**
* Execute the console command.
*
* @return mixed
*/
public function fire()
{
$this->call('db:seed', ['--class' => 'Flarum\Core\Seeders\UsersTableSeeder']);
$this->call('db:seed', ['--class' => 'Flarum\Core\Seeders\DiscussionsTableSeeder']);
}
/**
* Get the console command arguments.
*
* @return array
*/
protected function getArguments()
{
return [
// ['example', InputArgument::REQUIRED, 'An example argument.'],
];
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return [
// ['example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null],
];
}
}

View File

@@ -1,4 +1,4 @@
<?php namespace Flarum\Core\Users\Commands;
<?php namespace Flarum\Core\Commands;
class ConfirmEmailCommand
{

View File

@@ -1,4 +1,4 @@
<?php namespace Flarum\Core\Discussions\Commands;
<?php namespace Flarum\Core\Commands;
class DeleteDiscussionCommand
{

View File

@@ -1,4 +1,4 @@
<?php namespace Flarum\Core\Posts\Commands;
<?php namespace Flarum\Core\Commands;
class DeletePostCommand
{

View File

@@ -1,4 +1,4 @@
<?php namespace Flarum\Core\Users\Commands;
<?php namespace Flarum\Core\Commands;
class DeleteUserCommand
{

View File

@@ -1,4 +1,4 @@
<?php namespace Flarum\Core\Discussions\Commands;
<?php namespace Flarum\Core\Commands;
class EditDiscussionCommand
{

View File

@@ -1,4 +1,4 @@
<?php namespace Flarum\Core\Posts\Commands;
<?php namespace Flarum\Core\Commands;
class EditPostCommand
{

View File

@@ -1,4 +1,4 @@
<?php namespace Flarum\Core\Users\Commands;
<?php namespace Flarum\Core\Commands;
class EditUserCommand
{

View File

@@ -0,0 +1,11 @@
<?php namespace Flarum\Core\Commands;
class GenerateAccessTokenCommand
{
public $userId;
public function __construct($userId)
{
$this->userId = $userId;
}
}

View File

@@ -1,7 +1,7 @@
<?php namespace Flarum\Core\Posts\Commands;
<?php namespace Flarum\Core\Commands;
class PostReplyCommand
{
{
public $discussionId;
public $content;

View File

@@ -1,4 +1,4 @@
<?php namespace Flarum\Core\Discussions\Commands;
<?php namespace Flarum\Core\Commands;
class ReadDiscussionCommand
{

View File

@@ -1,7 +1,9 @@
<?php namespace Flarum\Core\Users\Commands;
<?php namespace Flarum\Core\Commands;
class RegisterUserCommand
{
public $forum;
public $user;
public $username;
@@ -10,11 +12,12 @@ class RegisterUserCommand
public $password;
public function __construct($username, $email, $password, $user)
public function __construct($username, $email, $password, $user, $forum)
{
$this->username = $username;
$this->email = $email;
$this->password = $password;
$this->user = $user;
$this->forum = $forum;
}
}

View File

@@ -1,4 +1,4 @@
<?php namespace Flarum\Core\Discussions\Commands;
<?php namespace Flarum\Core\Commands;
class StartDiscussionCommand
{
@@ -8,10 +8,13 @@ class StartDiscussionCommand
public $user;
public function __construct($title, $content, $user)
public $forum;
public function __construct($title, $content, $user, $forum)
{
$this->title = $title;
$this->content = $content;
$this->user = $user;
$this->forum = $forum;
}
}

View File

@@ -0,0 +1,179 @@
<?php namespace Flarum\Core;
use Illuminate\Bus\Dispatcher as Bus;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\ServiceProvider;
use Flarum\Core\Formatter\FormatterManager;
use Flarum\Core\Models\CommentPost;
use Flarum\Core\Models\Post;
use Flarum\Core\Models\Model;
use Flarum\Core\Models\Forum;
use Flarum\Core\Models\User;
use Flarum\Core\Models\Discussion;
use Flarum\Core\Search\GambitManager;
class CoreServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application events.
*
* @return void
*/
public function boot(Dispatcher $events, Bus $bus)
{
$this->loadViewsFrom(__DIR__.'../../views', 'flarum');
$this->registerEventHandlers($events);
$this->registerPostTypes();
$this->registerPermissions();
$this->registerGambits();
$this->setupModels();
$bus->mapUsing(function ($command) {
return Bus::simpleMapping(
$command, 'Flarum\Core\Commands', 'Flarum\Core\Handlers\Commands'
);
});
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
// Register a singleton entity that represents this forum. This entity
// will be used to check for global forum permissions (like viewing the
// forum, registering, and starting discussions.)
$this->app->singleton('flarum.forum', 'Flarum\Core\Models\Forum');
// Register the extensions manager object. This manages a list of
// available extensions, and provides functionality to enable/disable
// them.
$this->app->singleton('flarum.extensions', 'Flarum\Core\Support\Extensions\Manager');
$this->app->bind('flarum.discussionFinder', 'Flarum\Core\Discussions\DiscussionFinder');
$this->app->singleton('flarum.formatter', function () {
$formatter = new FormatterManager($this->app);
$formatter->add('basic', 'Flarum\Core\Formatter\BasicFormatter');
return $formatter;
});
$this->app->bind(
'Flarum\Core\Repositories\DiscussionRepositoryInterface',
'Flarum\Core\Repositories\EloquentDiscussionRepository'
);
$this->app->bind(
'Flarum\Core\Repositories\PostRepositoryInterface',
'Flarum\Core\Repositories\EloquentPostRepository'
);
$this->app->bind(
'Flarum\Core\Repositories\UserRepositoryInterface',
'Flarum\Core\Repositories\EloquentUserRepository'
);
}
public function registerGambits()
{
$this->app->bind('Flarum\Core\Search\GambitManager', function () {
$gambits = new GambitManager($this->app);
$gambits->add('Flarum\Core\Search\Discussions\Gambits\AuthorGambit');
$gambits->add('Flarum\Core\Search\Discussions\Gambits\UnreadGambit');
$gambits->setFulltextGambit('Flarum\Core\Search\Discussions\Gambits\FulltextGambit');
return $gambits;
});
}
public function registerPostTypes()
{
Post::addType('comment', 'Flarum\Core\Models\CommentPost');
Post::addType('renamed', 'Flarum\Core\Models\RenamedPost');
CommentPost::setFormatter($this->app['flarum.formatter']);
}
public function registerEventHandlers($events)
{
$events->subscribe('Flarum\Core\Handlers\Events\DiscussionMetadataUpdater');
$events->subscribe('Flarum\Core\Handlers\Events\UserMetadataUpdater');
$events->subscribe('Flarum\Core\Handlers\Events\RenamedPostCreator');
$events->subscribe('Flarum\Core\Handlers\Events\EmailConfirmationMailer');
}
public function setupModels()
{
Model::setForum($this->app['flarum.forum']);
Model::setValidator($this->app['validator']);
User::setHasher($this->app['hash']);
}
public function registerPermissions()
{
Forum::grantPermission(function ($grant, $user, $permission) {
return $user->hasPermission($permission, 'forum');
});
Post::grantPermission(function ($grant, $user, $permission) {
return $user->hasPermission($permission, 'post');
});
// Grant view access to a post only if the user can also view the
// discussion which the post is in. Also, the if the post is hidden,
// the user must have edit permissions too.
Post::grantPermission('view', function ($grant) {
$grant->whereCan('view', 'discussion');
});
Post::demandPermission('view', function ($demand) {
$demand->whereNull('hide_user_id')
->orWhereCan('edit');
});
// Allow a user to edit their own post, unless it has been hidden by
// someone else.
Post::grantPermission('edit', function ($grant, $user) {
$grant->whereCan('editOwn')
->where('user_id', $user->id);
});
Post::demandPermission('editOwn', function ($demand, $user) {
$demand->whereNull('hide_user_id');
if ($user) {
$demand->orWhere('hide_user_id', $user->id);
}
});
User::grantPermission(function ($grant, $user, $permission) {
return $user->hasPermission($permission, 'forum');
});
// Grant view access to a user if the user can view the forum.
User::grantPermission('view', function ($grant, $user) {
$grant->whereCan('view', 'forum');
});
// Allow a user to edit their own account.
User::grantPermission('edit', function ($grant, $user) {
$grant->where('id', $user->id);
});
Discussion::grantPermission(function ($grant, $user, $permission) {
return $user->hasPermission($permission, 'discussion');
});
// Grant view access to a discussion if the user can view the forum.
Discussion::grantPermission('view', function ($grant, $user) {
$grant->whereCan('view', 'forum');
});
// Allow a user to edit their own discussion.
Discussion::grantPermission('edit', function ($grant, $user) {
if ($user->hasPermission('editOwn', 'discussion')) {
$grant->where('start_user_id', $user->id);
}
});
}
}

View File

@@ -0,0 +1,16 @@
<?php namespace Flarum\Core\Events;
use Flarum\Core\Models\DiscussionState;
class DiscussionStateWillBeSaved
{
public $state;
public $command;
public function __construct(DiscussionState $state, $command)
{
$this->state = $state;
$this->command = $command;
}
}

View File

@@ -1,6 +1,6 @@
<?php namespace Flarum\Core\Discussions\Events;
<?php namespace Flarum\Core\Events;
use Flarum\Core\Discussions\Discussion;
use Flarum\Core\Models\Discussion;
class DiscussionWasDeleted
{

View File

@@ -1,6 +1,6 @@
<?php namespace Flarum\Core\Discussions\Events;
<?php namespace Flarum\Core\Events;
use Flarum\Core\Discussions\DiscussionState;
use Flarum\Core\Models\DiscussionState;
class DiscussionWasRead
{

View File

@@ -1,7 +1,7 @@
<?php namespace Flarum\Core\Discussions\Events;
<?php namespace Flarum\Core\Events;
use Flarum\Core\Discussions\Discussion;
use Flarum\Core\Users\User;
use Flarum\Core\Models\Discussion;
use Flarum\Core\Models\User;
class DiscussionWasRenamed
{

View File

@@ -1,6 +1,6 @@
<?php namespace Flarum\Core\Discussions\Events;
<?php namespace Flarum\Core\Events;
use Flarum\Core\Discussions\Discussion;
use Flarum\Core\Models\Discussion;
class DiscussionWasStarted
{

View File

@@ -0,0 +1,16 @@
<?php namespace Flarum\Core\Events;
use Flarum\Core\Models\Discussion;
class DiscussionWillBeDeleted
{
public $discussion;
public $command;
public function __construct(Discussion $discussion, $command)
{
$this->discussion = $discussion;
$this->command = $command;
}
}

View File

@@ -0,0 +1,16 @@
<?php namespace Flarum\Core\Events;
use Flarum\Core\Models\Discussion;
class DiscussionWillBeSaved
{
public $discussion;
public $command;
public function __construct(Discussion $discussion, $command)
{
$this->discussion = $discussion;
$this->command = $command;
}
}

View File

@@ -1,6 +1,6 @@
<?php namespace Flarum\Core\Posts\Events;
<?php namespace Flarum\Core\Events;
use Flarum\Core\Posts\Post;
use Flarum\Core\Models\Post;
class PostWasDeleted
{

View File

@@ -1,6 +1,6 @@
<?php namespace Flarum\Core\Posts\Events;
<?php namespace Flarum\Core\Events;
use Flarum\Core\Posts\Post;
use Flarum\Core\Models\Post;
class PostWasHidden
{

View File

@@ -1,8 +1,8 @@
<?php namespace Flarum\Core\Posts\Events;
<?php namespace Flarum\Core\Events;
use Flarum\Core\Posts\Post;
use Flarum\Core\Models\Post;
class ReplyWasPosted
class PostWasPosted
{
public $post;

View File

@@ -1,6 +1,6 @@
<?php namespace Flarum\Core\Posts\Events;
<?php namespace Flarum\Core\Events;
use Flarum\Core\Posts\Post;
use Flarum\Core\Models\Post;
class PostWasRestored
{

View File

@@ -1,6 +1,6 @@
<?php namespace Flarum\Core\Posts\Events;
<?php namespace Flarum\Core\Events;
use Flarum\Core\Posts\Post;
use Flarum\Core\Models\Post;
class PostWasRevised
{

View File

@@ -0,0 +1,16 @@
<?php namespace Flarum\Core\Events;
use Flarum\Core\Models\Post;
class PostWillBeDeleted
{
public $post;
public $command;
public function __construct(Post $post, $command)
{
$this->post = $post;
$this->command = $command;
}
}

View File

@@ -0,0 +1,16 @@
<?php namespace Flarum\Core\Events;
use Flarum\Core\Models\Post;
class PostWillBeSaved
{
public $post;
public $command;
public function __construct(Post $post, $command)
{
$this->post = $post;
$this->command = $command;
}
}

View File

@@ -1,6 +1,6 @@
<?php namespace Flarum\Core\Users\Events;
<?php namespace Flarum\Core\Events;
use Flarum\Core\Users\User;
use Flarum\Core\Models\User;
class EmailWasChanged
{

View File

@@ -1,6 +1,6 @@
<?php namespace Flarum\Core\Users\Events;
<?php namespace Flarum\Core\Events;
use Flarum\Core\Users\User;
use Flarum\Core\Models\User;
class EmailWasConfirmed
{

View File

@@ -1,6 +1,6 @@
<?php namespace Flarum\Core\Users\Events;
<?php namespace Flarum\Core\Events;
use Flarum\Core\Users\User;
use Flarum\Core\Models\User;
class PasswordWasChanged
{

View File

@@ -1,6 +1,6 @@
<?php namespace Flarum\Core\Users\Events;
<?php namespace Flarum\Core\Events;
use Flarum\Core\Users\User;
use Flarum\Core\Models\User;
class UserWasActivated
{

View File

@@ -1,6 +1,6 @@
<?php namespace Flarum\Core\Users\Events;
<?php namespace Flarum\Core\Events;
use Flarum\Core\Users\User;
use Flarum\Core\Models\User;
class UserWasDeleted
{

View File

@@ -1,6 +1,6 @@
<?php namespace Flarum\Core\Users\Events;
<?php namespace Flarum\Core\Events;
use Flarum\Core\Users\User;
use Flarum\Core\Models\User;
class UserWasRegistered
{

View File

@@ -1,6 +1,6 @@
<?php namespace Flarum\Core\Users\Events;
<?php namespace Flarum\Core\Events;
use Flarum\Core\Users\User;
use Flarum\Core\Models\User;
class UserWasRenamed
{

View File

@@ -0,0 +1,16 @@
<?php namespace Flarum\Core\Events;
use Flarum\Core\Models\User;
class UserWillBeDeleted
{
public $user;
public $command;
public function __construct(User $user, $command)
{
$this->user = $user;
$this->command = $command;
}
}

View File

@@ -0,0 +1,16 @@
<?php namespace Flarum\Core\Events;
use Flarum\Core\Models\User;
class UserWillBeSaved
{
public $user;
public $command;
public function __construct(User $user, $command)
{
$this->user = $user;
$this->command = $command;
}
}

View File

@@ -1,4 +1,4 @@
<?php namespace Flarum\Core\Support\Exceptions;
<?php namespace Flarum\Core\Exceptions;
use Exception;

View File

@@ -1,4 +1,4 @@
<?php namespace Flarum\Core\Support\Exceptions;
<?php namespace Flarum\Core\Exceptions;
use Exception;

View File

@@ -1,38 +1,38 @@
<?php namespace Flarum\Core\Support\Exceptions;
<?php namespace Flarum\Core\Exceptions;
use Illuminate\Support\MessageBag;
class ValidationFailureException extends \InvalidArgumentException
{
protected $errors;
protected $input = array();
public function __construct($message = '', $code = 0, Exception $previous = null)
{
parent::__construct($message, $code, $previous);
$this->errors = new MessageBag;
}
public function setErrors(MessageBag $errors)
{
$this->errors = $errors;
return $this;
}
public function getErrors()
{
return $this->errors;
}
public function setInput(array $input)
{
$this->input = $input;
return $this;
}
public function getInput()
{
return $this->input;

View File

@@ -0,0 +1,6 @@
<?php namespace Flarum\Core\Extensions;
class Extension
{
}

View File

@@ -0,0 +1,6 @@
<?php namespace Flarum\Core\Extensions;
class ExtensionManager
{
}

View File

@@ -0,0 +1,36 @@
<?php namespace Flarum\Core\Handlers\Commands;
use Flarum\Core\Repositories\UserRepositoryInterface as UserRepository;
use Flarum\Core\Events\UserWillBeSaved;
use Flarum\Core\Support\DispatchesEvents;
class ConfirmEmailCommandHandler
{
use DispatchesEvents;
protected $users;
public function __construct(UserRepository $users)
{
$this->users = $users;
}
public function handle($command)
{
$user = $this->users->findOrFail($command->userId);
$user->assertConfirmationTokenValid($command->token);
$user->confirmEmail();
if (! $user->is_activated) {
$user->activate();
}
event(new UserWillBeSaved($user, $command));
$user->save();
$this->dispatchEventsFor($user);
return $user;
}
}

View File

@@ -0,0 +1,32 @@
<?php namespace Flarum\Core\Handlers\Commands;
use Flarum\Core\Repositories\DiscussionRepositoryInterface as DiscussionRepository;
use Flarum\Core\Events\DiscussionWillBeDeleted;
use Flarum\Core\Support\DispatchesEvents;
class DeleteDiscussionCommandHandler
{
use DispatchesEvents;
protected $discussions;
public function __construct(DiscussionRepository $discussions)
{
$this->discussions = $discussions;
}
public function handle($command)
{
$user = $command->user;
$discussion = $this->discussions->findOrFail($command->discussionId, $user);
$discussion->assertCan($user, 'delete');
event(new DiscussionWillBeDeleted($discussion, $command));
$discussion->delete();
$this->dispatchEventsFor($discussion);
return $discussion;
}
}

View File

@@ -0,0 +1,32 @@
<?php namespace Flarum\Core\Handlers\Commands;
use Flarum\Core\Repositories\PostRepositoryInterface as PostRepository;
use Flarum\Core\Events\PostWillBeDeleted;
use Flarum\Core\Support\DispatchesEvents;
class DeletePostCommandHandler
{
use DispatchesEvents;
protected $posts;
public function __construct(PostRepository $posts)
{
$this->posts = $posts;
}
public function handle($command)
{
$user = $command->user;
$post = $this->posts->findOrFail($command->postId, $user);
$post->assertCan($user, 'delete');
event(new PostWillBeDeleted($post, $command));
$post->delete();
$this->dispatchEventsFor($post);
return $post;
}
}

View File

@@ -0,0 +1,32 @@
<?php namespace Flarum\Core\Handlers\Commands;
use Flarum\Core\Repositories\UserRepositoryInterface as UserRepository;
use Flarum\Core\Events\UserWillBeDeleted;
use Flarum\Core\Support\DispatchesEvents;
class DeleteUserCommandHandler
{
use DispatchesEvents;
protected $users;
public function __construct(UserRepository $users)
{
$this->users = $users;
}
public function handle($command)
{
$user = $command->user;
$userToDelete = $this->users->findOrFail($command->userId, $user);
$userToDelete->assertCan($user, 'delete');
event(new UserWillBeDeleted($userToDelete, $command));
$userToDelete->delete();
$this->dispatchEventsFor($userToDelete);
return $userToDelete;
}
}

View File

@@ -1,14 +1,12 @@
<?php namespace Flarum\Core\Discussions\Commands;
<?php namespace Flarum\Core\Handlers\Commands;
use Laracasts\Commander\CommandHandler;
use Laracasts\Commander\Events\DispatchableTrait;
use Event;
use Flarum\Core\Repositories\DiscussionRepositoryInterface as DiscussionRepository;
use Flarum\Core\Events\DiscussionWillBeSaved;
use Flarum\Core\Support\DispatchesEvents;
use Flarum\Core\Discussions\DiscussionRepository;
class EditDiscussionCommandHandler implements CommandHandler
class EditDiscussionCommandHandler
{
use DispatchableTrait;
use DispatchesEvents;
protected $discussions;
@@ -27,10 +25,10 @@ class EditDiscussionCommandHandler implements CommandHandler
if (isset($command->title)) {
$discussion->rename($command->title, $user);
}
Event::fire('Flarum.Core.Discussions.Commands.EditDiscussion.DiscussionWillBeSaved', [$discussion, $command]);
$this->discussions->save($discussion);
event(new DiscussionWillBeSaved($discussion, $command));
$discussion->save();
$this->dispatchEventsFor($discussion);
return $discussion;

View File

@@ -1,14 +1,12 @@
<?php namespace Flarum\Core\Posts\Commands;
<?php namespace Flarum\Core\Handlers\Commands;
use Laracasts\Commander\CommandHandler;
use Laracasts\Commander\Events\DispatchableTrait;
use Event;
use Flarum\Core\Repositories\PostRepositoryInterface as PostRepository;
use Flarum\Core\Events\PostWillBeSaved;
use Flarum\Core\Support\DispatchesEvents;
use Flarum\Core\Posts\PostRepository;
class EditPostCommandHandler implements CommandHandler
class EditPostCommandHandler
{
use DispatchableTrait;
use DispatchesEvents;
protected $posts;
@@ -34,9 +32,9 @@ class EditPostCommandHandler implements CommandHandler
$post->restore($user);
}
Event::fire('Flarum.Core.Posts.Commands.EditPost.PostWillBeSaved', [$post, $command]);
event(new PostWillBeSaved($post, $command));
$this->posts->save($post);
$post->save();
$this->dispatchEventsFor($post);
return $post;

View File

@@ -0,0 +1,43 @@
<?php namespace Flarum\Core\Handlers\Commands;
use Flarum\Core\Repositories\UserRepositoryInterface as UserRepository;
class EditUserCommandHandler
{
use DispatchesEvents;
protected $users;
public function __construct(UserRepository $users)
{
$this->users = $users;
}
public function handle($command)
{
$user = $command->user;
$userToEdit = $this->users->findOrFail($command->userId, $user);
$userToEdit->assertCan($user, 'edit');
if (isset($command->username)) {
$userToEdit->rename($command->username);
}
if (isset($command->email)) {
$userToEdit->changeEmail($command->email);
}
if (isset($command->password)) {
$userToEdit->changePassword($command->password);
}
if (! empty($command->readTime)) {
$userToEdit->markAllAsRead();
}
event(new UserWillBeSaved($userToEdit, $command));
$userToEdit->save();
$this->dispatchEventsFor($userToEdit);
return $userToEdit;
}
}

View File

@@ -0,0 +1,14 @@
<?php namespace Flarum\Core\Handlers\Commands;
use Flarum\Core\Models\AccessToken;
class GenerateAccessTokenCommandHandler
{
public function handle($command)
{
$token = AccessToken::generate($command->userId);
$token->save();
return $token;
}
}

View File

@@ -1,24 +1,19 @@
<?php namespace Flarum\Core\Posts\Commands;
<?php namespace Flarum\Core\Handlers\Commands;
use Flarum\Core\Discussions\DiscussionRepository;
use Flarum\Core\Posts\CommentPost;
use Flarum\Core\Posts\PostRepository;
use Laracasts\Commander\CommandHandler;
use Laracasts\Commander\Events\DispatchableTrait;
use Event;
use Flarum\Core\Events\PostWillBeSaved;
use Flarum\Core\Repositories\DiscussionRepositoryInterface as DiscussionRepository;
use Flarum\Core\Models\CommentPost;
use Flarum\Core\Support\DispatchesEvents;
class PostReplyCommandHandler implements CommandHandler
class PostReplyCommandHandler
{
use DispatchableTrait;
use DispatchesEvents;
protected $discussions;
protected $posts;
public function __construct(DiscussionRepository $discussions, PostRepository $posts)
public function __construct(DiscussionRepository $discussions)
{
$this->discussions = $discussions;
$this->posts = $posts;
}
public function handle($command)
@@ -43,9 +38,9 @@ class PostReplyCommandHandler implements CommandHandler
$user->id
);
Event::fire('Flarum.Core.Posts.Commands.PostReply.PostWillBeSaved', [$post, $command]);
event(new PostWillBeSaved($post, $command));
$this->posts->save($post);
$post->save();
$this->dispatchEventsFor($post);
return $post;

View File

@@ -0,0 +1,39 @@
<?php namespace Flarum\Core\Handlers\Commands;
use Flarum\Core\Repositories\DiscussionRepositoryInterface as DiscussionRepository;
use Flarum\Core\Events\DiscussionStateWillBeSaved;
use Flarum\Core\Exceptions\PermissionDeniedException;
use Flarum\Core\Support\DispatchesEvents;
class ReadDiscussionCommandHandler
{
use DispatchesEvents;
protected $discussions;
public function __construct(DiscussionRepository $discussions)
{
$this->discussions = $discussions;
}
public function handle($command)
{
$user = $command->user;
if (! $user->exists) {
throw new PermissionDeniedException;
}
$discussion = $this->discussions->findOrFail($command->discussionId, $user);
$state = $discussion->stateFor($user);
$state->read($command->readNumber);
event(new DiscussionStateWillBeSaved($state, $command));
$state->save();
$this->dispatchEventsFor($state);
return $state;
}
}

View File

@@ -1,25 +1,10 @@
<?php namespace Flarum\Core\Users\Commands;
<?php namespace Flarum\Core\Handlers\Commands;
use Flarum\Core\Forum;
use Flarum\Core\Users\User;
use Flarum\Core\Users\UserRepository;
use Laracasts\Commander\CommandHandler;
use Laracasts\Commander\Events\DispatchableTrait;
use Event;
use Flarum\Core\Support\DispatchesEvents;
class RegisterUserCommandHandler implements CommandHandler
class RegisterUserCommandHandler
{
use DispatchableTrait;
protected $forum;
protected $userRepo;
public function __construct(Forum $forum, UserRepository $userRepo)
{
$this->forum = $forum;
$this->userRepo = $userRepo;
}
use DispatchesEvents;
public function handle($command)
{
@@ -27,7 +12,7 @@ class RegisterUserCommandHandler implements CommandHandler
// case of a guest trying to register an account, this will depend on
// whether or not registration is open. If the user is an admin, though,
// it will be allowed.
$this->forum->assertCan($command->user, 'register');
$command->forum->assertCan($command->user, 'register');
// Create a new User entity, persist it, and dispatch domain events.
// Before persistance, though, fire an event to give plugins an
@@ -38,9 +23,9 @@ class RegisterUserCommandHandler implements CommandHandler
$command->password
);
Event::fire('Flarum.Core.Users.Commands.RegisterUser.UserWillBeSaved', [$user, $command]);
event(new UserWillBeSaved($user, $command));
$this->userRepo->save($user);
$user->save();
$this->dispatchEventsFor($user);
return $user;

View File

@@ -0,0 +1,53 @@
<?php namespace Flarum\Core\Handlers\Commands;
use Illuminate\Contracts\Bus\Dispatcher;
use Flarum\Core\Models\Discussion;
use Flarum\Core\Events\DiscussionWillBeSaved;
use Flarum\Core\Commands\PostReplyCommand;
use Flarum\Core\Support\DispatchesEvents;
class StartDiscussionCommandHandler
{
use DispatchesEvents;
protected $bus;
public function __construct(Dispatcher $bus)
{
$this->bus = $bus;
}
public function handle($command)
{
$command->forum->assertCan($command->user, 'startDiscussion');
// Create a new Discussion entity, persist it, and dispatch domain
// events. Before persistance, though, fire an event to give plugins
// an opportunity to alter the discussion entity based on data in the
// command they may have passed through in the controller.
$discussion = Discussion::start(
$command->title,
$command->user
);
event(new DiscussionWillBeSaved($discussion, $command));
$discussion->save();
// Now that the discussion has been created, we can add the first post.
// For now we will do this by running the PostReply command, but as this
// will trigger a domain event that is slightly semantically incorrect
// in this situation (PostWasPosted), we may need to reconsider someday.
$post = $this->bus->dispatch(
new PostReplyCommand($discussion->id, $command->content, $command->user)
);
// The discussion may have been updated by the PostReplyCommand; we need
// to refresh its data.
$discussion = $post->discussion;
$this->dispatchEventsFor($discussion);
return $discussion;
}
}

View File

@@ -0,0 +1,65 @@
<?php namespace Flarum\Core\Handlers\Events;
use Flarum\Core\Models\Post;
use Flarum\Core\Events\PostWasPosted;
use Flarum\Core\Events\PostWasDeleted;
use Flarum\Core\Events\PostWasHidden;
use Flarum\Core\Events\PostWasRestored;
class DiscussionMetadataUpdater
{
/**
* Register the listeners for the subscriber.
*
* @param Illuminate\Events\Dispatcher $events
* @return array
*/
public function subscribe($events)
{
$events->listen('Flarum\Core\Events\PostWasPosted', __CLASS__.'@whenPostWasPosted');
$events->listen('Flarum\Core\Events\PostWasDeleted', __CLASS__.'@whenPostWasDeleted');
$events->listen('Flarum\Core\Events\PostWasHidden', __CLASS__.'@whenPostWasHidden');
$events->listen('Flarum\Core\Events\PostWasRestored', __CLASS__.'@whenPostWasRestored');
}
public function whenPostWasPosted(PostWasPosted $event)
{
$discussion = $event->post->discussion;
$discussion->comments_count++;
$discussion->setLastPost($event->post);
$discussion->save();
}
public function whenPostWasDeleted(PostWasDeleted $event)
{
$this->removePost($event->post);
}
public function whenPostWasHidden(PostWasHidden $event)
{
$this->removePost($event->post);
}
public function whenPostWasRestored(PostWasRestored $event)
{
$discussion = $event->post->discussion;
$discussion->refreshCommentsCount();
$discussion->refreshLastPost();
$discussion->save();
}
protected function removePost(Post $post)
{
$discussion = $post->discussion;
$discussion->refreshCommentsCount();
if ($discussion->last_post_id == $post->id) {
$discussion->refreshLastPost();
}
$discussion->save();
}
}

View File

@@ -0,0 +1,49 @@
<?php namespace Flarum\Core\Handlers\Events;
use Illuminate\Mail\Mailer;
use Flarum\Core\Events\UserWasRegistered;
use Flarum\Core\Events\EmailWasChanged;
class EmailConfirmationMailer
{
protected $mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
/**
* Register the listeners for the subscriber.
*
* @param Illuminate\Events\Dispatcher $events
* @return array
*/
public function subscribe($events)
{
$events->listen('Flarum\Core\Events\UserWasRegistered', __CLASS__.'@whenUserWasRegistered');
$events->listen('Flarum\Core\Events\EmailWasChanged', __CLASS__.'@whenEmailWasChanged');
}
public function whenUserWasRegistered(UserWasRegistered $event)
{
$user = $event->user;
$forumTitle = Config::get('flarum::forum_tite');
$data = [
'username' => $user->username,
'forumTitle' => $forumTitle,
'url' => route('flarum.confirm', ['id' => $user->id, 'token' => $user->confirmation_token])
];
$this->mailer->send(['text' => 'flarum::emails.confirm'], $data, function ($message) use ($user) {
$message->to($user->email)->subject('['.$forumTitle.'] Email Address Confirmation');
});
}
public function whenEmailWasChanged(EmailWasChanged $event)
{
}
}

View File

@@ -0,0 +1,32 @@
<?php namespace Flarum\Core\Handlers\Events;
use Flarum\Core\Events\DiscussionWasRenamed;
use Flarum\Core\Models\RenamedPost;
class RenamedPostCreator
{
/**
* Register the listeners for the subscriber.
*
* @param Illuminate\Events\Dispatcher $events
* @return array
*/
public function subscribe($events)
{
$events->listen('Flarum\Core\Events\DiscussionWasRenamed', __CLASS__.'@whenDiscussionWasRenamed');
}
public function whenDiscussionWasRenamed(DiscussionWasRenamed $event)
{
$post = RenamedPost::reply(
$event->discussion->id,
$event->user->id,
$event->oldTitle,
$event->discussion->title
);
$post->save();
$event->discussion->postWasAdded($post);
}
}

View File

@@ -0,0 +1,70 @@
<?php namespace Flarum\Core\Handlers\Events;
use Flarum\Core\Models\User;
use Flarum\Core\Events\PostWasPosted;
use Flarum\Core\Events\PostWasDeleted;
use Flarum\Core\Events\PostWasHidden;
use Flarum\Core\Events\PostWasRestored;
use Flarum\Core\Events\DiscussionWasStarted;
use Flarum\Core\Events\DiscussionWasDeleted;
class UserMetadataUpdater
{
/**
* Register the listeners for the subscriber.
*
* @param Illuminate\Events\Dispatcher $events
* @return array
*/
public function subscribe($events)
{
$events->listen('Flarum\Core\Events\PostWasPosted', __CLASS__.'@whenPostWasPosted');
$events->listen('Flarum\Core\Events\PostWasDeleted', __CLASS__.'@whenPostWasDeleted');
$events->listen('Flarum\Core\Events\PostWasHidden', __CLASS__.'@whenPostWasHidden');
$events->listen('Flarum\Core\Events\PostWasRestored', __CLASS__.'@whenPostWasRestored');
$events->listen('Flarum\Core\Events\DiscussionWasStarted', __CLASS__.'@whenDiscussionWasStarted');
$events->listen('Flarum\Core\Events\DiscussionWasDeleted', __CLASS__.'@whenDiscussionWasDeleted');
}
public function whenPostWasPosted(PostWasPosted $event)
{
$this->updateRepliesCount($event->post->user, 1);
}
public function whenPostWasDeleted(PostWasDeleted $event)
{
$this->updateRepliesCount($event->post->user, -1);
}
public function whenPostWasHidden(PostWasHidden $event)
{
$this->updateRepliesCount($event->post->user, -1);
}
public function whenPostWasRestored(PostWasRestored $event)
{
$this->updateRepliesCount($event->post->user, 1);
}
public function whenDiscussionWasStarted(DiscussionWasStarted $event)
{
$this->updateDiscussionsCount($event->discussion->startUser, 1);
}
public function whenDiscussionWasDeleted(DiscussionWasDeleted $event)
{
$this->updateDiscussionsCount($event->discussion->startUser, -1);
}
protected function updateRepliesCount(User $user, $amount)
{
$user->posts_count += $amount;
$user->save();
}
protected function updateDiscussionsCount(User $user, $amount)
{
$user->discussions_count += $amount;
$user->save();
}
}

View File

@@ -0,0 +1,44 @@
<?php namespace Flarum\Core\Models;
class AccessToken extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'access_tokens';
/**
* Use a custom primary key for this model.
*
* @var boolean
*/
public $incrementing = false;
/**
* Generate an access token for the specified user.
*
* @param int $userId
* @return static
*/
public static function generate($userId)
{
$token = new static;
$token->id = str_random(40);
$token->user_id = $userId;
return $token;
}
/**
* Define the relationship with the owner of this access token.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo('Flarum\Core\Models\User');
}
}

View File

@@ -15,7 +15,7 @@ class Activity extends Entity {
public function fromUser()
{
return $this->belongsTo('Flarum\Core\Users\User', 'from_user_id');
return $this->belongsTo('Flarum\Core\Models\User', 'from_user_id');
}
public function permission($permission)

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