diff --git a/src/Api/Controllers/SearchController.php b/src/Api/Controllers/SearchController.php index e37a975..2a7c2d2 100644 --- a/src/Api/Controllers/SearchController.php +++ b/src/Api/Controllers/SearchController.php @@ -2,33 +2,39 @@ namespace Blomstra\Search\Api\Controllers; +use Blomstra\Search\Save\Document as ElasticDocument; use Blomstra\Search\Elasticsearch\TermsQuery; -use Blomstra\Search\Schemas\Schema; use Elasticsearch\Client; -use Flarum\Api\Controller\AbstractListController; use Flarum\Api\Controller\ListDiscussionsController; -use Flarum\Discussion\Filter\DiscussionFilterer; -use Flarum\Discussion\Search\DiscussionSearcher; +use Flarum\Api\Serializer\DiscussionSerializer; +use Flarum\Discussion\Discussion; use Flarum\Extension\ExtensionManager; use Flarum\Group\Group; use Flarum\Http\RequestUtil; -use Flarum\Http\UrlGenerator; use Flarum\User\User; use Illuminate\Contracts\Container\Container; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Arr; -use Illuminate\Support\Collection; +use Illuminate\Support\Str; use Psr\Http\Message\ServerRequestInterface; use Spatie\ElasticsearchQueryBuilder\Aggregations\TermsAggregation; use Spatie\ElasticsearchQueryBuilder\Aggregations\TopHitsAggregation; use Spatie\ElasticsearchQueryBuilder\Builder; use Spatie\ElasticsearchQueryBuilder\Queries\BoolQuery; -use Spatie\ElasticsearchQueryBuilder\Queries\MultiMatchQuery; +use Spatie\ElasticsearchQueryBuilder\Queries\MatchQuery; use Spatie\ElasticsearchQueryBuilder\Queries\TermQuery; use Spatie\ElasticsearchQueryBuilder\Sorts\Sort; use Tobscure\JsonApi\Document; class SearchController extends ListDiscussionsController { + public $serializer = DiscussionSerializer::class; + + protected array $translateSort = [ + 'lastPostedAt' => 'updated_at', + 'createdAt' => 'created_at' + ]; + public function __construct() { } @@ -44,17 +50,13 @@ class SearchController extends ListDiscussionsController $filters = $this->extractFilter($request); - $schema = $this->getSchema($type); - - $this->serializer = $schema::serializer(); + $include = array_merge($this->extractInclude($request), ['state']); $filterQuery = (BoolQuery::create()) ->add( BoolQuery::create() - ->add(MultiMatchQuery::create($filters['q'], array_keys($schema->fulltext(new ($schema::model()))))) - ->add(TermQuery::create('type', $type)) + ->add(MatchQuery::create('content', $filters['q'])) ); - $builder = (new Builder($client)) ->index(resolve('blomstra.search.elastic_index')) ->size($this->extractLimit($request)) @@ -62,26 +64,78 @@ class SearchController extends ListDiscussionsController ->addQuery( $this->addFilters($filterQuery, $actor) ) - ->addAggregation( - TermsAggregation::create('discussions', 'discussion_id') - ->aggregation(TopHitsAggregation::create('hits', 1)) - ); +// ->addAggregation( +// TermsAggregation::create('discussions', 'discussion_id') +// ->aggregation(TopHitsAggregation::create('hits', 1)) +// ) + ; foreach ($this->extractSort($request) as $field => $direction) { + $field = $this->translateSort[$field] ?? $field; $builder->addSort(new Sort($field, $direction)); } $result = $builder->search(); - return $schema::results(Arr::get($result, 'hits.hits')); + Discussion::setStateUser($actor); + + // Eager load groups for use in the policies (isAdmin check) + if (in_array('mostRelevantPost.user', $include)) { + $include[] = 'mostRelevantPost.user.groups'; + + // If the first level of the relationship wasn't explicitly included, + // add it so the code below can look for it + if (! in_array('mostRelevantPost', $include)) { + $include[] = 'mostRelevantPost'; + } + } + + $results = Collection::make(Arr::get($result, 'hits.hits')) + ->map(function ($hit) { + $id = Str::after($hit['_source']['id'], ':'); + $type = $hit['_source']['type']; + + if ($type === 'posts') { + /** @var Discussion $discussion */ + $discussion = Discussion::whereHas('posts', function ($query) use ($id) { + $query->where('id', $id); + })->first(); + + $discussion->most_relevant_post_id = $id; + } + if ($type === 'discussions') { + /** @var Discussion $discussion */ + $discussion = Discussion::find($id); + + $discussion->most_relevant_post_id = $discussion->first_post_id; + } + + return $discussion; + }) + ->keyBy('id') + ->unique(); + + $this->loadRelations($results, $include); + + if ($relations = array_intersect($include, ['firstPost', 'lastPost', 'mostRelevantPost'])) { + foreach ($results as $discussion) { + foreach ($relations as $relation) { + if ($discussion->$relation) { + $discussion->$relation->discussion = $discussion; + } + } + } + } + + return $results; } - protected function getSchema(string $type): ?Schema + protected function getDocument(string $type): ?ElasticDocument { - $mapping = resolve(Container::class)->tagged('blomstra.search.schemas'); + $documents = resolve(Container::class)->tagged('blomstra.search.documents'); - return collect($mapping)->first(function (Schema $schema) use ($type) { - return $schema::type() === $type; + return collect($documents)->first(function (ElasticDocument $document) use ($type) { + return $document->type() === $type; }); } @@ -103,12 +157,12 @@ class SearchController extends ListDiscussionsController if ($actor->is_email_confirmed) $groups->add(Group::MEMBER_ID); $subQuery = BoolQuery::create() - ->add(TermQuery::create('private', 'false')) + ->add(TermQuery::create('is_private', 'false')) ->add(TermsQuery::create('groups', $groups->toArray())); if ($this->extensionEnabled('fof-byobu') && $actor->exists) { $byobuQuery = BoolQuery::create() - ->add(TermQuery::create('private', 'true'), 'should') + ->add(TermQuery::create('is_private', 'true'), 'should') ->add( BoolQuery::create() ->add(TermsQuery::create('recipient-groups', $groups->toArray())) diff --git a/src/Commands/RebuildDocumentsCommand.php b/src/Commands/RebuildDocumentsCommand.php index 87939ac..fc72d71 100644 --- a/src/Commands/RebuildDocumentsCommand.php +++ b/src/Commands/RebuildDocumentsCommand.php @@ -12,7 +12,10 @@ use Illuminate\Database\Eloquent\Collection; class RebuildDocumentsCommand extends Command { - protected $signature = 'blomstra:search:documents:rebuild {--flush : Flushes ALL the documents inside the index}'; + protected $signature = 'blomstra:search:documents:rebuild + {--flush : Flushes ALL the documents inside the index} + {--mapping : Create property mappings} + {--max-id= : Limits for each object the number of items to seed}'; protected $description = 'Rebuilds the complete search server with its documents.'; public function handle(Container $container) @@ -27,20 +30,47 @@ class RebuildDocumentsCommand extends Command $client = $container->make('blomstra.search.elastic'); // Flush the index. - if ($this->option('flush')) $client->indices()->delete([ - 'index' => $container->make('blomstra.search.elastic_index') - ]); + if ($this->option('flush')) { + $client->indices()->delete([ + 'index' => $container->make('blomstra.search.elastic_index'), + 'ignore_unavailable' => true + ]); + $client->indices()->create([ + 'index' => $container->make('blomstra.search.elastic_index') + ]); + } + + if ($this->option('mapping')) { + $client->indices()->putMapping([ + 'index' => $container->make('blomstra.search.elastic_index'), + 'body' => [ + 'properties' => [ + 'content' => ['type' => 'text'], + 'created_at' => ['type' => 'date'], + 'updated_at' => ['type' => 'date'], + 'is_private' => ['type' => 'boolean'], + 'is_sticky' => ['type' => 'boolean'], + 'groups' => ['type' => 'integer'], + 'recipient_groups' => ['type' => 'integer'], + 'recipient_users' => ['type' => 'integer'], + ] + ] + ]); + } /** @var Seeder $seeder */ foreach ($seeders as $seeder) { - $seeder->query()->chunk(50, function (Collection $collection) use ($queue, &$total) { + $seeder->query() + ->when($this->option('max-id'), function ($query, $id) { + $query->where('id', '<=', $id); + }) + ->chunk(50, function (Collection $collection) use ($queue, &$total, $seeder) { + $queue->push(new SavingJob($collection, $seeder)); - $queue->push(new SavingJob($collection)); + $this->info("Pushed into the index, type: {$seeder->type()}, amount: {$collection->count()}."); - $this->info("Pushed {$collection->count()} into the index."); - - $total += $collection->count(); - }); + $total += $collection->count(); + }); $this->info("Pushed a total of $total into the index."); } diff --git a/src/Documents/CommentDocument.php b/src/Documents/CommentDocument.php deleted file mode 100644 index 30b4dfc..0000000 --- a/src/Documents/CommentDocument.php +++ /dev/null @@ -1,49 +0,0 @@ - $this->model->content, - ]; - } - - public function attributes(): array - { - $attributes = [ - 'author' => $this->model->user_id, - 'createdAt' => $this->model->created_at->toAtomString(), - 'is_private' => $this->model->is_private, - 'discussion_id' => $this->model->discussion?->id - ]; - - if ($this->extensionEnabled('flarum-flags')) { - $attributes['flags_count'] = $this->model->flags->count(); - } - - if ($this->extensionEnabled('flarum-approval')) { - $attributes['approved'] = $this->model->is_approved; - } - - return $attributes; - } - - public function serializer(): string - { - return PostSerializer::class; - } - - public function model(): string - { - return CommentPost::class; - } -} diff --git a/src/Documents/DiscussionDocument.php b/src/Documents/DiscussionDocument.php deleted file mode 100644 index 1c1943e..0000000 --- a/src/Documents/DiscussionDocument.php +++ /dev/null @@ -1,54 +0,0 @@ - $this->model->title - ]; - } - - public function attributes(): array - { - $attributes = [ - 'author' => $this->model->user_id, - 'createdAt' => $this->model->created_at->toAtomString(), - 'lastPostedAt' => $this->model->last_posted_at?->toAtomString(), - 'is_private' => $this->model->is_private, - 'first_post_id' => $this->model->first_post_id, - 'last_post_id' => $this->model->last_post_id, - 'commentCount' => $this->model?->comment_count, - 'groups' => $this->groupsForDiscussion($this->model), - ]; - - if ($this->extensionEnabled('fof-byobu')) { - $attributes['recipient-users'] = $this->model->recipientUsers->pluck('id')->toArray(); - $attributes['recipient-groups'] = $this->model->recipientGroups->pluck('id')->toArray(); - } - - if ($this->extensionEnabled('flarum-sticky')) { - $attributes['is_sticky'] = $this->model->is_sticky; - } - - return $attributes; - } - - public function serializer(): string - { - return DiscussionSerializer::class; - } - - public function model(): string - { - return Discussion::class; - } -} diff --git a/src/Documents/Document.php b/src/Documents/Document.php deleted file mode 100644 index e05df4a..0000000 --- a/src/Documents/Document.php +++ /dev/null @@ -1,82 +0,0 @@ -type() . ':' . $this->model->getKey(); - } - - public function index(): string - { - return resolve('blomstra.search.elastic_index'); - } - - public function type(): string - { - return resolve($this->serializer())->getType($this->model); - } - - abstract public function serializer(): string; - - abstract public function model(): string; - - protected function groupsForDiscussion(Discussion $discussion): array - { - $permissions = collect(); - - $globalPermission = Permission::query() - ->where('permission', 'viewForum') - ->pluck('group_id'); - - if ($this->extensionEnabled('flarum-tags')) { - /** @var Collection $tags */ - $tags = $discussion->tags; - - $filters['tags'] = $tags->pluck('id')->toArray(); - $tagPermissions = Permission::query() - ->whereIn( - 'permission', - $tags->pluck('id')->map(function (int $id) { - return "tag$id.viewForum"; - }) - )->get(); - - $permissions = $tags->map(function (Tag $tag) use ($tagPermissions) { - $permissions = $tagPermissions->where('permission', "tag$tag->id.viewForum"); - - if ($tag->is_restricted) { - $permissions = $permissions->add(['group_id' => Group::ADMINISTRATOR_ID]); - } - - return $permissions->pluck('group_id'); - })->flatten(); - } - - if (! $discussion->is_private && $permissions->isEmpty()) { - $permissions = $globalPermission; - } - - return $permissions->toArray(); - } - - protected function extensionEnabled(string $extension): bool - { - /** @var ExtensionManager $manager */ - $manager = resolve(ExtensionManager::class); - - return $manager->isEnabled($extension); - } -} diff --git a/src/Observe/Job.php b/src/Observe/Job.php index bd02f3d..e05df75 100644 --- a/src/Observe/Job.php +++ b/src/Observe/Job.php @@ -2,26 +2,16 @@ namespace Blomstra\Search\Observe; -use Blomstra\Search\Documents\Document; +use Blomstra\Search\Seeders\Seeder; use Flarum\Queue\AbstractJob; -use Illuminate\Contracts\Container\Container; -use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; abstract class Job extends AbstractJob { - public function __construct(protected Collection $models) - {} + protected string $index; - protected function getDocument(): ?Document + public function __construct(protected Collection $models, protected Seeder $seeder) { - $documents = resolve(Container::class)->tagged('blomstra.search.documents'); - - /** @var Model $model */ - $model = $this->models->first(); - - return collect($documents)->first(function (Document $document) use ($model) { - return $document->model() === get_class($model); - }); + $this->index = resolve('blomstra.search.elastic_index'); } } diff --git a/src/Observe/SavingJob.php b/src/Observe/SavingJob.php index 6df3c1c..b6febed 100644 --- a/src/Observe/SavingJob.php +++ b/src/Observe/SavingJob.php @@ -2,7 +2,6 @@ namespace Blomstra\Search\Observe; -use Blomstra\Search\Documents\Document; use Elasticsearch\Client; use Illuminate\Contracts\Container\Container; use Illuminate\Database\Eloquent\Model; @@ -16,62 +15,19 @@ class SavingJob extends Job /** @var Client $client */ $client = $container->make('blomstra.search.elastic'); - $document = $this->getDocument(); - - if (! $document) return; - - if($first = $this->models->first()) { -// $properties = []; -// -// foreach (array_keys($schema->fulltext($first)) as $key) { -// $properties[$key] = ['type' => 'text']; -// } - - // Set up the index -// if (! $client->indices()->exists([ -// 'index' => $schema::index(), -// 'expand_wildcards' => 'none' -// ])) { -// $client->indices()->create([ -// 'index' => $schema::index(), -// 'body' => [ -// 'mappings' => [ -// 'properties' => $properties -// ], -// 'settings' => [ -// 'index' => [ -// 'query' => [ -// 'default_field' => array_keys($schema->fulltext($first)) -// ] -// ] -// ] -// ] -// ]); -// } - } - // Preparing body for storing. - $body = $this->models->map(function (Model $model) use ($document) { - /** @var Document $markup */ - $markup = new $document($model); + $body = $this->models->map(function (Model $model) { + $document = $this->seeder->toDocument($model); return [ - [ - 'index' => [ - '_id' => $markup->id(), - '_index' => resolve('blomstra.search.elastic_index') - ] - ], - array_merge( - ['type' => $markup->type()], - $markup->fulltext(), - $markup->attributes() - ) + ['index' => ['_index' => $this->index, '_id' => $document->id]], + $document->toArray() ]; - })->flatten(1); + }) + ->flatten(1); $response = $client->bulk([ - 'index' => resolve('blomstra.search.elastic_index'), + 'index' => $this->index, 'body' => $body->toArray(), 'refresh' => true ]); diff --git a/src/Provider.php b/src/Provider.php index b1899e8..a402711 100644 --- a/src/Provider.php +++ b/src/Provider.php @@ -2,10 +2,8 @@ namespace Blomstra\Search; -use Blomstra\Search\Documents; use Blomstra\Search\Observe\DeletingJob; use Blomstra\Search\Observe\SavingJob; -use Blomstra\Search\Schemas\Schema; use Blomstra\Search\Seeders; use Elasticsearch\ClientBuilder; use Flarum\Foundation\AbstractServiceProvider; @@ -21,13 +19,8 @@ class Provider extends AbstractServiceProvider public function register() { $this->container->tag([ - Documents\CommentDocument::class, - Documents\DiscussionDocument::class - ], 'blomstra.search.documents'); - - $this->container->tag([ + Seeders\DiscussionSeeder::class, Seeders\CommentSeeder::class, - Seeders\DiscussionSeeder::class ], 'blomstra.search.seeders'); $config = $this->container->make('flarum.config') ?? []; @@ -64,12 +57,12 @@ class Provider extends AbstractServiceProvider /** @var string|Seeders\Seeder $seeder */ foreach ($seeders as $seeder) { - $seeder::savingOn($events, function ($model) use ($queue) { - $queue->push(new SavingJob(Collection::make([$model]))); + $seeder::savingOn($events, function ($model) use ($queue, $seeder) { + $queue->push(new SavingJob(Collection::make([$model]), $seeder)); }); - $seeder::deletingOn($events, function ($model) use ($queue) { - $queue->push(new DeletingJob(Collection::make([$model]))); + $seeder::deletingOn($events, function ($model) use ($queue, $seeder) { + $queue->push(new DeletingJob(Collection::make([$model]), $seeder)); }); } } diff --git a/src/Save/Document.php b/src/Save/Document.php new file mode 100644 index 0000000..ba4d7af --- /dev/null +++ b/src/Save/Document.php @@ -0,0 +1,23 @@ + CommentPost::$type, - 'author' => $post->user_id, - 'createdAt' => $post->created_at->toAtomString(), - 'private' => $post->is_private, - 'discussion_id' => $post->discussion?->id - ]; - - if ($this->extensionEnabled('flarum-flags')) { - $filters['flags_count'] = $post->flags->count(); - } - - if ($this->extensionEnabled('flarum-approval')) { - $filters['approved'] = $post->is_approved; - } - - return $filters; - } - - public function fulltext(CommentPost $post): array - { - return [ - 'content' => $post->content, - ]; - } - - public static function model(): string - { - return CommentPost::class; - } - - public static function query(): Builder - { - return CommentPost::query() - ->where('type', CommentPost::$type) - ->with('discussion'); - } - - public static function results(array $hits): \Illuminate\Database\Eloquent\Collection - { - $postIds = Collection::make($hits)->keyBy('_source.discussion_id')->pluck('_id'); - $discussionIds = Collection::make($hits)->pluck('_source.discussion_id'); - - return Discussion::query()->findMany($discussionIds)->map(function (Discussion $discussion) use ($postIds) { - $discussion->most_relevant_post_id = $postIds->get($discussion->id); - - return $discussion; - })->load('mostRelevantPost'); - } - - public static function serializer(): string - { - return DiscussionSerializer::class; - } - - public static function savingOn(Dispatcher $events, callable $callable) - { - $events->listen(Posted::class, function (Posted $event) use ($callable) { - $callable($event->post); - }); - } - - public static function deletingOn(Dispatcher $events, callable $callable) - { - $events->listen(Deleted::class, function (Deleted $event) use ($callable) { - $callable($event->post); - }); - } - - public static function type(): string - { - return CommentPost::$type; - } -} diff --git a/src/Schemas/DiscussionSchema.php b/src/Schemas/DiscussionSchema.php deleted file mode 100644 index b5ac311..0000000 --- a/src/Schemas/DiscussionSchema.php +++ /dev/null @@ -1,104 +0,0 @@ - 'discussions', - 'author' => $discussion->user_id, - 'createdAt' => $discussion->created_at->toAtomString(), - 'lastPostedAt' => $discussion->last_posted_at->toAtomString(), - 'private' => $discussion->is_private, - 'first_post_id' => $discussion->first_post_id, - 'last_post_id' => $discussion->last_post_id, - 'commentCount' => $discussion?->comment_count, - 'groups' => $this->groupsForDiscussion($discussion), - ]; - - if ($this->extensionEnabled('fof-byobu')) { - $filters['recipient-users'] = $discussion->recipientUsers->pluck('id')->toArray(); - $filters['recipient-groups'] = $discussion->recipientGroups->pluck('id')->toArray(); - } - - if ($this->extensionEnabled('flarum-sticky')) { - $filters['is_sticky'] = $discussion->is_sticky; - } - - return $filters; - } - - public static function relations() - { - return [ - 'discussion_id' => [ - 'type' => 'discussion' - ] - ]; - } - - public function fulltext(Discussion $discussion): array - { - return [ - 'title' => $discussion->title - ]; - } - - public static function model(): string - { - return Discussion::class; - } - - public static function serializer(): string - { - return DiscussionSerializer::class; - } - - public static function savingOn(Dispatcher $events, callable $callable) - { - $events->listen([Started::class, Restored::class], function ($event) use ($callable) { - return $callable($event->discussion); - }); - } - - public static function deletingOn(Dispatcher $events, callable $callable) - { - $events->listen([Deleted::class, Hidden::class], function ($event) use ($callable) { - return $callable($event->discussion); - }); - } - - public static function query(): Builder - { - return Discussion::query(); - } - - public static function results(array $hits): Collection - { - $postIds = \Illuminate\Support\Collection::make($hits)->keyBy('_source.discussion_id')->pluck('_id'); - $discussionIds = Collection::make($hits)->pluck('_source.discussion_id'); - - return Discussion::query()->findMany($discussionIds)->map(function (Discussion $discussion) use ($postIds) { - $discussion->most_relevant_post_id = $postIds->get($discussion->id); - - return $discussion; - })->load('mostRelevantPost'); - } - - public static function type(): string - { - return 'discussion'; - } -} diff --git a/src/Schemas/Schema.php b/src/Schemas/Schema.php deleted file mode 100644 index d1844e3..0000000 --- a/src/Schemas/Schema.php +++ /dev/null @@ -1,73 +0,0 @@ -isEnabled($extension); - } - - abstract public static function type(): string; - - abstract public static function model(): string; - abstract public static function query(): Builder; - abstract public static function results(array $hits): Collection; - - abstract public static function serializer(): string; - - abstract public static function savingOn(Dispatcher $events, callable $callable); - abstract public static function deletingOn(Dispatcher $events, callable $callable); - - protected function groupsForDiscussion(Discussion $discussion): array - { - $permissions = collect(); - - $globalPermission = Permission::query() - ->where('permission', 'viewForum') - ->pluck('group_id'); - - if ($this->extensionEnabled('flarum-tags')) { - /** @var Collection $tags */ - $tags = $discussion->tags; - - $filters['tags'] = $tags->pluck('id')->toArray(); - $tagPermissions = Permission::query() - ->whereIn( - 'permission', - $tags->pluck('id')->map(function (int $id) { - return "tag$id.viewForum"; - }) - )->get(); - - $permissions = $tags->map(function (Tag $tag) use ($tagPermissions) { - $permissions = $tagPermissions->where('permission', "tag$tag->id.viewForum"); - - if ($tag->is_restricted) { - $permissions = $permissions->add(['group_id' => Group::ADMINISTRATOR_ID]); - } - - return $permissions->pluck('group_id'); - })->flatten(); - } - - if (! $discussion->is_private && $permissions->isEmpty()) { - $permissions = $globalPermission; - } - - return $permissions->toArray(); - } -} diff --git a/src/Seeders/CommentSeeder.php b/src/Seeders/CommentSeeder.php index 5d1ed02..85a21d6 100644 --- a/src/Seeders/CommentSeeder.php +++ b/src/Seeders/CommentSeeder.php @@ -2,18 +2,20 @@ namespace Blomstra\Search\Seeders; +use Blomstra\Search\Save\Document; use Flarum\Api\Serializer\PostSerializer; use Flarum\Post\CommentPost; use Flarum\Post\Event\Deleted; use Flarum\Post\Event\Posted; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; class CommentSeeder extends Seeder { public function type(): string { - return resolve(PostSerializer::class)->type; + return resolve(PostSerializer::class)->getType(new CommentPost); } public function query(): Builder @@ -36,4 +38,29 @@ class CommentSeeder extends Seeder $callable($event->post); }); } + + /** + * @param CommentPost $model + * @return Document + */ + public function toDocument(Model $model): Document + { + $document = new Document([ + 'type' => $this->type(), + 'id' => $this->type() . ':' . $model->id, + 'content' => $model->content, + 'created_at' => $model->created_at?->toAtomString(), + 'updated_at' => $model->edited_at?->toAtomString(), + 'is_private' => $model->is_private, + 'user_id' => $model->user_id, + 'groups' => $this->groupsForDiscussion($model->discussion) + ]); + + if ($this->extensionEnabled('fof-byobu')) { + $document['recipient_users'] = $model->discussion->recipientUsers->pluck('id')->toArray(); + $document['recipient_groups'] = $model->discussion->recipientGroups->pluck('id')->toArray(); + } + + return $document; + } } diff --git a/src/Seeders/DiscussionSeeder.php b/src/Seeders/DiscussionSeeder.php index bdedbb1..4529c8e 100644 --- a/src/Seeders/DiscussionSeeder.php +++ b/src/Seeders/DiscussionSeeder.php @@ -2,6 +2,7 @@ namespace Blomstra\Search\Seeders; +use Blomstra\Search\Save\Document; use Flarum\Api\Serializer\DiscussionSerializer; use Flarum\Discussion\Discussion; use Flarum\Discussion\Event\Deleted; @@ -10,12 +11,13 @@ use Flarum\Discussion\Event\Restored; use Flarum\Discussion\Event\Started; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; class DiscussionSeeder extends Seeder { public function type(): string { - return resolve(DiscussionSerializer::class)->type; + return resolve(DiscussionSerializer::class)->getType(new Discussion); } public function query(): Builder @@ -36,4 +38,33 @@ class DiscussionSeeder extends Seeder return $callable($event->discussion); }); } + + /** + * @param Discussion $model + * @return Document + */ + public function toDocument(Model $model): Document + { + $document = new Document([ + 'type' => $this->type(), + 'id' => $this->type() . ':' . $model->id, + 'content' => $model->title, + 'created_at' => $model->created_at?->toAtomString(), + 'updated_at' => $model->last_posted_at?->toAtomString(), + 'is_private' => $model->is_private, + 'user_id' => $model->user_id, + 'groups' => $this->groupsForDiscussion($model) + ]); + + if ($this->extensionEnabled('fof-byobu')) { + $document['recipient_users'] = $model->recipientUsers->pluck('id')->toArray(); + $document['recipient_groups'] = $model->recipientGroups->pluck('id')->toArray(); + } + + if ($this->extensionEnabled('flarum-sticky')) { + $document['is_sticky'] = $model->is_sticky; + } + + return $document; + } } diff --git a/src/Seeders/Seeder.php b/src/Seeders/Seeder.php index f790ddf..cdbc84b 100644 --- a/src/Seeders/Seeder.php +++ b/src/Seeders/Seeder.php @@ -2,8 +2,16 @@ namespace Blomstra\Search\Seeders; +use Blomstra\Search\Save\Document; +use Flarum\Discussion\Discussion; +use Flarum\Extension\ExtensionManager; +use Flarum\Group\Group; +use Flarum\Group\Permission; +use Flarum\Tags\Tag; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Collection; +use Illuminate\Database\Eloquent\Model; abstract class Seeder { @@ -13,4 +21,53 @@ abstract class Seeder abstract public static function savingOn(Dispatcher $events, callable $callable); abstract public static function deletingOn(Dispatcher $events, callable $callable); + + abstract public function toDocument(Model $model): Document; + + protected function groupsForDiscussion(Discussion $discussion): array + { + $permissions = collect(); + + $globalPermission = Permission::query() + ->where('permission', 'viewForum') + ->pluck('group_id'); + + if ($this->extensionEnabled('flarum-tags')) { + /** @var Collection $tags */ + $tags = $discussion->tags; + + $filters['tags'] = $tags->pluck('id')->toArray(); + $tagPermissions = Permission::query() + ->whereIn( + 'permission', + $tags->pluck('id')->map(function (int $id) { + return "tag$id.viewForum"; + }) + )->get(); + + $permissions = $tags->map(function (Tag $tag) use ($tagPermissions) { + $permissions = $tagPermissions->where('permission', "tag$tag->id.viewForum"); + + if ($tag->is_restricted) { + $permissions = $permissions->add(['group_id' => Group::ADMINISTRATOR_ID]); + } + + return $permissions->pluck('group_id'); + })->flatten(); + } + + if (! $discussion->is_private && $permissions->isEmpty()) { + $permissions = $globalPermission; + } + + return $permissions->toArray(); + } + + protected function extensionEnabled(string $extension): bool + { + /** @var ExtensionManager $manager */ + $manager = resolve(ExtensionManager::class); + + return $manager->isEnabled($extension); + } }