diff --git a/extensions/tags/bootstrap.php b/extensions/tags/bootstrap.php index bcde602ce..19039db79 100644 --- a/extensions/tags/bootstrap.php +++ b/extensions/tags/bootstrap.php @@ -1,5 +1,9 @@ app->register('Flarum\Categories\CategoriesServiceProvider'); +// Register our service provider with the Flarum application. In here we can +// register bindings and execute code when the application boots. +$app->register('Flarum\Categories\CategoriesServiceProvider'); diff --git a/extensions/tags/js/bootstrap.js b/extensions/tags/js/bootstrap.js index 00de45748..7eeeb273e 100644 --- a/extensions/tags/js/bootstrap.js +++ b/extensions/tags/js/bootstrap.js @@ -16,6 +16,7 @@ import icon from 'flarum/helpers/icon'; import CategoriesPage from 'categories/components/categories-page'; import Category from 'categories/category'; import PostDiscussionMoved from 'categories/components/post-discussion-moved'; +import NotificationDiscussionMoved from 'categories/components/notification-discussion-moved'; import app from 'flarum/app'; @@ -31,6 +32,7 @@ app.initializers.add('categories', function() { // app.routes['category.filter'] = ['/c/:slug/:filter', IndexPage.component({category: true})]; app.postComponentRegistry['discussionMoved'] = PostDiscussionMoved; + app.notificationComponentRegistry['discussionMoved'] = NotificationDiscussionMoved; app.store.model('categories', Category); extend(DiscussionList.prototype, 'infoItems', function(items, discussion) { diff --git a/extensions/tags/js/src/components/notification-discussion-moved.js b/extensions/tags/js/src/components/notification-discussion-moved.js new file mode 100644 index 000000000..f3ef00481 --- /dev/null +++ b/extensions/tags/js/src/components/notification-discussion-moved.js @@ -0,0 +1,27 @@ +import Notification from 'flarum/components/notification'; +import avatar from 'flarum/helpers/avatar'; +import icon from 'flarum/helpers/icon'; +import username from 'flarum/helpers/username'; +import humanTime from 'flarum/helpers/human-time'; + +export default class NotificationDiscussionMoved extends Notification { + content() { + var notification = this.props.notification; + var discussion = notification.subject(); + var category = discussion.category(); + + return m('a', {href: app.route('discussion.near', { + id: discussion.id(), + slug: discussion.slug(), + near: notification.content().postNumber + }), config: m.route}, [ + avatar(notification.sender()), + m('h3.notification-title', discussion.title()), + m('div.notification-info', [ + icon('arrow-right'), + ' Moved to ', m('span.category', {style: 'color: '+category.color()}, category.title()), ' by ', username(notification.sender()), + ' ', humanTime(notification.time()) + ]) + ]); + } +} diff --git a/extensions/tags/src/Handlers/Handler.php b/extensions/tags/src/CategoriesHandler.php similarity index 54% rename from extensions/tags/src/Handlers/Handler.php rename to extensions/tags/src/CategoriesHandler.php index fae68dd60..c21b49753 100755 --- a/extensions/tags/src/Handlers/Handler.php +++ b/extensions/tags/src/CategoriesHandler.php @@ -1,35 +1,27 @@ -actor = $actor; - } - public function subscribe($events) { $events->listen('Flarum\Forum\Events\RenderView', __CLASS__.'@renderForum'); $events->listen('Flarum\Api\Events\SerializeRelationship', __CLASS__.'@serializeRelationship'); - $events->listen('Flarum\Core\Events\RegisterDiscussionGambits', __CLASS__.'@registerGambits'); - $events->listen('Flarum\Core\Events\DiscussionWillBeSaved', __CLASS__.'@saveCategoryToDiscussion'); + $events->listen('Flarum\Core\Events\RegisterDiscussionGambits', __CLASS__.'@registerDiscussionGambits'); + $events->listen('Flarum\Core\Events\DiscussionWillBeSaved', __CLASS__.'@whenDiscussionWillBeSaved'); } public function renderForum(RenderView $event) { - $root = __DIR__.'/../..'; + $root = __DIR__.'/..'; $event->assets->addFile([ $root.'/js/dist/extension.js', @@ -40,7 +32,7 @@ class Handler $event->view->data = array_merge($event->view->data, $serializer->collection(Category::orderBy('position')->get())->toArray()); } - public function saveCategoryToDiscussion(DiscussionWillBeSaved $event) + public function whenDiscussionWillBeSaved(DiscussionWillBeSaved $event) { if (isset($event->command->data['links']['category']['linkage'])) { $linkage = $event->command->data['links']['category']['linkage']; @@ -56,37 +48,11 @@ class Handler } $discussion->category_id = $categoryId; - - if ($discussion->exists) { - $lastPost = $discussion->posts()->orderBy('time', 'desc')->first(); - if ($lastPost instanceof DiscussionMovedPost) { - if ($lastPost->content[0] == $categoryId) { - $lastPost->delete(); - $discussion->postWasRemoved($lastPost); - } else { - $newContent = $lastPost->content; - $newContent[1] = $categoryId; - $lastPost->content = $newContent; - $lastPost->save(); - $discussion->postWasAdded($lastPost); - } - } else { - $post = DiscussionMovedPost::reply( - $discussion->id, - $user->id, - $oldCategoryId, - $categoryId - ); - - $post->save(); - - $discussion->postWasAdded($post); - } - } + $discussion->raise(new DiscussionWasMoved($discussion, $user, $oldCategoryId)); } } - public function registerGambits(RegisterDiscussionGambits $event) + public function registerDiscussionGambits(RegisterDiscussionGambits $event) { $event->gambits->add('Flarum\Categories\CategoryGambit'); } diff --git a/extensions/tags/src/CategoriesServiceProvider.php b/extensions/tags/src/CategoriesServiceProvider.php index bd99b994a..6ce82ce4a 100644 --- a/extensions/tags/src/CategoriesServiceProvider.php +++ b/extensions/tags/src/CategoriesServiceProvider.php @@ -2,10 +2,11 @@ use Illuminate\Support\ServiceProvider; use Illuminate\Contracts\Events\Dispatcher; -use Flarum\Api\Actions\Discussions\IndexAction; -use Flarum\Api\Actions\Discussions\ShowAction; use Flarum\Core\Models\Post; use Flarum\Core\Models\Discussion; +use Flarum\Core\Notifications\Notifier; +use Flarum\Api\Actions\Discussions\IndexAction as DiscussionsIndexAction; +use Flarum\Api\Actions\Discussions\ShowAction as DiscussionsShowAction; class CategoriesServiceProvider extends ServiceProvider { @@ -14,23 +15,31 @@ class CategoriesServiceProvider extends ServiceProvider * * @return void */ - public function boot(Dispatcher $events) + public function boot(Dispatcher $events, Notifier $notifier) { - $events->subscribe('Flarum\Categories\Handlers\Handler'); - - IndexAction::$include['category'] = true; - - ShowAction::$include['category'] = true; - - Post::addType('discussionMoved', 'Flarum\Categories\DiscussionMovedPost'); + $events->subscribe('Flarum\Categories\CategoriesHandler'); + $events->subscribe('Flarum\Categories\DiscussionMovedNotifier'); + // Add the category relationship to the Discussion model, and include + // it in discussion-related API actions by default. Discussion::addRelationship('category', function ($model) { return $model->belongsTo('Flarum\Categories\Category', null, null, 'category'); }); + DiscussionsIndexAction::$include['category'] = true; + DiscussionsShowAction::$include['category'] = true; + + // Add a new post and notification type to represent a discussion + // being moved from one category to another. + Post::addType('Flarum\Categories\DiscussionMovedPost'); + + $notifier->registerType('Flarum\Categories\DiscussionMovedNotification', ['alert' => true]); } public function register() { - // + $this->app->bind( + 'Flarum\Categories\CategoryRepositoryInterface', + 'Flarum\Categories\EloquentCategoryRepository' + ); } } diff --git a/extensions/tags/src/CategoryGambit.php b/extensions/tags/src/CategoryGambit.php index 928a8bb9d..38b75ddcf 100644 --- a/extensions/tags/src/CategoryGambit.php +++ b/extensions/tags/src/CategoryGambit.php @@ -8,17 +8,39 @@ class CategoryGambit extends GambitAbstract { /** * The gambit's regex pattern. + * * @var string */ protected $pattern = 'category:(.+)'; + /** + * @var \Flarum\Categories\CategoryRepositoryInterface + */ + protected $categories; + + /** + * Instantiate the gambit. + * + * @param \Flarum\Categories\CategoryRepositoryInterface $categories + */ + public function __construct(CategoryRepositoryInterface $categories) + { + $this->categories = $categories; + } + + /** + * Apply conditions to the searcher, given matches from the gambit's + * regex. + * + * @param array $matches The matches from the gambit's regex. + * @param \Flarum\Core\Search\SearcherInterface $searcher + * @return void + */ public function conditions($matches, SearcherInterface $searcher) { $slug = trim($matches[1], '"'); - // @todo implement categories repository - // $id = $this->categories->getIdForSlug($slug); - $id = Category::whereSlug($slug)->pluck('id'); + $id = $this->categories->getIdForSlug($slug); $searcher->query()->where('category_id', $id); } diff --git a/extensions/tags/src/CategoryRepositoryInterface.php b/extensions/tags/src/CategoryRepositoryInterface.php new file mode 100644 index 000000000..01c0875a9 --- /dev/null +++ b/extensions/tags/src/CategoryRepositoryInterface.php @@ -0,0 +1,24 @@ +post = $post; + + parent::__construct($recipient, $sender); + } + + public function getSubject() + { + return $this->post->discussion; + } + + public function getAlertData() + { + return [ + 'postNumber' => $this->post->number + ]; + } + + public static function getType() + { + return 'discussionMoved'; + } + + public static function getSubjectModel() + { + return 'Flarum\Core\Models\Discussion'; + } +} diff --git a/extensions/tags/src/DiscussionMovedNotifier.php b/extensions/tags/src/DiscussionMovedNotifier.php new file mode 100755 index 000000000..71adacb55 --- /dev/null +++ b/extensions/tags/src/DiscussionMovedNotifier.php @@ -0,0 +1,58 @@ +notifier = $notifier; + } + + /** + * Register the listeners for the subscriber. + * + * @param \Illuminate\Contracts\Events\Dispatcher $events + */ + public function subscribe(Dispatcher $events) + { + $events->listen('Flarum\Categories\Events\DiscussionWasMoved', __CLASS__.'@whenDiscussionWasMoved'); + } + + public function whenDiscussionWasMoved(DiscussionWasMoved $event) + { + $post = $this->createPost($event); + + $post = $event->discussion->addPost($post); + + if ($event->discussion->start_user_id !== $event->user->id) { + $this->sendNotification($event, $post); + } + } + + protected function createPost(DiscussionWasMoved $event) + { + return DiscussionMovedPost::reply( + $event->discussion->id, + $event->user->id, + $event->oldCategoryId, + $event->discussion->category_id + ); + } + + protected function sendNotification(DiscussionWasMoved $event, DiscussionMovedPost $post) + { + $notification = new DiscussionMovedNotification( + $event->discussion->startUser, + $event->user, + $post, + $event->discussion->category_id + ); + + $this->notifier->send($notification); + } +} diff --git a/extensions/tags/src/DiscussionMovedPost.php b/extensions/tags/src/DiscussionMovedPost.php index a5cd59639..a1287f3c0 100755 --- a/extensions/tags/src/DiscussionMovedPost.php +++ b/extensions/tags/src/DiscussionMovedPost.php @@ -1,49 +1,63 @@ content[0] == $this->content[1]) { + return false; + } + + $previous->content = static::buildContent($previous->content[0], $this->content[1]); + return true; + } + /** * Create a new instance in reply to a discussion. * - * @param int $discussionId - * @param int $userId - * @param string $oldTitle - * @param string $newTitle + * @param integer $discussionId + * @param integer $userId + * @param integer $oldCategoryId + * @param integer $newCategoryId * @return static */ public static function reply($discussionId, $userId, $oldCategoryId, $newCategoryId) { $post = new static; - $post->content = [$oldCategoryId, $newCategoryId]; + $post->content = static::buildContent($oldCategoryId, $newCategoryId); $post->time = time(); $post->discussion_id = $discussionId; $post->user_id = $userId; - $post->type = 'discussionMoved'; return $post; } /** - * Unserialize the content attribute. + * Build the content attribute. * - * @param string $value - * @return string + * @param boolean $oldCategoryId The old category ID. + * @param boolean $newCategoryId The new category ID. + * @return array */ - public function getContentAttribute($value) + public static function buildContent($oldCategoryId, $newCategoryId) { - return json_decode($value); - } - - /** - * Serialize the content attribute. - * - * @param string $value - */ - public function setContentAttribute($value) - { - $this->attributes['content'] = json_encode($value); + return [$oldCategoryId, $newCategoryId]; } } diff --git a/extensions/tags/src/EloquentCategoryRepository.php b/extensions/tags/src/EloquentCategoryRepository.php new file mode 100644 index 000000000..a04a2408e --- /dev/null +++ b/extensions/tags/src/EloquentCategoryRepository.php @@ -0,0 +1,52 @@ +scopeVisibleForUser($query, $user)->get(); + } + + /** + * Get the ID of a category with the given slug. + * + * @param string $slug + * @param \Flarum\Core\Models\User|null $user + * @return integer + */ + public function getIdForSlug($slug, User $user = null) + { + $query = Category::where('slug', 'like', $slug); + + return $this->scopeVisibleForUser($query, $user)->pluck('id'); + } + + /** + * Scope a query to only include records that are visible to a user. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Flarum\Core\Models\User $user + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function scopeVisibleForUser(Builder $query, User $user = null) + { + if ($user !== null) { + $query->whereCan($user, 'view'); + } + + return $query; + } +} diff --git a/extensions/tags/src/Events/DiscussionWasMoved.php b/extensions/tags/src/Events/DiscussionWasMoved.php new file mode 100644 index 000000000..6393accbe --- /dev/null +++ b/extensions/tags/src/Events/DiscussionWasMoved.php @@ -0,0 +1,34 @@ +discussion = $discussion; + $this->user = $user; + $this->oldCategoryId = $oldCategoryId; + } +}