mirror of
https://github.com/flarum/core.git
synced 2025-10-10 14:34:30 +02:00
Massive refactor
- Use contextual namespaces within Flarum\Core - Clean up and docblock everything - Refactor Activity/Notification blueprint stuff - Refactor Formatter stuff - Refactor Search stuff - Upgrade to JSON-API 1.0 - Removed “addedPosts” and “removedPosts” relationships from discussion API. This was used for adding/removing event posts after renaming a discussion etc. Instead we should make an additional request to get all new posts Todo: - Fix Extenders and extensions - Get rid of repository interfaces - Fix other bugs I’ve inevitably introduced
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
<?php namespace Flarum\Core\Models;
|
||||
<?php namespace Flarum\Core\Activity;
|
||||
|
||||
use Flarum\Core\Model;
|
||||
|
||||
/**
|
||||
* Models a user activity record in the database.
|
||||
@@ -16,23 +18,19 @@
|
||||
class Activity extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $table = 'activity';
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $dates = ['time'];
|
||||
protected static $dateAttributes = ['time'];
|
||||
|
||||
/**
|
||||
* A map of activity types and the model classes to use for their subjects.
|
||||
* For example, the 'posted' activity type, which represents that a user
|
||||
* made a post, has the subject model class 'Flarum\Core\Models\Post'.
|
||||
* made a post, has the subject model class 'Flarum\Core\Posts\Post'.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
@@ -79,7 +77,7 @@ class Activity extends Model
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('Flarum\Core\Models\User', 'user_id');
|
||||
return $this->belongsTo('Flarum\Core\Users\User', 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,7 +104,8 @@ class Activity extends Model
|
||||
* Set the subject model for the given activity type.
|
||||
*
|
||||
* @param string $type The activity type.
|
||||
* @param string $class The class name of the subject model for that type.
|
||||
* @param string $subjectModel The class name of the subject model for that
|
||||
* type.
|
||||
* @return void
|
||||
*/
|
||||
public static function setSubjectModel($type, $subjectModel)
|
@@ -1,5 +0,0 @@
|
||||
<?php namespace Flarum\Core\Activity;
|
||||
|
||||
abstract class ActivityAbstract implements ActivityInterface
|
||||
{
|
||||
}
|
18
src/Core/Activity/ActivityRepositoryInterface.php
Normal file
18
src/Core/Activity/ActivityRepositoryInterface.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php namespace Flarum\Core\Activity;
|
||||
|
||||
use Flarum\Core\Users\User;
|
||||
|
||||
interface ActivityRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* Find a user's activity.
|
||||
*
|
||||
* @param integer $userId
|
||||
* @param \Flarum\Core\Users\User $actor
|
||||
* @param null|integer $count
|
||||
* @param integer $start
|
||||
* @param null|string $type
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function findByUser($userId, User $actor, $count = null, $start = 0, $type = null);
|
||||
}
|
@@ -13,24 +13,29 @@ class ActivityServiceProvider extends ServiceProvider
|
||||
public function boot()
|
||||
{
|
||||
$this->extend([
|
||||
(new Extend\EventSubscriber('Flarum\Core\Handlers\Events\UserActivitySyncer')),
|
||||
(new Extend\EventSubscriber('Flarum\Core\Activity\Listeners\UserActivitySyncer')),
|
||||
|
||||
(new Extend\ActivityType('Flarum\Core\Activity\PostedActivity'))
|
||||
(new Extend\ActivityType('Flarum\Core\Activity\PostedBlueprint'))
|
||||
->subjectSerializer('Flarum\Api\Serializers\PostBasicSerializer'),
|
||||
|
||||
(new Extend\ActivityType('Flarum\Core\Activity\StartedDiscussionActivity'))
|
||||
(new Extend\ActivityType('Flarum\Core\Activity\StartedDiscussionBlueprint'))
|
||||
->subjectSerializer('Flarum\Api\Serializers\PostBasicSerializer'),
|
||||
|
||||
(new Extend\ActivityType('Flarum\Core\Activity\JoinedActivity'))
|
||||
(new Extend\ActivityType('Flarum\Core\Activity\JoinedBlueprint'))
|
||||
->subjectSerializer('Flarum\Api\Serializers\UserBasicSerializer')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the service provider.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->app->bind(
|
||||
'Flarum\Core\Repositories\ActivityRepositoryInterface',
|
||||
'Flarum\Core\Repositories\EloquentActivityRepository'
|
||||
'Flarum\Core\Activity\ActivityRepositoryInterface',
|
||||
'Flarum\Core\Activity\EloquentActivityRepository'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +1,22 @@
|
||||
<?php namespace Flarum\Core\Activity;
|
||||
|
||||
use Flarum\Core\Repositories\ActivityRepositoryInterface;
|
||||
use Flarum\Core\Models\Activity;
|
||||
|
||||
/**
|
||||
* The Activity Syncer commits activity blueprints to the database. Where a
|
||||
* blueprint represents a single piece of activity, the syncer associates it
|
||||
* with a particular user(s) and makes it available on their activity feed.
|
||||
*/
|
||||
class ActivitySyncer
|
||||
{
|
||||
/**
|
||||
* @var ActivityRepositoryInterface
|
||||
*/
|
||||
protected $activity;
|
||||
|
||||
/**
|
||||
* Create a new instance of the activity syncer.
|
||||
*
|
||||
* @param ActivityRepositoryInterface $activity
|
||||
*/
|
||||
public function __construct(ActivityRepositoryInterface $activity)
|
||||
{
|
||||
$this->activity = $activity;
|
||||
@@ -16,38 +26,92 @@ class ActivitySyncer
|
||||
* Sync a piece of activity so that it is present for the specified users,
|
||||
* and not present for anyone else.
|
||||
*
|
||||
* @param \Flarum\Core\Activity\ActivityInterface $activity
|
||||
* @param Blueprint $blueprint
|
||||
* @param \Flarum\Core\Models\User[] $users
|
||||
* @return void
|
||||
*/
|
||||
public function sync(ActivityInterface $activity, array $users)
|
||||
public function sync(Blueprint $blueprint, array $users)
|
||||
{
|
||||
Activity::unguard();
|
||||
|
||||
$attributes = [
|
||||
'type' => $activity::getType(),
|
||||
'subject_id' => $activity->getSubject()->id,
|
||||
'time' => $activity->getTime()
|
||||
];
|
||||
$attributes = $this->getAttributes($blueprint);
|
||||
|
||||
// Find all existing activity records in the database matching this
|
||||
// blueprint. We will begin by assuming that they all need to be
|
||||
// deleted in order to match the provided list of users.
|
||||
$toDelete = Activity::where($attributes)->get();
|
||||
$toInsert = [];
|
||||
|
||||
// For each of the provided users, check to see if they already have
|
||||
// an activity record in the database. If they do, we can leave it be;
|
||||
// otherwise, we will need to create a new one for them.
|
||||
foreach ($users as $user) {
|
||||
$existing = $toDelete->where('user_id', $user->id)->first();
|
||||
$existing = $toDelete->first(function ($activity) use ($user) {
|
||||
return $activity->user_id === $user->id;
|
||||
});
|
||||
|
||||
if ($k = $toDelete->search($existing)) {
|
||||
$toDelete->pull($k);
|
||||
if ($existing) {
|
||||
$toDelete->forget($toDelete->search($existing));
|
||||
} else {
|
||||
$toInsert[] = $attributes + ['user_id' => $user->id];
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, delete all of the remaining activity records which weren't
|
||||
// removed from this collection by the above loop. Insert the records
|
||||
// we need to insert as well.
|
||||
if (count($toDelete)) {
|
||||
Activity::whereIn('id', $toDelete->lists('id'))->delete();
|
||||
$this->deleteActivity($toDelete->lists('id'));
|
||||
}
|
||||
|
||||
if (count($toInsert)) {
|
||||
Activity::insert($toInsert);
|
||||
$this->createActivity($toInsert);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a piece of activity for all users.
|
||||
*
|
||||
* @param Blueprint $blueprint
|
||||
* @return void
|
||||
*/
|
||||
public function delete(Blueprint $blueprint)
|
||||
{
|
||||
Activity::where($this->getAttributes($blueprint))->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a list of activity records.
|
||||
*
|
||||
* @param int[] $ids
|
||||
*/
|
||||
protected function deleteActivity(array $ids)
|
||||
{
|
||||
Activity::whereIn('id', $ids)->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a list of activity record into the database.
|
||||
*
|
||||
* @param array[] $records An array containing arrays of activity record
|
||||
* attributes to insert.
|
||||
*/
|
||||
protected function createActivity(array $records)
|
||||
{
|
||||
Activity::insert($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an array of attributes to be stored in an activity record in
|
||||
* the database, given an activity blueprint.
|
||||
*
|
||||
* @param Blueprint $blueprint
|
||||
* @return array
|
||||
*/
|
||||
protected function getAttributes(Blueprint $blueprint)
|
||||
{
|
||||
return [
|
||||
'type' => $blueprint::getType(),
|
||||
'subject_id' => $blueprint->getSubject()->id,
|
||||
'time' => $blueprint->getTime()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,16 @@
|
||||
<?php namespace Flarum\Core\Activity;
|
||||
|
||||
interface ActivityInterface
|
||||
/**
|
||||
* An activity Blueprint, when instantiated, represents a single piece of
|
||||
* activity. The blueprint is used by the ActivitySyncer to commit the activity
|
||||
* to the database.
|
||||
*/
|
||||
interface Blueprint
|
||||
{
|
||||
/**
|
||||
* Get the model that is the subject of this activity.
|
||||
*
|
||||
* @return \Flarum\Core\Models\Model
|
||||
* @return \Flarum\Core\Model
|
||||
*/
|
||||
public function getSubject();
|
||||
|
35
src/Core/Activity/EloquentActivityRepository.php
Normal file
35
src/Core/Activity/EloquentActivityRepository.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php namespace Flarum\Core\Activity;
|
||||
|
||||
use Flarum\Core\Users\User;
|
||||
|
||||
class EloquentActivityRepository implements ActivityRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function findByUser($userId, User $actor, $limit = null, $offset = 0, $type = null)
|
||||
{
|
||||
$query = Activity::where('user_id', $userId)
|
||||
->whereIn('type', $this->getRegisteredTypes())
|
||||
->latest('time')
|
||||
->skip($offset)
|
||||
->take($limit);
|
||||
|
||||
if ($type !== null) {
|
||||
$query->where('type', $type);
|
||||
}
|
||||
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of activity types that have been registered with the activity
|
||||
* model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getRegisteredTypes()
|
||||
{
|
||||
return array_keys(Activity::getSubjectModels());
|
||||
}
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
<?php namespace Flarum\Core\Activity;
|
||||
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
class JoinedActivity extends ActivityAbstract
|
||||
{
|
||||
protected $user;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function getSubject()
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function getTime()
|
||||
{
|
||||
return $this->user->join_time;
|
||||
}
|
||||
|
||||
public static function getType()
|
||||
{
|
||||
return 'joined';
|
||||
}
|
||||
|
||||
public static function getSubjectModel()
|
||||
{
|
||||
return 'Flarum\Core\Models\User';
|
||||
}
|
||||
}
|
59
src/Core/Activity/JoinedBlueprint.php
Normal file
59
src/Core/Activity/JoinedBlueprint.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php namespace Flarum\Core\Activity;
|
||||
|
||||
use Flarum\Core\Users\User;
|
||||
|
||||
/**
|
||||
* An activity blueprint for the 'joined' activity type, which represents a user
|
||||
* joining the forum.
|
||||
*/
|
||||
class JoinedBlueprint implements Blueprint
|
||||
{
|
||||
/**
|
||||
* The user who joined the forum.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* Create a new 'joined' activity blueprint.
|
||||
*
|
||||
* @param User $user The user who joined the forum.
|
||||
*/
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSubject()
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTime()
|
||||
{
|
||||
return $this->user->join_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getType()
|
||||
{
|
||||
return 'joined';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubjectModel()
|
||||
{
|
||||
return 'Flarum\Core\Users\User';
|
||||
}
|
||||
}
|
126
src/Core/Activity/Listeners/UserActivitySyncer.php
Executable file
126
src/Core/Activity/Listeners/UserActivitySyncer.php
Executable file
@@ -0,0 +1,126 @@
|
||||
<?php namespace Flarum\Core\Activity\Listeners;
|
||||
|
||||
use Flarum\Core\Activity\ActivitySyncer;
|
||||
use Flarum\Core\Activity\PostedBlueprint;
|
||||
use Flarum\Core\Activity\StartedDiscussionBlueprint;
|
||||
use Flarum\Core\Activity\JoinedBlueprint;
|
||||
use Flarum\Core\Posts\Post;
|
||||
use Flarum\Core\Posts\Events\PostWasPosted;
|
||||
use Flarum\Core\Posts\Events\PostWasDeleted;
|
||||
use Flarum\Core\Posts\Events\PostWasHidden;
|
||||
use Flarum\Core\Posts\Events\PostWasRestored;
|
||||
use Flarum\Core\Users\Events\UserWasRegistered;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
||||
class UserActivitySyncer
|
||||
{
|
||||
/**
|
||||
* @var \Flarum\Core\Activity\ActivitySyncer
|
||||
*/
|
||||
protected $activity;
|
||||
|
||||
/**
|
||||
* @param \Flarum\Core\Activity\ActivitySyncer $activity
|
||||
*/
|
||||
public function __construct(ActivitySyncer $activity)
|
||||
{
|
||||
$this->activity = $activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $events
|
||||
* @return void
|
||||
*/
|
||||
public function subscribe(Dispatcher $events)
|
||||
{
|
||||
$events->listen('Flarum\Core\Events\PostWasPosted', __CLASS__.'@whenPostWasPosted');
|
||||
$events->listen('Flarum\Core\Events\PostWasHidden', __CLASS__.'@whenPostWasHidden');
|
||||
$events->listen('Flarum\Core\Events\PostWasRestored', __CLASS__.'@whenPostWasRestored');
|
||||
$events->listen('Flarum\Core\Events\PostWasDeleted', __CLASS__.'@whenPostWasDeleted');
|
||||
$events->listen('Flarum\Core\Events\UserWasRegistered', __CLASS__.'@whenUserWasRegistered');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Flarum\Core\Posts\Events\PostWasPosted $event
|
||||
* @return void
|
||||
*/
|
||||
public function whenPostWasPosted(PostWasPosted $event)
|
||||
{
|
||||
$this->postBecameVisible($event->post);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Flarum\Core\Posts\Events\PostWasHidden $event
|
||||
* @return void
|
||||
*/
|
||||
public function whenPostWasHidden(PostWasHidden $event)
|
||||
{
|
||||
$this->postBecameInvisible($event->post);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Flarum\Core\Posts\Events\PostWasRestored $event
|
||||
* @return void
|
||||
*/
|
||||
public function whenPostWasRestored(PostWasRestored $event)
|
||||
{
|
||||
$this->postBecameVisible($event->post);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Flarum\Core\Posts\Events\PostWasDeleted $event
|
||||
* @return void
|
||||
*/
|
||||
public function whenPostWasDeleted(PostWasDeleted $event)
|
||||
{
|
||||
$this->postBecameInvisible($event->post);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Flarum\Core\Users\Events\UserWasRegistered $event
|
||||
* @return void
|
||||
*/
|
||||
public function whenUserWasRegistered(UserWasRegistered $event)
|
||||
{
|
||||
$blueprint = new JoinedBlueprint($event->user);
|
||||
|
||||
$this->activity->sync($blueprint, [$event->user]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync activity to a post's author when a post becomes visible.
|
||||
*
|
||||
* @param \Flarum\Core\Posts\Post $post
|
||||
* @return void
|
||||
*/
|
||||
protected function postBecameVisible(Post $post)
|
||||
{
|
||||
$blueprint = $this->postedBlueprint($post);
|
||||
|
||||
$this->activity->sync($blueprint, [$post->user]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete activity when a post becomes invisible.
|
||||
*
|
||||
* @param \Flarum\Core\Posts\Post $post
|
||||
* @return void
|
||||
*/
|
||||
protected function postBecameInvisible(Post $post)
|
||||
{
|
||||
$blueprint = $this->postedBlueprint($post);
|
||||
|
||||
$this->activity->delete($blueprint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the appropriate activity blueprint for a post.
|
||||
*
|
||||
* @param \Flarum\Core\Posts\Post $post
|
||||
* @return \Flarum\Core\Activity\Blueprint
|
||||
*/
|
||||
protected function postedBlueprint(Post $post)
|
||||
{
|
||||
return $post->number == 1 ? new StartedDiscussionBlueprint($post) : new PostedBlueprint($post);
|
||||
}
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
<?php namespace Flarum\Core\Activity;
|
||||
|
||||
use Flarum\Core\Models\Post;
|
||||
|
||||
class PostedActivity extends ActivityAbstract
|
||||
{
|
||||
protected $post;
|
||||
|
||||
public function __construct(Post $post)
|
||||
{
|
||||
$this->post = $post;
|
||||
}
|
||||
|
||||
public function getSubject()
|
||||
{
|
||||
return $this->post;
|
||||
}
|
||||
|
||||
public function getTime()
|
||||
{
|
||||
return $this->post->time;
|
||||
}
|
||||
|
||||
public static function getType()
|
||||
{
|
||||
return 'posted';
|
||||
}
|
||||
|
||||
public static function getSubjectModel()
|
||||
{
|
||||
return 'Flarum\Core\Models\Post';
|
||||
}
|
||||
}
|
59
src/Core/Activity/PostedBlueprint.php
Normal file
59
src/Core/Activity/PostedBlueprint.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php namespace Flarum\Core\Activity;
|
||||
|
||||
use Flarum\Core\Posts\Post;
|
||||
|
||||
/**
|
||||
* An activity blueprint for the 'posted' activity type, which represents a user
|
||||
* posting in a discussion.
|
||||
*/
|
||||
class PostedBlueprint implements Blueprint
|
||||
{
|
||||
/**
|
||||
* The user who joined the forum.
|
||||
*
|
||||
* @var Post
|
||||
*/
|
||||
protected $post;
|
||||
|
||||
/**
|
||||
* Create a new 'posted' activity blueprint.
|
||||
*
|
||||
* @param Post $post The post that was made.
|
||||
*/
|
||||
public function __construct(Post $post)
|
||||
{
|
||||
$this->post = $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSubject()
|
||||
{
|
||||
return $this->post;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTime()
|
||||
{
|
||||
return $this->post->time;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getType()
|
||||
{
|
||||
return 'posted';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubjectModel()
|
||||
{
|
||||
return 'Flarum\Core\Posts\Post';
|
||||
}
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
<?php namespace Flarum\Core\Activity;
|
||||
|
||||
class StartedDiscussionActivity extends PostedActivity
|
||||
{
|
||||
public static function getType()
|
||||
{
|
||||
return 'startedDiscussion';
|
||||
}
|
||||
}
|
16
src/Core/Activity/StartedDiscussionBlueprint.php
Normal file
16
src/Core/Activity/StartedDiscussionBlueprint.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php namespace Flarum\Core\Activity;
|
||||
|
||||
/**
|
||||
* An activity blueprint for the 'startedDiscussion' activity type, which
|
||||
* represents a user starting a discussion.
|
||||
*/
|
||||
class StartedDiscussionBlueprint extends PostedBlueprint
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getType()
|
||||
{
|
||||
return 'startedDiscussion';
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
<?php namespace Flarum\Core\Commands;
|
||||
|
||||
class ConfirmEmailCommand
|
||||
{
|
||||
public $token;
|
||||
|
||||
public function __construct($token)
|
||||
{
|
||||
$this->token = $token;
|
||||
}
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
<?php namespace Flarum\Core\Commands;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class DeleteAvatarCommand
|
||||
{
|
||||
public $userId;
|
||||
|
||||
public $actor;
|
||||
|
||||
public function __construct($userId, $actor)
|
||||
{
|
||||
if (empty($userId) || !intval($userId)) {
|
||||
throw new RuntimeException('No valid user ID specified.');
|
||||
}
|
||||
|
||||
$this->userId = $userId;
|
||||
$this->actor = $actor;
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
<?php namespace Flarum\Core\Commands;
|
||||
|
||||
class DeleteDiscussionCommand
|
||||
{
|
||||
public $discussionId;
|
||||
|
||||
public $user;
|
||||
|
||||
public function __construct($discussionId, $user)
|
||||
{
|
||||
$this->discussionId = $discussionId;
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
<?php namespace Flarum\Core\Commands;
|
||||
|
||||
class DeletePostCommand
|
||||
{
|
||||
public $postId;
|
||||
|
||||
public $user;
|
||||
|
||||
public function __construct($postId, $user)
|
||||
{
|
||||
$this->postId = $postId;
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
<?php namespace Flarum\Core\Commands;
|
||||
|
||||
class DeleteUserCommand
|
||||
{
|
||||
public $userId;
|
||||
|
||||
public $user;
|
||||
|
||||
public function __construct($userId, $user)
|
||||
{
|
||||
$this->userId = $userId;
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
<?php namespace Flarum\Core\Commands;
|
||||
|
||||
class EditDiscussionCommand
|
||||
{
|
||||
public $discussionId;
|
||||
|
||||
public $user;
|
||||
|
||||
public $data;
|
||||
|
||||
public function __construct($discussionId, $user, $data)
|
||||
{
|
||||
$this->discussionId = $discussionId;
|
||||
$this->user = $user;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
<?php namespace Flarum\Core\Commands;
|
||||
|
||||
class EditPostCommand
|
||||
{
|
||||
public $postId;
|
||||
|
||||
public $user;
|
||||
|
||||
public $data;
|
||||
|
||||
public function __construct($postId, $user, $data)
|
||||
{
|
||||
$this->postId = $postId;
|
||||
$this->user = $user;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
<?php namespace Flarum\Core\Commands;
|
||||
|
||||
class EditUserCommand
|
||||
{
|
||||
public $userId;
|
||||
|
||||
public $user;
|
||||
|
||||
public $data;
|
||||
|
||||
public function __construct($userId, $user, $data)
|
||||
{
|
||||
$this->userId = $userId;
|
||||
$this->user = $user;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
<?php namespace Flarum\Core\Commands;
|
||||
|
||||
class GenerateAccessTokenCommand
|
||||
{
|
||||
public $userId;
|
||||
|
||||
public function __construct($userId)
|
||||
{
|
||||
$this->userId = $userId;
|
||||
}
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
<?php namespace Flarum\Core\Commands;
|
||||
|
||||
class PostReplyCommand
|
||||
{
|
||||
public $discussionId;
|
||||
|
||||
public $user;
|
||||
|
||||
public $data;
|
||||
|
||||
public function __construct($discussionId, $user, $data)
|
||||
{
|
||||
$this->discussionId = $discussionId;
|
||||
$this->user = $user;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
<?php namespace Flarum\Core\Commands;
|
||||
|
||||
class ReadDiscussionCommand
|
||||
{
|
||||
public $discussionId;
|
||||
|
||||
public $user;
|
||||
|
||||
public $readNumber;
|
||||
|
||||
public function __construct($discussionId, $user, $readNumber)
|
||||
{
|
||||
$this->discussionId = $discussionId;
|
||||
$this->user = $user;
|
||||
$this->readNumber = $readNumber;
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
<?php namespace Flarum\Core\Commands;
|
||||
|
||||
class ReadNotificationCommand
|
||||
{
|
||||
public $notificationId;
|
||||
|
||||
public $user;
|
||||
|
||||
public function __construct($notificationId, $user)
|
||||
{
|
||||
$this->notificationId = $notificationId;
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
<?php namespace Flarum\Core\Commands;
|
||||
|
||||
class RegisterUserCommand
|
||||
{
|
||||
public $forum;
|
||||
|
||||
public $user;
|
||||
|
||||
public $data;
|
||||
|
||||
public function __construct($user, $forum, $data)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->forum = $forum;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
<?php namespace Flarum\Core\Commands;
|
||||
|
||||
class RequestPasswordResetCommand
|
||||
{
|
||||
public $email;
|
||||
|
||||
public function __construct($email)
|
||||
{
|
||||
$this->email = $email;
|
||||
}
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
<?php namespace Flarum\Core\Commands;
|
||||
|
||||
class StartDiscussionCommand
|
||||
{
|
||||
public $user;
|
||||
|
||||
public $forum;
|
||||
|
||||
public $data;
|
||||
|
||||
public function __construct($user, $forum, $data)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->forum = $forum;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
@@ -1,31 +0,0 @@
|
||||
<?php namespace Flarum\Core\Commands;
|
||||
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use RuntimeException;
|
||||
|
||||
class UploadAvatarCommand
|
||||
{
|
||||
public $userId;
|
||||
|
||||
/**
|
||||
* @var \Psr\Http\Message\UploadedFileInterface
|
||||
*/
|
||||
public $file;
|
||||
|
||||
public $actor;
|
||||
|
||||
public function __construct($userId, UploadedFileInterface $file, $actor)
|
||||
{
|
||||
if (empty($userId) || !intval($userId)) {
|
||||
throw new RuntimeException('No valid user ID specified.');
|
||||
}
|
||||
|
||||
if (is_null($file)) {
|
||||
throw new RuntimeException('No file to upload');
|
||||
}
|
||||
|
||||
$this->userId = $userId;
|
||||
$this->file = $file;
|
||||
$this->actor = $actor;
|
||||
}
|
||||
}
|
@@ -1,15 +1,7 @@
|
||||
<?php namespace Flarum\Core;
|
||||
|
||||
use Illuminate\Bus\Dispatcher as Bus;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Flarum\Core\Users\User;
|
||||
use Flarum\Support\ServiceProvider;
|
||||
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;
|
||||
use Flarum\Extend;
|
||||
|
||||
class CoreServiceProvider extends ServiceProvider
|
||||
@@ -23,21 +15,14 @@ class CoreServiceProvider extends ServiceProvider
|
||||
{
|
||||
$this->loadViewsFrom(__DIR__.'/../../views', 'flarum');
|
||||
|
||||
$this->addEventHandlers();
|
||||
$this->bootModels();
|
||||
$this->addPostTypes();
|
||||
$this->grantPermissions();
|
||||
$this->mapCommandHandlers();
|
||||
}
|
||||
$this->app->make('Illuminate\Contracts\Bus\Dispatcher')->mapUsing(function ($command) {
|
||||
return get_class($command).'Handler@handle';
|
||||
});
|
||||
|
||||
public function mapCommandHandlers()
|
||||
{
|
||||
$this->app->make(Bus::class)->mapUsing(function ($command) {
|
||||
return Bus::simpleMapping(
|
||||
$command,
|
||||
'Flarum\Core\Commands',
|
||||
'Flarum\Core\Handlers\Commands'
|
||||
);
|
||||
Model::setValidator($this->app['validator']);
|
||||
|
||||
Forum::allow('*', function (Forum $forum, User $user, $action) {
|
||||
return $user->hasPermission('forum.'.$action) ?: null;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,205 +33,15 @@ class CoreServiceProvider extends ServiceProvider
|
||||
*/
|
||||
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');
|
||||
$this->app->singleton('flarum.forum', 'Flarum\Core\Forum');
|
||||
|
||||
// TODO: probably use Illuminate's AggregateServiceProvider
|
||||
// functionality, because it includes the 'provides' stuff.
|
||||
$this->app->register('Flarum\Core\Activity\ActivityServiceProvider');
|
||||
$this->app->register('Flarum\Core\Discussions\DiscussionsServiceProvider');
|
||||
$this->app->register('Flarum\Core\Formatter\FormatterServiceProvider');
|
||||
$this->app->register('Flarum\Core\Notifications\NotificationsServiceProvider');
|
||||
|
||||
// TODO: refactor these into the appropriate service providers, when
|
||||
// (if) we restructure our namespaces per-entity
|
||||
// (Flarum\Core\Discussions\DiscussionsServiceProvider, etc.)
|
||||
$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'
|
||||
);
|
||||
|
||||
$this->app->bind(
|
||||
'Flarum\Core\Search\Discussions\Fulltext\DriverInterface',
|
||||
'Flarum\Core\Search\Discussions\Fulltext\MySqlFulltextDriver'
|
||||
);
|
||||
|
||||
$this->registerDiscussionGambits();
|
||||
$this->registerUserGambits();
|
||||
$this->registerAvatarsFilesystem();
|
||||
}
|
||||
|
||||
public function registerAvatarsFilesystem()
|
||||
{
|
||||
$avatarsFilesystem = function (Container $app) {
|
||||
return $app->make('Illuminate\Contracts\Filesystem\Factory')->disk('flarum-avatars')->getDriver();
|
||||
};
|
||||
|
||||
$this->app->when('Flarum\Core\Handlers\Commands\UploadAvatarCommandHandler')
|
||||
->needs('League\Flysystem\FilesystemInterface')
|
||||
->give($avatarsFilesystem);
|
||||
|
||||
$this->app->when('Flarum\Core\Handlers\Commands\DeleteAvatarCommandHandler')
|
||||
->needs('League\Flysystem\FilesystemInterface')
|
||||
->give($avatarsFilesystem);
|
||||
}
|
||||
|
||||
public function registerDiscussionGambits()
|
||||
{
|
||||
$this->app->instance('flarum.discussionGambits', [
|
||||
'Flarum\Core\Search\Discussions\Gambits\AuthorGambit',
|
||||
'Flarum\Core\Search\Discussions\Gambits\UnreadGambit'
|
||||
]);
|
||||
|
||||
$this->app->when('Flarum\Core\Search\Discussions\DiscussionSearcher')
|
||||
->needs('Flarum\Core\Search\GambitManager')
|
||||
->give(function (Container $app) {
|
||||
$gambits = new GambitManager($app);
|
||||
|
||||
foreach ($app->make('flarum.discussionGambits') as $gambit) {
|
||||
$gambits->add($gambit);
|
||||
}
|
||||
|
||||
$gambits->setFulltextGambit('Flarum\Core\Search\Discussions\Gambits\FulltextGambit');
|
||||
|
||||
return $gambits;
|
||||
});
|
||||
}
|
||||
|
||||
public function registerUserGambits()
|
||||
{
|
||||
$this->app->instance('flarum.userGambits', []);
|
||||
|
||||
$this->app->when('Flarum\Core\Search\Users\UserSearcher')
|
||||
->needs('Flarum\Core\Search\GambitManager')
|
||||
->give(function (Container $app) {
|
||||
$gambits = new GambitManager($app);
|
||||
|
||||
foreach ($app->make('flarum.userGambits') as $gambit) {
|
||||
$gambits->add($gambit);
|
||||
}
|
||||
|
||||
$gambits->setFulltextGambit('Flarum\Core\Search\Users\Gambits\FulltextGambit');
|
||||
|
||||
return $gambits;
|
||||
});
|
||||
}
|
||||
|
||||
public function addPostTypes()
|
||||
{
|
||||
$this->extend([
|
||||
new Extend\PostType('Flarum\Core\Models\CommentPost'),
|
||||
new Extend\PostType('Flarum\Core\Models\DiscussionRenamedPost')
|
||||
]);
|
||||
}
|
||||
|
||||
public function addEventHandlers()
|
||||
{
|
||||
$this->extend([
|
||||
new Extend\EventSubscriber('Flarum\Core\Handlers\Events\DiscussionMetadataUpdater'),
|
||||
new Extend\EventSubscriber('Flarum\Core\Handlers\Events\UserMetadataUpdater'),
|
||||
new Extend\EventSubscriber('Flarum\Core\Handlers\Events\EmailConfirmationMailer')
|
||||
]);
|
||||
}
|
||||
|
||||
public function bootModels()
|
||||
{
|
||||
Model::setValidator($this->app['validator']);
|
||||
|
||||
CommentPost::setFormatter($this->app['flarum.formatter']);
|
||||
|
||||
User::setHasher($this->app['hash']);
|
||||
User::setFormatter($this->app['flarum.formatter']);
|
||||
|
||||
User::registerPreference('discloseOnline', 'boolval', true);
|
||||
User::registerPreference('indexProfile', 'boolval', true);
|
||||
}
|
||||
|
||||
public function grantPermissions()
|
||||
{
|
||||
Forum::allow('*', function ($forum, $user, $action) {
|
||||
if ($user->hasPermission('forum.'.$action)) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
Post::allow('*', function ($post, $user, $action) {
|
||||
if ($post->discussion->can($user, $action.'Posts')) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// When fetching a discussion's posts: if the user doesn't have permission
|
||||
// to moderate the discussion, then they can't see posts that have been
|
||||
// hidden by someone other than themself.
|
||||
Discussion::addVisiblePostsScope(function ($query, User $user, Discussion $discussion) {
|
||||
if (! $discussion->can($user, 'editPosts')) {
|
||||
$query->where(function ($query) use ($user) {
|
||||
$query->whereNull('hide_user_id')
|
||||
->orWhere('hide_user_id', $user->id);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Post::allow('view', function ($post, $user) {
|
||||
if (! $post->hide_user_id || $post->can($user, 'edit')) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// A post is allowed to be edited if the user has permission to moderate
|
||||
// the discussion which it's in, or if they are the author and the post
|
||||
// hasn't been deleted by someone else.
|
||||
Post::allow('edit', function ($post, $user) {
|
||||
if ($post->discussion->can($user, 'editPosts') ||
|
||||
($post->user_id == $user->id && (! $post->hide_user_id || $post->hide_user_id == $user->id))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
User::allow('*', function ($discussion, $user, $action) {
|
||||
if ($user->hasPermission('user.'.$action)) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
User::allow(['edit', 'delete'], function ($user, $actor) {
|
||||
if ($user->id == $actor->id) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
Discussion::allow('*', function ($discussion, $user, $action) {
|
||||
if ($user->hasPermission('discussion.'.$action)) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// Allow a user to rename their own discussion.
|
||||
Discussion::allow('rename', function ($discussion, $user) {
|
||||
if ($discussion->start_user_id == $user->id) {
|
||||
return true;
|
||||
// @todo add limitations to time etc. according to a config setting
|
||||
}
|
||||
});
|
||||
|
||||
Discussion::allow('delete', function ($discussion, $user) {
|
||||
if ($discussion->start_user_id == $user->id && $discussion->participants_count == 1) {
|
||||
return true;
|
||||
// @todo add limitations to time etc. according to a config setting
|
||||
}
|
||||
});
|
||||
$this->app->register('Flarum\Core\Posts\PostsServiceProvider');
|
||||
$this->app->register('Flarum\Core\Users\UsersServiceProvider');
|
||||
}
|
||||
}
|
||||
|
@@ -40,6 +40,8 @@ class DatabaseServiceProvider extends ServiceProvider
|
||||
$this->app->booting(function() {
|
||||
$resolver = $this->app->make('Illuminate\Database\ConnectionResolverInterface');
|
||||
Model::setConnectionResolver($resolver);
|
||||
|
||||
Model::setEventDispatcher($this->app->make('events'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
41
src/Core/Discussions/Commands/DeleteDiscussion.php
Normal file
41
src/Core/Discussions/Commands/DeleteDiscussion.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php namespace Flarum\Core\Discussions\Commands;
|
||||
|
||||
use Flarum\Core\Users\User;
|
||||
|
||||
class DeleteDiscussion
|
||||
{
|
||||
/**
|
||||
* The ID of the discussion to delete.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $discussionId;
|
||||
|
||||
/**
|
||||
* The user performing the action.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* Any other user input associated with the action. This is unused by
|
||||
* default, but may be used by extensions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @param int $discussionId The ID of the discussion to delete.
|
||||
* @param User $actor The user performing the action.
|
||||
* @param array $data Any other user input associated with the action. This
|
||||
* is unused by default, but may be used by extensions.
|
||||
*/
|
||||
public function __construct($discussionId, User $actor, array $data = [])
|
||||
{
|
||||
$this->discussionId = $discussionId;
|
||||
$this->actor = $actor;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
44
src/Core/Discussions/Commands/DeleteDiscussionHandler.php
Normal file
44
src/Core/Discussions/Commands/DeleteDiscussionHandler.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php namespace Flarum\Core\Discussions\Commands;
|
||||
|
||||
use Flarum\Core\Discussions\DiscussionRepositoryInterface;
|
||||
use Flarum\Core\Discussions\Events\DiscussionWillBeDeleted;
|
||||
use Flarum\Core\Support\DispatchesEvents;
|
||||
|
||||
class DeleteDiscussionHandler
|
||||
{
|
||||
use DispatchesEvents;
|
||||
|
||||
/**
|
||||
* @var \Flarum\Core\Discussions\DiscussionRepositoryInterface
|
||||
*/
|
||||
protected $discussions;
|
||||
|
||||
/**
|
||||
* @param \Flarum\Core\Discussions\DiscussionRepositoryInterface $discussions
|
||||
*/
|
||||
public function __construct(DiscussionRepositoryInterface $discussions)
|
||||
{
|
||||
$this->discussions = $discussions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Flarum\Core\Discussions\Commands\DeleteDiscussion $command
|
||||
* @return \Flarum\Core\Discussions\Discussion
|
||||
*/
|
||||
public function handle(DeleteDiscussion $command)
|
||||
{
|
||||
$actor = $command->actor;
|
||||
|
||||
$discussion = $this->discussions->findOrFail($command->discussionId, $actor);
|
||||
|
||||
$discussion->assertCan($actor, 'delete');
|
||||
|
||||
event(new DiscussionWillBeDeleted($discussion, $actor, $command->data));
|
||||
|
||||
$discussion->delete();
|
||||
|
||||
$this->dispatchEventsFor($discussion);
|
||||
|
||||
return $discussion;
|
||||
}
|
||||
}
|
39
src/Core/Discussions/Commands/EditDiscussion.php
Normal file
39
src/Core/Discussions/Commands/EditDiscussion.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php namespace Flarum\Core\Discussions\Commands;
|
||||
|
||||
use Flarum\Core\Users\User;
|
||||
|
||||
class EditDiscussion
|
||||
{
|
||||
/**
|
||||
* The ID of the discussion to edit.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $discussionId;
|
||||
|
||||
/**
|
||||
* The user performing the action.
|
||||
*
|
||||
* @var \Flarum\Core\Users\User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* The attributes to update on the discussion.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @param integer $discussionId The ID of the discussion to edit.
|
||||
* @param \Flarum\Core\Users\User $actor The user performing the action.
|
||||
* @param array $data The attributes to update on the discussion.
|
||||
*/
|
||||
public function __construct($discussionId, User $actor, array $data)
|
||||
{
|
||||
$this->discussionId = $discussionId;
|
||||
$this->actor = $actor;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
50
src/Core/Discussions/Commands/EditDiscussionHandler.php
Normal file
50
src/Core/Discussions/Commands/EditDiscussionHandler.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php namespace Flarum\Core\Discussions\Commands;
|
||||
|
||||
use Flarum\Core\Discussions\DiscussionRepositoryInterface;
|
||||
use Flarum\Core\Discussions\Events\DiscussionWillBeSaved;
|
||||
use Flarum\Core\Support\DispatchesEvents;
|
||||
|
||||
class EditDiscussionHandler
|
||||
{
|
||||
use DispatchesEvents;
|
||||
|
||||
/**
|
||||
* @var DiscussionRepositoryInterface
|
||||
*/
|
||||
protected $discussions;
|
||||
|
||||
/**
|
||||
* @param DiscussionRepositoryInterface $discussions
|
||||
*/
|
||||
public function __construct(DiscussionRepositoryInterface $discussions)
|
||||
{
|
||||
$this->discussions = $discussions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EditDiscussion $command
|
||||
* @return \Flarum\Core\Discussions\Discussion
|
||||
* @throws \Flarum\Core\Exceptions\PermissionDeniedException
|
||||
*/
|
||||
public function handle(EditDiscussion $command)
|
||||
{
|
||||
$actor = $command->actor;
|
||||
$data = $command->data;
|
||||
$attributes = array_get($data, 'attributes', []);
|
||||
|
||||
$discussion = $this->discussions->findOrFail($command->discussionId, $actor);
|
||||
|
||||
if (isset($attributes['title'])) {
|
||||
$discussion->assertCan($actor, 'rename');
|
||||
$discussion->rename($attributes['title'], $actor);
|
||||
}
|
||||
|
||||
event(new DiscussionWillBeSaved($discussion, $actor, $data));
|
||||
|
||||
$discussion->save();
|
||||
|
||||
$this->dispatchEventsFor($discussion);
|
||||
|
||||
return $discussion;
|
||||
}
|
||||
}
|
39
src/Core/Discussions/Commands/ReadDiscussion.php
Normal file
39
src/Core/Discussions/Commands/ReadDiscussion.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php namespace Flarum\Core\Discussions\Commands;
|
||||
|
||||
use Flarum\Core\Users\User;
|
||||
|
||||
class ReadDiscussion
|
||||
{
|
||||
/**
|
||||
* The ID of the discussion to mark as read.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $discussionId;
|
||||
|
||||
/**
|
||||
* The user to mark the discussion as read for.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* The number of the post to mark as read.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $readNumber;
|
||||
|
||||
/**
|
||||
* @param integer $discussionId The ID of the discussion to mark as read.
|
||||
* @param User $actor The user to mark the discussion as read for.
|
||||
* @param integer $readNumber The number of the post to mark as read.
|
||||
*/
|
||||
public function __construct($discussionId, User $actor, $readNumber)
|
||||
{
|
||||
$this->discussionId = $discussionId;
|
||||
$this->actor = $actor;
|
||||
$this->readNumber = $readNumber;
|
||||
}
|
||||
}
|
51
src/Core/Discussions/Commands/ReadDiscussionHandler.php
Normal file
51
src/Core/Discussions/Commands/ReadDiscussionHandler.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php namespace Flarum\Core\Discussions\Commands;
|
||||
|
||||
use Flarum\Core\Discussions\DiscussionRepositoryInterface;
|
||||
use Flarum\Core\Discussions\Events\DiscussionStateWillBeSaved;
|
||||
use Flarum\Core\Exceptions\PermissionDeniedException;
|
||||
use Flarum\Core\Support\DispatchesEvents;
|
||||
|
||||
class ReadDiscussionHandler
|
||||
{
|
||||
use DispatchesEvents;
|
||||
|
||||
/**
|
||||
* @var DiscussionRepositoryInterface
|
||||
*/
|
||||
protected $discussions;
|
||||
|
||||
/**
|
||||
* @param DiscussionRepositoryInterface $discussions
|
||||
*/
|
||||
public function __construct(DiscussionRepositoryInterface $discussions)
|
||||
{
|
||||
$this->discussions = $discussions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReadDiscussion $command
|
||||
* @return \Flarum\Core\Discussions\DiscussionState
|
||||
* @throws \Flarum\Core\Exceptions\PermissionDeniedException
|
||||
*/
|
||||
public function handle(ReadDiscussion $command)
|
||||
{
|
||||
$actor = $command->actor;
|
||||
|
||||
if (! $actor->exists) {
|
||||
throw new PermissionDeniedException;
|
||||
}
|
||||
|
||||
$discussion = $this->discussions->findOrFail($command->discussionId, $actor);
|
||||
|
||||
$state = $discussion->stateFor($actor);
|
||||
$state->read($command->readNumber);
|
||||
|
||||
event(new DiscussionStateWillBeSaved($state));
|
||||
|
||||
$state->save();
|
||||
|
||||
$this->dispatchEventsFor($state);
|
||||
|
||||
return $state;
|
||||
}
|
||||
}
|
30
src/Core/Discussions/Commands/StartDiscussion.php
Normal file
30
src/Core/Discussions/Commands/StartDiscussion.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php namespace Flarum\Core\Discussions\Commands;
|
||||
|
||||
use Flarum\Core\Users\User;
|
||||
|
||||
class StartDiscussion
|
||||
{
|
||||
/**
|
||||
* The user authoring the discussion.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* The discussion attributes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @param User $actor The user authoring the discussion.
|
||||
* @param array $data The discussion attributes.
|
||||
*/
|
||||
public function __construct(User $actor, array $data)
|
||||
{
|
||||
$this->actor = $actor;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
@@ -1,43 +1,63 @@
|
||||
<?php namespace Flarum\Core\Handlers\Commands;
|
||||
<?php namespace Flarum\Core\Discussions\Commands;
|
||||
|
||||
use Flarum\Core\Discussions\Events\DiscussionWillBeSaved;
|
||||
use Flarum\Core\Forum;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Flarum\Core\Models\Discussion;
|
||||
use Flarum\Core\Events\DiscussionWillBeSaved;
|
||||
use Flarum\Core\Commands\PostReplyCommand;
|
||||
use Flarum\Core\Discussions\Discussion;
|
||||
use Flarum\Core\Posts\Commands\PostReply;
|
||||
use Flarum\Core\Support\DispatchesEvents;
|
||||
|
||||
class StartDiscussionCommandHandler
|
||||
class StartDiscussionHandler
|
||||
{
|
||||
use DispatchesEvents;
|
||||
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected $bus;
|
||||
|
||||
public function __construct(Dispatcher $bus)
|
||||
/**
|
||||
* @var Forum
|
||||
*/
|
||||
protected $forum;
|
||||
|
||||
/**
|
||||
* @param Dispatcher $bus
|
||||
*/
|
||||
public function __construct(Dispatcher $bus, Forum $forum)
|
||||
{
|
||||
$this->bus = $bus;
|
||||
$this->forum = $forum;
|
||||
}
|
||||
|
||||
public function handle($command)
|
||||
/**
|
||||
* @param StartDiscussion $command
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(StartDiscussion $command)
|
||||
{
|
||||
$command->forum->assertCan($command->user, 'startDiscussion');
|
||||
$actor = $command->actor;
|
||||
$data = $command->data;
|
||||
|
||||
$this->forum->assertCan($actor, '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(
|
||||
array_get($command->data, 'title'),
|
||||
$command->user
|
||||
array_get($data, 'attributes.title'),
|
||||
$actor
|
||||
);
|
||||
|
||||
event(new DiscussionWillBeSaved($discussion, $command));
|
||||
event(new DiscussionWillBeSaved($discussion, $actor, $data));
|
||||
|
||||
$discussion->save();
|
||||
|
||||
// Now that the discussion has been created, we can add the first post.
|
||||
// We will do this by running the PostReply command.
|
||||
$post = $this->bus->dispatch(
|
||||
new PostReplyCommand($discussion->id, $command->user, $command->data)
|
||||
new PostReply($discussion->id, $actor, $data)
|
||||
);
|
||||
|
||||
// Before we dispatch events, refresh our discussion instance's
|
||||
@@ -50,6 +70,6 @@ class StartDiscussionCommandHandler
|
||||
|
||||
$discussion->save();
|
||||
|
||||
return $post->discussion;
|
||||
return $discussion;
|
||||
}
|
||||
}
|
@@ -1,13 +1,17 @@
|
||||
<?php namespace Flarum\Core\Models;
|
||||
<?php namespace Flarum\Core\Discussions;
|
||||
|
||||
use Flarum\Core\Model;
|
||||
use Flarum\Core\Discussions\Events\DiscussionWasDeleted;
|
||||
use Flarum\Core\Discussions\Events\DiscussionWasStarted;
|
||||
use Flarum\Core\Discussions\Events\DiscussionWasRenamed;
|
||||
use Flarum\Core\Posts\Events\PostWasDeleted;
|
||||
use Flarum\Core\Posts\Post;
|
||||
use Flarum\Core\Posts\MergeablePost;
|
||||
use Flarum\Core\Users\Guest;
|
||||
use Flarum\Core\Users\User;
|
||||
use Flarum\Core\Support\EventGenerator;
|
||||
use Flarum\Core\Support\Locked;
|
||||
use Flarum\Core\Support\VisibleScope;
|
||||
use Flarum\Core\Events\DiscussionWasDeleted;
|
||||
use Flarum\Core\Events\DiscussionWasStarted;
|
||||
use Flarum\Core\Events\DiscussionWasRenamed;
|
||||
use Flarum\Core\Events\PostWasDeleted;
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
class Discussion extends Model
|
||||
{
|
||||
@@ -50,16 +54,16 @@ class Discussion extends Model
|
||||
/**
|
||||
* The user for which the state relationship should be loaded.
|
||||
*
|
||||
* @var \Flarum\Core\Models\User
|
||||
* @var User
|
||||
*/
|
||||
protected static $stateUser;
|
||||
|
||||
/**
|
||||
* An array of callables that apply constraints to the visiblePosts query.
|
||||
* An array of callables that apply constraints to the postsVisibleTo query.
|
||||
*
|
||||
* @var callable[]
|
||||
*/
|
||||
protected static $visiblePostsScopes = [];
|
||||
protected static $postVisibilityScopes = [];
|
||||
|
||||
/**
|
||||
* Boot the model.
|
||||
@@ -94,8 +98,8 @@ class Discussion extends Model
|
||||
* Start a new discussion. Raises the DiscussionWasStarted event.
|
||||
*
|
||||
* @param string $title
|
||||
* @param \Flarum\Core\Models\User $user
|
||||
* @return \Flarum\Core\Models\Discussion
|
||||
* @param User $user
|
||||
* @return static
|
||||
*/
|
||||
public static function start($title, User $user)
|
||||
{
|
||||
@@ -105,6 +109,8 @@ class Discussion extends Model
|
||||
$discussion->start_time = time();
|
||||
$discussion->start_user_id = $user->id;
|
||||
|
||||
$discussion->setRelation('startUser', $user);
|
||||
|
||||
$discussion->raise(new DiscussionWasStarted($discussion));
|
||||
|
||||
return $discussion;
|
||||
@@ -114,7 +120,7 @@ class Discussion extends Model
|
||||
* Rename the discussion. Raises the DiscussionWasRenamed event.
|
||||
*
|
||||
* @param string $title
|
||||
* @param \Flarum\Core\Models\User $user
|
||||
* @param User $user
|
||||
* @return $this
|
||||
*/
|
||||
public function rename($title, User $user)
|
||||
@@ -132,7 +138,7 @@ class Discussion extends Model
|
||||
/**
|
||||
* Set the discussion's start post details.
|
||||
*
|
||||
* @param \Flarum\Core\Models\Post $post
|
||||
* @param \Flarum\Core\Posts\Post $post
|
||||
* @return $this
|
||||
*/
|
||||
public function setStartPost(Post $post)
|
||||
@@ -147,7 +153,7 @@ class Discussion extends Model
|
||||
/**
|
||||
* Set the discussion's last post details.
|
||||
*
|
||||
* @param \Flarum\Core\Models\Post $post
|
||||
* @param \Flarum\Core\Posts\Post $post
|
||||
* @return $this
|
||||
*/
|
||||
public function setLastPost(Post $post)
|
||||
@@ -211,7 +217,7 @@ class Discussion extends Model
|
||||
* the same post as was originally intended to be saved. It also may not
|
||||
* exist, if the merge logic resulted in deletion.
|
||||
*/
|
||||
public function mergePost(Mergable $post)
|
||||
public function mergePost(MergeablePost $post)
|
||||
{
|
||||
$lastPost = $this->posts()->latest('time')->first();
|
||||
|
||||
@@ -225,21 +231,21 @@ class Discussion extends Model
|
||||
*/
|
||||
public function posts()
|
||||
{
|
||||
return $this->hasMany('Flarum\Core\Models\Post');
|
||||
return $this->hasMany('Flarum\Core\Posts\Post');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with the discussion's posts, but only ones which
|
||||
* are visible to the given user.
|
||||
*
|
||||
* @param \Flarum\Core\Models\User $user
|
||||
* @param User $user
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function visiblePosts(User $user)
|
||||
public function postsVisibleTo(User $user)
|
||||
{
|
||||
$query = $this->posts();
|
||||
|
||||
foreach (static::$visiblePostsScopes as $scope) {
|
||||
foreach (static::$postVisibilityScopes as $scope) {
|
||||
$scope($query, $user, $this);
|
||||
}
|
||||
|
||||
@@ -253,7 +259,7 @@ class Discussion extends Model
|
||||
*/
|
||||
public function comments()
|
||||
{
|
||||
return $this->visiblePosts(new Guest)->where('type', 'comment');
|
||||
return $this->postsVisibleTo(new Guest)->where('type', 'comment');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,7 +283,7 @@ class Discussion extends Model
|
||||
*/
|
||||
public function startPost()
|
||||
{
|
||||
return $this->belongsTo('Flarum\Core\Models\Post', 'start_post_id');
|
||||
return $this->belongsTo('Flarum\Core\Posts\Post', 'start_post_id');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,7 +293,7 @@ class Discussion extends Model
|
||||
*/
|
||||
public function startUser()
|
||||
{
|
||||
return $this->belongsTo('Flarum\Core\Models\User', 'start_user_id');
|
||||
return $this->belongsTo('Flarum\Core\Users\User', 'start_user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -297,7 +303,7 @@ class Discussion extends Model
|
||||
*/
|
||||
public function lastPost()
|
||||
{
|
||||
return $this->belongsTo('Flarum\Core\Models\Post', 'last_post_id');
|
||||
return $this->belongsTo('Flarum\Core\Posts\Post', 'last_post_id');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,7 +313,7 @@ class Discussion extends Model
|
||||
*/
|
||||
public function lastUser()
|
||||
{
|
||||
return $this->belongsTo('Flarum\Core\Models\User', 'last_user_id');
|
||||
return $this->belongsTo('Flarum\Core\Users\User', 'last_user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -317,7 +323,7 @@ class Discussion extends Model
|
||||
*/
|
||||
public function readers()
|
||||
{
|
||||
return $this->belongsToMany('Flarum\Core\Models\User', 'users_discussions');
|
||||
return $this->belongsToMany('Flarum\Core\Users\User', 'users_discussions');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -327,24 +333,24 @@ class Discussion extends Model
|
||||
* If no user is passed (i.e. in the case of eager loading the 'state'
|
||||
* relation), then the static `$stateUser` property is used.
|
||||
*
|
||||
* @see \Flarum\Core\Models\Discussion::setStateUser()
|
||||
* @see Discussion::setStateUser()
|
||||
*
|
||||
* @param \Flarum\Core\Models\User $user
|
||||
* @param User|null $user
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function state(User $user = null)
|
||||
{
|
||||
$user = $user ?: static::$stateUser;
|
||||
|
||||
return $this->hasOne('Flarum\Core\Models\DiscussionState')->where('user_id', $user ? $user->id : null);
|
||||
return $this->hasOne('Flarum\Core\Discussions\DiscussionState')->where('user_id', $user ? $user->id : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the state model for a user, or instantiate a new one if it does not
|
||||
* exist.
|
||||
*
|
||||
* @param \Flarum\Core\Models\User $user
|
||||
* @return \Flarum\Core\Models\DiscussionState
|
||||
* @param User $user
|
||||
* @return \Flarum\Core\Discussions\DiscussionState
|
||||
*/
|
||||
public function stateFor(User $user)
|
||||
{
|
||||
@@ -362,7 +368,7 @@ class Discussion extends Model
|
||||
/**
|
||||
* Set the user for which the state relationship should be loaded.
|
||||
*
|
||||
* @param \Flarum\Core\Models\User $user
|
||||
* @param User $user
|
||||
*/
|
||||
public static function setStateUser(User $user)
|
||||
{
|
||||
@@ -376,8 +382,8 @@ class Discussion extends Model
|
||||
* query. It is passed three parameters: the query builder object, the
|
||||
* user to constrain posts for, and the discussion instance.
|
||||
*/
|
||||
public static function addVisiblePostsScope(callable $scope)
|
||||
public static function addPostVisibilityScope(callable $scope)
|
||||
{
|
||||
static::$visiblePostsScopes[] = $scope;
|
||||
static::$postVisibilityScopes[] = $scope;
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
<?php namespace Flarum\Core\Repositories;
|
||||
<?php namespace Flarum\Core\Discussions;
|
||||
|
||||
use Flarum\Core\Models\User;
|
||||
use Flarum\Core\Users\User;
|
||||
|
||||
interface DiscussionRepositoryInterface
|
||||
{
|
||||
@@ -15,18 +15,18 @@ interface DiscussionRepositoryInterface
|
||||
* Find a discussion by ID, optionally making sure it is visible to a certain
|
||||
* user, or throw an exception.
|
||||
*
|
||||
* @param integer $id
|
||||
* @param \Flarum\Core\Models\User $user
|
||||
* @return \Flarum\Core\Models\Discussion
|
||||
* @param integer $id
|
||||
* @param \Flarum\Core\Users\User $actor
|
||||
* @return \Flarum\Core\Discussions\Discussion
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public function findOrFail($id, User $user = null);
|
||||
public function findOrFail($id, User $actor = null);
|
||||
|
||||
/**
|
||||
* Get the IDs of discussions which a user has read completely.
|
||||
*
|
||||
* @param \Flarum\Core\Models\User $user
|
||||
* @param \Flarum\Core\Users\User $user
|
||||
* @return array
|
||||
*/
|
||||
public function getReadIds(User $user);
|
@@ -1,6 +1,7 @@
|
||||
<?php namespace Flarum\Core\Models;
|
||||
<?php namespace Flarum\Core\Discussions;
|
||||
|
||||
use Flarum\Core\Events\DiscussionWasRead;
|
||||
use Flarum\Core\Discussions\Events\DiscussionWasRead;
|
||||
use Flarum\Core\Model;
|
||||
use Flarum\Core\Support\EventGenerator;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
@@ -16,16 +17,12 @@ class DiscussionState extends Model
|
||||
use EventGenerator;
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $table = 'users_discussions';
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $dateAttributes = ['read_time'];
|
||||
|
||||
@@ -55,7 +52,7 @@ class DiscussionState extends Model
|
||||
*/
|
||||
public function discussion()
|
||||
{
|
||||
return $this->belongsTo('Flarum\Core\Models\Discussion', 'discussion_id');
|
||||
return $this->belongsTo('Flarum\Core\Discussions\Discussion', 'discussion_id');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,14 +62,14 @@ class DiscussionState extends Model
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('Flarum\Core\Models\User', 'user_id');
|
||||
return $this->belongsTo('Flarum\Core\Users\User', 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the keys for a save update query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
* @param Builder $query
|
||||
* @return Builder
|
||||
*/
|
||||
protected function setKeysForSaveQuery(Builder $query)
|
||||
{
|
74
src/Core/Discussions/DiscussionsServiceProvider.php
Normal file
74
src/Core/Discussions/DiscussionsServiceProvider.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php namespace Flarum\Core\Discussions;
|
||||
|
||||
use Flarum\Core\Search\GambitManager;
|
||||
use Flarum\Core\Users\User;
|
||||
use Flarum\Support\ServiceProvider;
|
||||
use Flarum\Extend;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class DiscussionsServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap the application events.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->extend([
|
||||
new Extend\EventSubscriber('Flarum\Core\Discussions\Listeners\DiscussionMetadataUpdater')
|
||||
]);
|
||||
|
||||
Discussion::allow('*', function (Discussion $discussion, User $user, $action) {
|
||||
return $user->hasPermission('discussion.'.$action) ?: null;
|
||||
});
|
||||
|
||||
// Allow a user to rename their own discussion.
|
||||
Discussion::allow('rename', function (Discussion $discussion, User $user) {
|
||||
return $discussion->start_user_id == $user->id ?: null;
|
||||
// TODO: add limitations to time etc. according to a config setting
|
||||
});
|
||||
|
||||
Discussion::allow('delete', function (Discussion $discussion, User $user) {
|
||||
return $discussion->start_user_id == $user->id && $discussion->participants_count == 1 ?: null;
|
||||
// TODO: add limitations to time etc. according to a config setting
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the service provider.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->app->bind(
|
||||
'Flarum\Core\Discussions\DiscussionRepositoryInterface',
|
||||
'Flarum\Core\Discussions\EloquentDiscussionRepository'
|
||||
);
|
||||
|
||||
$this->app->bind(
|
||||
'Flarum\Core\Discussions\Search\Fulltext\DriverInterface',
|
||||
'Flarum\Core\Discussions\Search\Fulltext\MySqlFulltextDriver'
|
||||
);
|
||||
|
||||
$this->app->instance('flarum.discussionGambits', [
|
||||
'Flarum\Core\Discussions\Search\Gambits\AuthorGambit',
|
||||
'Flarum\Core\Discussions\Search\Gambits\UnreadGambit'
|
||||
]);
|
||||
|
||||
$this->app->when('Flarum\Core\Discussions\Search\DiscussionSearcher')
|
||||
->needs('Flarum\Core\Search\GambitManager')
|
||||
->give(function (Container $app) {
|
||||
$gambits = new GambitManager($app);
|
||||
|
||||
foreach ($app->make('flarum.discussionGambits') as $gambit) {
|
||||
$gambits->add($gambit);
|
||||
}
|
||||
|
||||
$gambits->setFulltextGambit('Flarum\Core\Discussions\Search\Gambits\FulltextGambit');
|
||||
|
||||
return $gambits;
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,15 +1,14 @@
|
||||
<?php namespace Flarum\Core\Repositories;
|
||||
<?php namespace Flarum\Core\Discussions;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Flarum\Core\Models\Discussion;
|
||||
use Flarum\Core\Models\User;
|
||||
use Flarum\Core\Users\User;
|
||||
|
||||
class EloquentDiscussionRepository implements DiscussionRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* Get a new query builder for the discussions table.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
* @return Builder
|
||||
*/
|
||||
public function query()
|
||||
{
|
||||
@@ -17,14 +16,12 @@ class EloquentDiscussionRepository implements DiscussionRepositoryInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a discussion by ID, optionally making sure it is visible to a certain
|
||||
* user, or throw an exception.
|
||||
* Find a discussion by ID, optionally making sure it is visible to a
|
||||
* certain user, or throw an exception.
|
||||
*
|
||||
* @param integer $id
|
||||
* @param \Flarum\Core\Models\User $user
|
||||
* @return \Flarum\Core\Models\Discussion
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
* @param integer $id
|
||||
* @param \Flarum\Core\Users\User $user
|
||||
* @return \Flarum\Core\Discussions\Discussion
|
||||
*/
|
||||
public function findOrFail($id, User $user = null)
|
||||
{
|
||||
@@ -36,7 +33,7 @@ class EloquentDiscussionRepository implements DiscussionRepositoryInterface
|
||||
/**
|
||||
* Get the IDs of discussions which a user has read completely.
|
||||
*
|
||||
* @param \Flarum\Core\Models\User $user
|
||||
* @param \Flarum\Core\Users\User $user
|
||||
* @return array
|
||||
*/
|
||||
public function getReadIds(User $user)
|
||||
@@ -50,9 +47,9 @@ class EloquentDiscussionRepository implements DiscussionRepositoryInterface
|
||||
/**
|
||||
* 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
|
||||
* @param Builder $query
|
||||
* @param \Flarum\Core\Users\User $user
|
||||
* @return Builder
|
||||
*/
|
||||
protected function scopeVisibleTo(Builder $query, User $user = null)
|
||||
{
|
@@ -0,0 +1,27 @@
|
||||
<?php namespace Flarum\Core\Discussions\Events;
|
||||
|
||||
use Flarum\Core\Discussions\Search\DiscussionSearch;
|
||||
use Flarum\Core\Search\SearchCriteria;
|
||||
|
||||
class DiscussionSearchWillBePerformed
|
||||
{
|
||||
/**
|
||||
* @var DiscussionSearch
|
||||
*/
|
||||
public $search;
|
||||
|
||||
/**
|
||||
* @var SearchCriteria
|
||||
*/
|
||||
public $criteria;
|
||||
|
||||
/**
|
||||
* @param DiscussionSearch $search
|
||||
* @param SearchCriteria $criteria
|
||||
*/
|
||||
public function __construct(DiscussionSearch $search, SearchCriteria $criteria)
|
||||
{
|
||||
$this->search = $search;
|
||||
$this->criteria = $criteria;
|
||||
}
|
||||
}
|
19
src/Core/Discussions/Events/DiscussionStateWillBeSaved.php
Normal file
19
src/Core/Discussions/Events/DiscussionStateWillBeSaved.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php namespace Flarum\Core\Discussions\Events;
|
||||
|
||||
use Flarum\Core\Discussions\DiscussionState;
|
||||
|
||||
class DiscussionStateWillBeSaved
|
||||
{
|
||||
/**
|
||||
* @var DiscussionState
|
||||
*/
|
||||
public $state;
|
||||
|
||||
/**
|
||||
* @param DiscussionState $state
|
||||
*/
|
||||
public function __construct(DiscussionState $state)
|
||||
{
|
||||
$this->state = $state;
|
||||
}
|
||||
}
|
20
src/Core/Discussions/Events/DiscussionWasDeleted.php
Normal file
20
src/Core/Discussions/Events/DiscussionWasDeleted.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php namespace Flarum\Core\Discussions\Events;
|
||||
|
||||
use Flarum\Core\Discussions\Discussion;
|
||||
use Flarum\Core\Users\User;
|
||||
|
||||
class DiscussionWasDeleted
|
||||
{
|
||||
/**
|
||||
* @var Discussion
|
||||
*/
|
||||
public $discussion;
|
||||
|
||||
/**
|
||||
* @param Discussion $discussion
|
||||
*/
|
||||
public function __construct(Discussion $discussion)
|
||||
{
|
||||
$this->discussion = $discussion;
|
||||
}
|
||||
}
|
19
src/Core/Discussions/Events/DiscussionWasRead.php
Normal file
19
src/Core/Discussions/Events/DiscussionWasRead.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php namespace Flarum\Core\Discussions\Events;
|
||||
|
||||
use Flarum\Core\Discussions\DiscussionState;
|
||||
|
||||
class DiscussionWasRead
|
||||
{
|
||||
/**
|
||||
* @var DiscussionState
|
||||
*/
|
||||
public $state;
|
||||
|
||||
/**
|
||||
* @param DiscussionState $state
|
||||
*/
|
||||
public function __construct(DiscussionState $state)
|
||||
{
|
||||
$this->state = $state;
|
||||
}
|
||||
}
|
34
src/Core/Discussions/Events/DiscussionWasRenamed.php
Normal file
34
src/Core/Discussions/Events/DiscussionWasRenamed.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php namespace Flarum\Core\Discussions\Events;
|
||||
|
||||
use Flarum\Core\Discussions\Discussion;
|
||||
use Flarum\Core\Users\User;
|
||||
|
||||
class DiscussionWasRenamed
|
||||
{
|
||||
/**
|
||||
* @var Discussion
|
||||
*/
|
||||
public $discussion;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $oldTitle;
|
||||
|
||||
/**
|
||||
* @param Discussion $discussion
|
||||
* @param User $actor
|
||||
* @param string $oldTitle
|
||||
*/
|
||||
public function __construct(Discussion $discussion, User $actor, $oldTitle)
|
||||
{
|
||||
$this->discussion = $discussion;
|
||||
$this->actor = $actor;
|
||||
$this->oldTitle = $oldTitle;
|
||||
}
|
||||
}
|
19
src/Core/Discussions/Events/DiscussionWasStarted.php
Normal file
19
src/Core/Discussions/Events/DiscussionWasStarted.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php namespace Flarum\Core\Discussions\Events;
|
||||
|
||||
use Flarum\Core\Discussions\Discussion;
|
||||
|
||||
class DiscussionWasStarted
|
||||
{
|
||||
/**
|
||||
* @var Discussion
|
||||
*/
|
||||
public $discussion;
|
||||
|
||||
/**
|
||||
* @param Discussion $discussion
|
||||
*/
|
||||
public function __construct(Discussion $discussion)
|
||||
{
|
||||
$this->discussion = $discussion;
|
||||
}
|
||||
}
|
40
src/Core/Discussions/Events/DiscussionWillBeDeleted.php
Normal file
40
src/Core/Discussions/Events/DiscussionWillBeDeleted.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php namespace Flarum\Core\Discussions\Events;
|
||||
|
||||
use Flarum\Core\Discussions\Discussion;
|
||||
use Flarum\Core\Users\User;
|
||||
|
||||
class DiscussionWillBeDeleted
|
||||
{
|
||||
/**
|
||||
* The discussion that is going to be deleted.
|
||||
*
|
||||
* @var Discussion
|
||||
*/
|
||||
public $discussion;
|
||||
|
||||
/**
|
||||
* The user who is performing the action.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* Any user input associated with the command.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @param Discussion $discussion
|
||||
* @param User $actor
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(Discussion $discussion, User $actor, array $data = [])
|
||||
{
|
||||
$this->discussion = $discussion;
|
||||
$this->actor = $actor;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
40
src/Core/Discussions/Events/DiscussionWillBeSaved.php
Normal file
40
src/Core/Discussions/Events/DiscussionWillBeSaved.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php namespace Flarum\Core\Discussions\Events;
|
||||
|
||||
use Flarum\Core\Discussions\Discussion;
|
||||
use Flarum\Core\Users\User;
|
||||
|
||||
class DiscussionWillBeSaved
|
||||
{
|
||||
/**
|
||||
* The discussion that will be saved.
|
||||
*
|
||||
* @var Discussion
|
||||
*/
|
||||
public $discussion;
|
||||
|
||||
/**
|
||||
* The user who is performing the action.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* Any user input associated with the command.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @param Discussion $discussion
|
||||
* @param User $actor
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(Discussion $discussion, User $actor, array $data = [])
|
||||
{
|
||||
$this->discussion = $discussion;
|
||||
$this->actor = $actor;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
@@ -1,27 +1,28 @@
|
||||
<?php namespace Flarum\Core\Handlers\Events;
|
||||
<?php namespace Flarum\Core\Discussions\Listeners;
|
||||
|
||||
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;
|
||||
use Flarum\Core\Posts\Post;
|
||||
use Flarum\Core\Posts\Events\PostWasPosted;
|
||||
use Flarum\Core\Posts\Events\PostWasDeleted;
|
||||
use Flarum\Core\Posts\Events\PostWasHidden;
|
||||
use Flarum\Core\Posts\Events\PostWasRestored;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
||||
class DiscussionMetadataUpdater
|
||||
{
|
||||
/**
|
||||
* Register the listeners for the subscriber.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $events
|
||||
* @param Dispatcher $events
|
||||
*/
|
||||
public function subscribe(Dispatcher $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(PostWasPosted::class, __CLASS__.'@whenPostWasPosted');
|
||||
$events->listen(PostWasDeleted::class, __CLASS__.'@whenPostWasDeleted');
|
||||
$events->listen(PostWasHidden::class, __CLASS__.'@whenPostWasHidden');
|
||||
$events->listen(PostWasRestored::class, __CLASS__.'@whenPostWasRestored');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PostWasPosted $event
|
||||
*/
|
||||
public function whenPostWasPosted(PostWasPosted $event)
|
||||
{
|
||||
$discussion = $event->post->discussion;
|
||||
@@ -32,16 +33,25 @@ class DiscussionMetadataUpdater
|
||||
$discussion->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PostWasDeleted $event
|
||||
*/
|
||||
public function whenPostWasDeleted(PostWasDeleted $event)
|
||||
{
|
||||
$this->removePost($event->post);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PostWasHidden $event
|
||||
*/
|
||||
public function whenPostWasHidden(PostWasHidden $event)
|
||||
{
|
||||
$this->removePost($event->post);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PostWasRestored $event
|
||||
*/
|
||||
public function whenPostWasRestored(PostWasRestored $event)
|
||||
{
|
||||
$discussion = $event->post->discussion;
|
||||
@@ -52,6 +62,9 @@ class DiscussionMetadataUpdater
|
||||
$discussion->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Post $post
|
||||
*/
|
||||
protected function removePost(Post $post)
|
||||
{
|
||||
$discussion = $post->discussion;
|
55
src/Core/Discussions/Search/DiscussionSearch.php
Normal file
55
src/Core/Discussions/Search/DiscussionSearch.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php namespace Flarum\Core\Discussions\Search;
|
||||
|
||||
use Flarum\Core\Search\Search;
|
||||
|
||||
/**
|
||||
* An object which represents the internal state of a search for discussions:
|
||||
* the search query, the user performing the search, the fallback sort order,
|
||||
* relevant post information, and a log of which gambits have been used.
|
||||
*/
|
||||
class DiscussionSearch extends Search
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultSort = ['lastTime' => 'desc'];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $relevantPostIds = [];
|
||||
|
||||
/**
|
||||
* Get the related IDs for each result.
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public function getRelevantPostIds()
|
||||
{
|
||||
return $this->relevantPostIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the relevant post IDs for a result.
|
||||
*
|
||||
* @param int $discussionId
|
||||
* @param int[] $postIds
|
||||
* @return void
|
||||
*/
|
||||
public function setRelevantPostIds($discussionId, array $postIds)
|
||||
{
|
||||
$this->relevantPostIds[$discussionId] = $postIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relevant post ID for a discussion result.
|
||||
*
|
||||
* @param int $discussionId
|
||||
* @param int $postId
|
||||
* @return void
|
||||
*/
|
||||
public function addRelevantPostId($discussionId, $postId)
|
||||
{
|
||||
$this->relevantPostIds[$discussionId][] = $postId;
|
||||
}
|
||||
}
|
124
src/Core/Discussions/Search/DiscussionSearcher.php
Normal file
124
src/Core/Discussions/Search/DiscussionSearcher.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php namespace Flarum\Core\Discussions\Search;
|
||||
|
||||
use Flarum\Core\Discussions\Discussion;
|
||||
use Flarum\Core\Search\AppliesParametersToSearch;
|
||||
use Flarum\Core\Search\SearchCriteria;
|
||||
use Flarum\Core\Search\SearcherInterface;
|
||||
use Flarum\Core\Search\GambitManager;
|
||||
use Flarum\Core\Discussions\DiscussionRepositoryInterface;
|
||||
use Flarum\Core\Posts\PostRepositoryInterface;
|
||||
use Flarum\Core\Discussions\Events\DiscussionSearchWillBePerformed;
|
||||
use Flarum\Core\Search\SearchResults;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
/**
|
||||
* Takes a DiscussionSearchCriteria object, performs a search using gambits,
|
||||
* and spits out a DiscussionSearchResults object.
|
||||
*/
|
||||
class DiscussionSearcher
|
||||
{
|
||||
use AppliesParametersToSearch;
|
||||
|
||||
/**
|
||||
* @var GambitManager
|
||||
*/
|
||||
protected $gambits;
|
||||
|
||||
/**
|
||||
* @var DiscussionRepositoryInterface
|
||||
*/
|
||||
protected $discussions;
|
||||
|
||||
/**
|
||||
* @var PostRepositoryInterface
|
||||
*/
|
||||
protected $posts;
|
||||
|
||||
/**
|
||||
* @param GambitManager $gambits
|
||||
* @param DiscussionRepositoryInterface $discussions
|
||||
* @param PostRepositoryInterface $posts
|
||||
*/
|
||||
public function __construct(
|
||||
GambitManager $gambits,
|
||||
DiscussionRepositoryInterface $discussions,
|
||||
PostRepositoryInterface $posts
|
||||
) {
|
||||
$this->gambits = $gambits;
|
||||
$this->discussions = $discussions;
|
||||
$this->posts = $posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchCriteria $criteria
|
||||
* @param int|null $limit
|
||||
* @param int $offset
|
||||
* @param array $load An array of relationships to load on the results.
|
||||
* @return SearchResults
|
||||
*/
|
||||
public function search(SearchCriteria $criteria, $limit = null, $offset = 0, array $load = [])
|
||||
{
|
||||
$actor = $criteria->actor;
|
||||
|
||||
$query = $this->discussions->query()->whereVisibleTo($actor);
|
||||
|
||||
// Construct an object which represents this search for discussions.
|
||||
// Apply gambits to it, sort, and paging criteria. Also give extensions
|
||||
// an opportunity to modify it.
|
||||
$search = new DiscussionSearch($query->getQuery(), $actor);
|
||||
|
||||
$this->gambits->apply($search, $criteria->query);
|
||||
$this->applySort($search, $criteria->sort);
|
||||
$this->applyOffset($search, $offset);
|
||||
$this->applyLimit($search, $limit + 1);
|
||||
|
||||
event(new DiscussionSearchWillBePerformed($search, $criteria));
|
||||
|
||||
// Execute the search query and retrieve the results. We get one more
|
||||
// results than the user asked for, so that we can say if there are more
|
||||
// results. If there are, we will get rid of that extra result.
|
||||
$discussions = $query->get();
|
||||
|
||||
if ($areMoreResults = ($limit > 0 && $discussions->count() > $limit)) {
|
||||
$discussions->pop();
|
||||
}
|
||||
|
||||
// The relevant posts relationship isn't a typical Eloquent
|
||||
// relationship; rather, we need to extract that information from our
|
||||
// search object. We will delegate that task and prevent Eloquent
|
||||
// from trying to load it.
|
||||
if (in_array('relevantPosts', $load)) {
|
||||
$this->loadRelevantPosts($discussions, $search);
|
||||
|
||||
$load = array_diff($load, ['relevantPosts', 'relevantPosts.discussion', 'relevantPosts.user']);
|
||||
}
|
||||
|
||||
Discussion::setStateUser($actor);
|
||||
$discussions->load($load);
|
||||
|
||||
return new SearchResults($discussions, $areMoreResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load relevant posts onto each discussion using information from the
|
||||
* search.
|
||||
*
|
||||
* @param Collection $discussions
|
||||
* @param DiscussionSearch $search
|
||||
*/
|
||||
protected function loadRelevantPosts(Collection $discussions, DiscussionSearch $search)
|
||||
{
|
||||
$postIds = [];
|
||||
foreach ($search->getRelevantPostIds() as $relevantPostIds) {
|
||||
$postIds = array_merge($postIds, array_slice($relevantPostIds, 0, 2));
|
||||
}
|
||||
|
||||
$posts = $postIds ? $this->posts->findByIds($postIds, $search->getActor())->load('user')->all() : [];
|
||||
|
||||
foreach ($discussions as $discussion) {
|
||||
$discussion->relevantPosts = array_filter($posts, function ($post) use ($discussion) {
|
||||
return $post->discussion_id == $discussion->id;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
<?php namespace Flarum\Core\Search\Discussions\Fulltext;
|
||||
<?php namespace Flarum\Core\Discussions\Search\Fulltext;
|
||||
|
||||
interface DriverInterface
|
||||
{
|
@@ -1,6 +1,6 @@
|
||||
<?php namespace Flarum\Core\Search\Discussions\Fulltext;
|
||||
<?php namespace Flarum\Core\Discussions\Search\Fulltext;
|
||||
|
||||
use Flarum\Core\Models\Post;
|
||||
use Flarum\Core\Posts\Post;
|
||||
|
||||
class MySqlFulltextDriver implements DriverInterface
|
||||
{
|
44
src/Core/Discussions/Search/Gambits/AuthorGambit.php
Normal file
44
src/Core/Discussions/Search/Gambits/AuthorGambit.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php namespace Flarum\Core\Discussions\Search\Gambits;
|
||||
|
||||
use Flarum\Core\Discussions\Search\DiscussionSearch;
|
||||
use Flarum\Core\Users\UserRepositoryInterface;
|
||||
use Flarum\Core\Search\RegexGambit;
|
||||
use Flarum\Core\Search\Search;
|
||||
use LogicException;
|
||||
|
||||
class AuthorGambit extends RegexGambit
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $pattern = 'author:(.+)';
|
||||
|
||||
/**
|
||||
* @var UserRepositoryInterface
|
||||
*/
|
||||
protected $users;
|
||||
|
||||
/**
|
||||
* @param UserRepositoryInterface $users
|
||||
*/
|
||||
public function __construct(UserRepositoryInterface $users)
|
||||
{
|
||||
$this->users = $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(Search $search, array $matches, $negate)
|
||||
{
|
||||
if (! $search instanceof DiscussionSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
|
||||
}
|
||||
|
||||
$username = trim($matches[1], '"');
|
||||
|
||||
$id = $this->users->getIdForUsername($username);
|
||||
|
||||
$search->getQuery()->where('start_user_id', $negate ? '!=' : '=', $id);
|
||||
}
|
||||
}
|
47
src/Core/Discussions/Search/Gambits/FulltextGambit.php
Normal file
47
src/Core/Discussions/Search/Gambits/FulltextGambit.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php namespace Flarum\Core\Discussions\Search\Gambits;
|
||||
|
||||
use Flarum\Core\Discussions\Search\DiscussionSearch;
|
||||
use Flarum\Core\Posts\PostRepositoryInterface;
|
||||
use Flarum\Core\Search\Search;
|
||||
use Flarum\Core\Search\GambitInterface;
|
||||
use LogicException;
|
||||
|
||||
class FulltextGambit implements GambitInterface
|
||||
{
|
||||
/**
|
||||
* @var PostRepositoryInterface
|
||||
*/
|
||||
protected $posts;
|
||||
|
||||
/**
|
||||
* @param PostRepositoryInterface $posts
|
||||
*/
|
||||
public function __construct(PostRepositoryInterface $posts)
|
||||
{
|
||||
$this->posts = $posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(Search $search, $bit)
|
||||
{
|
||||
if (! $search instanceof DiscussionSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
|
||||
}
|
||||
|
||||
$posts = $this->posts->findByContent($bit, $search->getActor());
|
||||
|
||||
$discussions = [];
|
||||
foreach ($posts as $post) {
|
||||
$discussions[] = $id = $post->discussion_id;
|
||||
$search->addRelevantPostId($id, $post->id);
|
||||
}
|
||||
$discussions = array_unique($discussions);
|
||||
|
||||
// TODO: implement negate (match for - at start of string)
|
||||
$search->getQuery()->whereIn('id', $discussions);
|
||||
|
||||
$search->setDefaultSort(['id' => $discussions]);
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
<?php namespace Flarum\Core\Search;
|
||||
|
||||
abstract class GambitAbstract
|
||||
abstract class RegexGambit implements Gambit
|
||||
{
|
||||
protected $pattern;
|
||||
|
52
src/Core/Discussions/Search/Gambits/UnreadGambit.php
Normal file
52
src/Core/Discussions/Search/Gambits/UnreadGambit.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php namespace Flarum\Core\Discussions\Search\Gambits;
|
||||
|
||||
use Flarum\Core\Discussions\DiscussionRepositoryInterface;
|
||||
use Flarum\Core\Discussions\Search\DiscussionSearch;
|
||||
use Flarum\Core\Search\RegexGambit;
|
||||
use Flarum\Core\Search\Search;
|
||||
use LogicException;
|
||||
|
||||
class UnreadGambit extends RegexGambit
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $pattern = 'is:unread';
|
||||
|
||||
/**
|
||||
* @var DiscussionRepositoryInterface
|
||||
*/
|
||||
protected $discussions;
|
||||
|
||||
/**
|
||||
* @param DiscussionRepositoryInterface $discussions
|
||||
*/
|
||||
public function __construct(DiscussionRepositoryInterface $discussions)
|
||||
{
|
||||
$this->discussions = $discussions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(Search $search, array $matches, $negate)
|
||||
{
|
||||
if (! $search instanceof DiscussionSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
|
||||
}
|
||||
|
||||
$actor = $search->getActor();
|
||||
|
||||
if ($actor->exists) {
|
||||
$readIds = $this->discussions->getReadIds($actor);
|
||||
|
||||
$search->getQuery()->where(function ($query) use ($readIds, $negate, $actor) {
|
||||
if (! $negate) {
|
||||
$query->whereNotIn('id', $readIds)->where('last_time', '>', $actor->read_time ?: 0);
|
||||
} else {
|
||||
$query->whereIn('id', $readIds)->orWhere('last_time', '<=', $actor->read_time ?: 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
class AvatarWillBeDeleted
|
||||
{
|
||||
public $user;
|
||||
|
||||
public $command;
|
||||
|
||||
public function __construct(User $user, $command)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->command = $command;
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
class AvatarWillBeUploaded
|
||||
{
|
||||
public $user;
|
||||
|
||||
public $command;
|
||||
|
||||
public function __construct(User $user, $command)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->command = $command;
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Search\Discussions\DiscussionSearcher;
|
||||
|
||||
class DiscussionSearchWillBePerformed
|
||||
{
|
||||
public $searcher;
|
||||
|
||||
public $criteria;
|
||||
|
||||
public function __construct(DiscussionSearcher $searcher, $criteria)
|
||||
{
|
||||
$this->searcher = $searcher;
|
||||
$this->criteria = $criteria;
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\Discussion;
|
||||
|
||||
class DiscussionWasDeleted
|
||||
{
|
||||
public $discussion;
|
||||
|
||||
public function __construct(Discussion $discussion)
|
||||
{
|
||||
$this->discussion = $discussion;
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\DiscussionState;
|
||||
|
||||
class DiscussionWasRead
|
||||
{
|
||||
public $state;
|
||||
|
||||
public function __construct(DiscussionState $state)
|
||||
{
|
||||
$this->state = $state;
|
||||
}
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\Discussion;
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
class DiscussionWasRenamed
|
||||
{
|
||||
public $discussion;
|
||||
|
||||
public $user;
|
||||
|
||||
public $oldTitle;
|
||||
|
||||
public function __construct(Discussion $discussion, User $user, $oldTitle)
|
||||
{
|
||||
$this->discussion = $discussion;
|
||||
$this->user = $user;
|
||||
$this->oldTitle = $oldTitle;
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\Discussion;
|
||||
|
||||
class DiscussionWasStarted
|
||||
{
|
||||
public $discussion;
|
||||
|
||||
public function __construct(Discussion $discussion)
|
||||
{
|
||||
$this->discussion = $discussion;
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Notifications\NotificationInterface;
|
||||
|
||||
class NotificationWillBeSent
|
||||
{
|
||||
public $notification;
|
||||
|
||||
public $users;
|
||||
|
||||
public function __construct(NotificationInterface $notification, array &$users)
|
||||
{
|
||||
$this->notification = $notification;
|
||||
$this->users = $users;
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\Post;
|
||||
|
||||
class PostWasDeleted
|
||||
{
|
||||
public $post;
|
||||
|
||||
public function __construct(Post $post)
|
||||
{
|
||||
$this->post = $post;
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\Post;
|
||||
|
||||
class PostWasHidden
|
||||
{
|
||||
public $post;
|
||||
|
||||
public function __construct(Post $post)
|
||||
{
|
||||
$this->post = $post;
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\Post;
|
||||
|
||||
class PostWasPosted
|
||||
{
|
||||
public $post;
|
||||
|
||||
public function __construct(Post $post)
|
||||
{
|
||||
$this->post = $post;
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\Post;
|
||||
|
||||
class PostWasRestored
|
||||
{
|
||||
public $post;
|
||||
|
||||
public function __construct(Post $post)
|
||||
{
|
||||
$this->post = $post;
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\Post;
|
||||
|
||||
class PostWasRevised
|
||||
{
|
||||
public $post;
|
||||
|
||||
public function __construct(Post $post)
|
||||
{
|
||||
$this->post = $post;
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
class UserAvatarWasChanged
|
||||
{
|
||||
public $user;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
class UserBioWasChanged
|
||||
{
|
||||
public $user;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
class UserEmailChangeWasRequested
|
||||
{
|
||||
public $user;
|
||||
|
||||
public $email;
|
||||
|
||||
public function __construct(User $user, $email)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->email = $email;
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
class UserEmailWasChanged
|
||||
{
|
||||
public $user;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
class UserEmailWasConfirmed
|
||||
{
|
||||
public $user;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
class UserPasswordWasChanged
|
||||
{
|
||||
public $user;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Search\Users\UserSearcher;
|
||||
|
||||
class UserSearchWillBePerformed
|
||||
{
|
||||
public $searcher;
|
||||
|
||||
public $criteria;
|
||||
|
||||
public function __construct(UserSearcher $searcher, $criteria)
|
||||
{
|
||||
$this->searcher = $searcher;
|
||||
$this->criteria = $criteria;
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
class UserWasActivated
|
||||
{
|
||||
public $user;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
class UserWasDeleted
|
||||
{
|
||||
public $user;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
class UserWasRegistered
|
||||
{
|
||||
public $user;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<?php namespace Flarum\Core\Events;
|
||||
|
||||
use Flarum\Core\Models\User;
|
||||
|
||||
class UserWasRenamed
|
||||
{
|
||||
public $user;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@@ -1,19 +1,37 @@
|
||||
<?php namespace Flarum\Core\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Support\MessageBag;
|
||||
use DomainException;
|
||||
|
||||
class ValidationFailureException extends \InvalidArgumentException
|
||||
class ValidationFailureException extends DomainException
|
||||
{
|
||||
/**
|
||||
* @var MessageBag
|
||||
*/
|
||||
protected $errors;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $input = array();
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @param Exception $previous
|
||||
*/
|
||||
public function __construct($message = '', $code = 0, Exception $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
|
||||
$this->errors = new MessageBag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param MessageBag $errors
|
||||
* @return $this
|
||||
*/
|
||||
public function setErrors(MessageBag $errors)
|
||||
{
|
||||
$this->errors = $errors;
|
||||
@@ -21,11 +39,18 @@ class ValidationFailureException extends \InvalidArgumentException
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MessageBag
|
||||
*/
|
||||
public function getErrors()
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $input
|
||||
* @return $this
|
||||
*/
|
||||
public function setInput(array $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
@@ -33,6 +58,9 @@ class ValidationFailureException extends \InvalidArgumentException
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getInput()
|
||||
{
|
||||
return $this->input;
|
||||
|
@@ -1,48 +0,0 @@
|
||||
<?php namespace Flarum\Core\Formatter;
|
||||
|
||||
use Flarum\Core\Models\Post;
|
||||
use Closure;
|
||||
|
||||
abstract class FormatterAbstract
|
||||
{
|
||||
public function beforePurification($text, Post $post = null)
|
||||
{
|
||||
return $text;
|
||||
}
|
||||
|
||||
public function afterPurification($text, Post $post = null)
|
||||
{
|
||||
return $text;
|
||||
}
|
||||
|
||||
protected function ignoreTags($text, array $tags, Closure $callback)
|
||||
{
|
||||
$chunks = preg_split('/(<.+?>)/is', $text, 0, PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
$openTag = null;
|
||||
|
||||
for ($i = 0; $i < count($chunks); $i++) {
|
||||
if ($i % 2 === 0) { // even numbers are text
|
||||
// Only process this chunk if there are no unclosed $ignoreTags
|
||||
if (null === $openTag) {
|
||||
$chunks[$i] = $callback($chunks[$i]);
|
||||
}
|
||||
} else { // odd numbers are tags
|
||||
// Only process this tag if there are no unclosed $ignoreTags
|
||||
if (null === $openTag) {
|
||||
// Check whether this tag is contained in $ignoreTags and is not self-closing
|
||||
if (preg_match("`<(" . implode('|', $tags) . ").*(?<!/)>$`is", $chunks[$i], $matches)) {
|
||||
$openTag = $matches[1];
|
||||
}
|
||||
} else {
|
||||
// Otherwise, check whether this is the closing tag for $openTag.
|
||||
if (preg_match('`</\s*' . $openTag . '>`i', $chunks[$i], $matches)) {
|
||||
$openTag = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return implode($chunks);
|
||||
}
|
||||
}
|
@@ -1,10 +1,31 @@
|
||||
<?php namespace Flarum\Core\Formatter;
|
||||
|
||||
use Flarum\Core\Models\Post;
|
||||
use Flarum\Core\Model;
|
||||
|
||||
interface FormatterInterface
|
||||
{
|
||||
public function beforePurification($text, Post $post = null);
|
||||
/**
|
||||
* Configure the formatter manager before formatting takes place.
|
||||
*
|
||||
* @param FormatterManager $manager
|
||||
*/
|
||||
public function config(FormatterManager $manager);
|
||||
|
||||
public function afterPurification($text, Post $post = null);
|
||||
/**
|
||||
* Format the text before purification takes place.
|
||||
*
|
||||
* @param string $text
|
||||
* @param Model|null $model The entity that owns the text.
|
||||
* @return string
|
||||
*/
|
||||
public function formatBeforePurification($text, Model $model = null);
|
||||
|
||||
/**
|
||||
* Format the text after purification takes place.
|
||||
*
|
||||
* @param string $text
|
||||
* @param Model|null $model The entity that owns the text.
|
||||
* @return string
|
||||
*/
|
||||
public function formatAfterPurification($text, Model $model = null);
|
||||
}
|
||||
|
@@ -1,92 +1,143 @@
|
||||
<?php namespace Flarum\Core\Formatter;
|
||||
|
||||
use Flarum\Core\Model;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use HTMLPurifier;
|
||||
use HTMLPurifier_Config;
|
||||
use LogicException;
|
||||
|
||||
class FormatterManager
|
||||
{
|
||||
protected $formatters = [];
|
||||
|
||||
/**
|
||||
* The IoC container instance.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Container\Container
|
||||
* @var Container
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
public $config;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $formatters = [];
|
||||
|
||||
/**
|
||||
* @var HTMLPurifier_Config
|
||||
*/
|
||||
protected $htmlPurifierConfig;
|
||||
|
||||
/**
|
||||
* Create a new formatter manager instance.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Container\Container $container
|
||||
* @param Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
|
||||
// Studio does not yet merge autoload_files...
|
||||
// TODO: Studio does not yet merge autoload_files...
|
||||
// https://github.com/franzliedke/studio/commit/4f0f4314db4ed3e36c869a5f79b855c97bdd1be7
|
||||
require __DIR__.'/../../../vendor/ezyang/htmlpurifier/library/HTMLPurifier.composer.php';
|
||||
|
||||
$this->config = HTMLPurifier_Config::createDefault();
|
||||
$this->config->set('Core.Encoding', 'UTF-8');
|
||||
$this->config->set('Core.EscapeInvalidTags', true);
|
||||
$this->config->set('HTML.Doctype', 'HTML 4.01 Strict');
|
||||
$this->config->set('HTML.Allowed', 'p,em,strong,a[href|title],ul,ol,li,code,pre,blockquote,h1,h2,h3,h4,h5,h6,br,hr,img[src|alt]');
|
||||
$this->config->set('HTML.Nofollow', true);
|
||||
$this->htmlPurifierConfig = $this->getDefaultHtmlPurifierConfig();
|
||||
}
|
||||
|
||||
public function add($name, $formatter, $priority = 0)
|
||||
/**
|
||||
* Get the HTMLPurifier configuration object.
|
||||
*
|
||||
* @return HTMLPurifier_Config
|
||||
*/
|
||||
public function getHtmlPurifierConfig()
|
||||
{
|
||||
$this->formatters[$name] = [$formatter, $priority];
|
||||
return $this->htmlPurifierConfig;
|
||||
}
|
||||
|
||||
public function remove($name)
|
||||
/**
|
||||
* Add a new formatter.
|
||||
*
|
||||
* @param string $formatter
|
||||
*/
|
||||
public function add($formatter)
|
||||
{
|
||||
unset($this->formatters[$name]);
|
||||
$this->formatters[] = $formatter;
|
||||
}
|
||||
|
||||
protected function getFormatters()
|
||||
/**
|
||||
* Format the given text using the collected formatters.
|
||||
*
|
||||
* @param string $text
|
||||
* @param Model|null $model The entity that owns the text.
|
||||
* @return string
|
||||
*/
|
||||
public function format($text, Model $model = null)
|
||||
{
|
||||
$sorted = [];
|
||||
$formatters = $this->getFormatters();
|
||||
|
||||
foreach ($this->formatters as $array) {
|
||||
list($formatter, $priority) = $array;
|
||||
$sorted[$priority][] = $formatter;
|
||||
}
|
||||
|
||||
ksort($sorted);
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($sorted as $formatters) {
|
||||
$result = array_merge($result, $formatters);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function format($text, $post = null)
|
||||
{
|
||||
$formatters = [];
|
||||
foreach ($this->getFormatters() as $formatter) {
|
||||
$formatters[] = $this->container->make($formatter);
|
||||
foreach ($formatters as $formatter) {
|
||||
$formatter->config($this);
|
||||
}
|
||||
|
||||
foreach ($formatters as $formatter) {
|
||||
$text = $formatter->beforePurification($text, $post);
|
||||
$text = $formatter->formatBeforePurification($text, $model);
|
||||
}
|
||||
|
||||
$purifier = new HTMLPurifier($this->config);
|
||||
|
||||
$text = $purifier->purify($text);
|
||||
$text = $this->purify($text);
|
||||
|
||||
foreach ($formatters as $formatter) {
|
||||
$text = $formatter->afterPurification($text, $post);
|
||||
$text = $formatter->formatAfterPurification($text, $model);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate the collected formatters.
|
||||
*
|
||||
* @return FormatterInterface[]
|
||||
*/
|
||||
protected function getFormatters()
|
||||
{
|
||||
$formatters = [];
|
||||
|
||||
foreach ($this->formatters as $formatter) {
|
||||
$formatter = $this->container->make($formatter);
|
||||
|
||||
if (! $formatter instanceof FormatterInterface) {
|
||||
throw new LogicException('Formatter ' . get_class($formatter)
|
||||
. ' does not implement ' . FormatterInterface::class);
|
||||
}
|
||||
|
||||
$formatters[] = $formatter;
|
||||
}
|
||||
|
||||
return $formatters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Purify the given text, making sure it is safe to be displayed in web
|
||||
* browsers.
|
||||
*
|
||||
* @param string $text
|
||||
* @return string
|
||||
*/
|
||||
protected function purify($text)
|
||||
{
|
||||
$purifier = new HTMLPurifier($this->htmlPurifierConfig);
|
||||
|
||||
return $purifier->purify($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default HTMLPurifier config settings.
|
||||
*
|
||||
* @return HTMLPurifier_Config
|
||||
*/
|
||||
protected function getDefaultHtmlPurifierConfig()
|
||||
{
|
||||
$config = HTMLPurifier_Config::createDefault();
|
||||
$config->set('Core.Encoding', 'UTF-8');
|
||||
$config->set('Core.EscapeInvalidTags', true);
|
||||
$config->set('HTML.Doctype', 'HTML 4.01 Strict');
|
||||
$config->set('HTML.Allowed', 'p,em,strong,a[href|title],ul,ol,li,code,pre,blockquote,h1,h2,h3,h4,h5,h6,br,hr,img[src|alt]');
|
||||
$config->set('HTML.Nofollow', true);
|
||||
|
||||
return $config;
|
||||
}
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ class FormatterServiceProvider extends ServiceProvider
|
||||
public function boot()
|
||||
{
|
||||
$this->extend([
|
||||
new Extend\Formatter('linkify', 'Flarum\Core\Formatter\LinkifyFormatter')
|
||||
new Extend\PostFormatter('Flarum\Core\Formatter\LinkifyFormatter')
|
||||
]);
|
||||
}
|
||||
|
||||
|
@@ -1,18 +1,27 @@
|
||||
<?php namespace Flarum\Core\Formatter;
|
||||
|
||||
use Flarum\Core\Models\Post;
|
||||
use Flarum\Core\Model;
|
||||
use Misd\Linkify\Linkify;
|
||||
|
||||
class LinkifyFormatter extends FormatterAbstract
|
||||
class LinkifyFormatter extends TextFormatter
|
||||
{
|
||||
/**
|
||||
* @var Linkify
|
||||
*/
|
||||
protected $linkify;
|
||||
|
||||
/**
|
||||
* @param Linkify $linkify
|
||||
*/
|
||||
public function __construct(Linkify $linkify)
|
||||
{
|
||||
$this->linkify = $linkify;
|
||||
}
|
||||
|
||||
public function beforePurification($text, Post $post = null)
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function formatTextBeforePurification($text, Model $post = null)
|
||||
{
|
||||
return $this->linkify->process($text, ['attr' => ['target' => '_blank']]);
|
||||
}
|
||||
|
120
src/Core/Formatter/TextFormatter.php
Normal file
120
src/Core/Formatter/TextFormatter.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php namespace Flarum\Core\Formatter;
|
||||
|
||||
use Flarum\Core\Model;
|
||||
|
||||
/**
|
||||
* A formatter which formats a block of HTML, while leaving the contents
|
||||
* of specific tags like <code> and <pre> untouched.
|
||||
*/
|
||||
abstract class TextFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* A list of tags to ignore when applying formatting.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $ignoreTags = ['code', 'pre'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function config(FormatterManager $manager)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatBeforePurification($text, Model $model = null)
|
||||
{
|
||||
return $this->formatAroundIgnoredTags($text, function ($text) use ($model) {
|
||||
return $this->formatTextBeforePurification($text, $model);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatAfterPurification($text, Model $model = null)
|
||||
{
|
||||
return $this->formatAroundIgnoredTags($text, function ($text) use ($model) {
|
||||
return $this->formatTextAfterPurification($text, $model);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format non-ignored text before purification has taken place.
|
||||
*
|
||||
* @param string $text
|
||||
* @param Model $model
|
||||
* @return mixed
|
||||
*/
|
||||
protected function formatTextBeforePurification($text, Model $model = null)
|
||||
{
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format non-ignored text after purification has taken place.
|
||||
*
|
||||
* @param string $text
|
||||
* @param Model $model
|
||||
* @return string
|
||||
*/
|
||||
protected function formatTextAfterPurification($text, Model $model = null)
|
||||
{
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a callback on parts of the provided text that aren't within the list
|
||||
* of ignored tags.
|
||||
*
|
||||
* @param string $text
|
||||
* @param callable $callback
|
||||
* @return string
|
||||
*/
|
||||
protected function formatAroundIgnoredTags($text, callable $callback)
|
||||
{
|
||||
return $this->formatAroundTags($text, $this->ignoreTags, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a callback on parts of the provided text that aren't within the
|
||||
* given list of tags.
|
||||
*
|
||||
* @param string $text
|
||||
* @param array $tags
|
||||
* @param callable $callback
|
||||
* @return string
|
||||
*/
|
||||
protected function formatAroundTags($text, array $tags, callable $callback)
|
||||
{
|
||||
$chunks = preg_split('/(<.+?>)/is', $text, 0, PREG_SPLIT_DELIM_CAPTURE);
|
||||
$openTag = null;
|
||||
|
||||
for ($i = 0; $i < count($chunks); $i++) {
|
||||
if ($i % 2 === 0) { // even numbers are text
|
||||
// Only process this chunk if there are no unclosed $ignoreTags
|
||||
if (null === $openTag) {
|
||||
$chunks[$i] = $callback($chunks[$i]);
|
||||
}
|
||||
} else { // odd numbers are tags
|
||||
// Only process this tag if there are no unclosed $ignoreTags
|
||||
if (null === $openTag) {
|
||||
// Check whether this tag is contained in $ignoreTags and is not self-closing
|
||||
if (preg_match("`<(" . implode('|', $tags) . ").*(?<!/)>$`is", $chunks[$i], $matches)) {
|
||||
$openTag = $matches[1];
|
||||
}
|
||||
} else {
|
||||
// Otherwise, check whether this is the closing tag for $openTag.
|
||||
if (preg_match('`</\s*' . $openTag . '>`i', $chunks[$i], $matches)) {
|
||||
$openTag = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return implode($chunks);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user