mirror of
https://github.com/flarum/core.git
synced 2025-10-18 10:16:09 +02:00
Extract new Flarum\Discussion namespace
This commit is contained in:
53
src/Discussion/Search/DiscussionSearch.php
Normal file
53
src/Discussion/Search/DiscussionSearch.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Search;
|
||||
|
||||
use Flarum\Core\Search\AbstractSearch;
|
||||
|
||||
/**
|
||||
* An object which represents the internal state of a search for discussions:
|
||||
* the search query, the user performing the search, the fallback sort order,
|
||||
* relevant post information, and a log of which gambits have been used.
|
||||
*/
|
||||
class DiscussionSearch extends AbstractSearch
|
||||
{
|
||||
/**
|
||||
* {@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 the results.
|
||||
*
|
||||
* @param array $relevantPostIds
|
||||
* @return void
|
||||
*/
|
||||
public function setRelevantPostIds(array $relevantPostIds)
|
||||
{
|
||||
$this->relevantPostIds = $relevantPostIds;
|
||||
}
|
||||
}
|
138
src/Discussion/Search/DiscussionSearcher.php
Normal file
138
src/Discussion/Search/DiscussionSearcher.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Search;
|
||||
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Discussion\DiscussionRepository;
|
||||
use Flarum\Discussion\Search\DiscussionSearch;
|
||||
use Flarum\Post\PostRepository;
|
||||
use Flarum\Core\Search\ApplySearchParametersTrait;
|
||||
use Flarum\Core\Search\GambitManager;
|
||||
use Flarum\Core\Search\SearchCriteria;
|
||||
use Flarum\Core\Search\SearchResults;
|
||||
use Flarum\Event\ConfigureDiscussionSearch;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
/**
|
||||
* Takes a DiscussionSearchCriteria object, performs a search using gambits,
|
||||
* and spits out a DiscussionSearchResults object.
|
||||
*/
|
||||
class DiscussionSearcher
|
||||
{
|
||||
use ApplySearchParametersTrait;
|
||||
|
||||
/**
|
||||
* @var GambitManager
|
||||
*/
|
||||
protected $gambits;
|
||||
|
||||
/**
|
||||
* @var DiscussionRepository
|
||||
*/
|
||||
protected $discussions;
|
||||
|
||||
/**
|
||||
* @var PostRepository
|
||||
*/
|
||||
protected $posts;
|
||||
|
||||
/**
|
||||
* @param GambitManager $gambits
|
||||
* @param DiscussionRepository $discussions
|
||||
* @param PostRepository $posts
|
||||
*/
|
||||
public function __construct(
|
||||
GambitManager $gambits,
|
||||
DiscussionRepository $discussions,
|
||||
PostRepository $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);
|
||||
|
||||
// TODO: inject dispatcher
|
||||
event(new ConfigureDiscussionSearch($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();
|
||||
|
||||
$areMoreResults = $limit > 0 && $discussions->count() > $limit;
|
||||
|
||||
if ($areMoreResults) {
|
||||
$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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
24
src/Discussion/Search/Fulltext/DriverInterface.php
Normal file
24
src/Discussion/Search/Fulltext/DriverInterface.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Search\Fulltext;
|
||||
|
||||
interface DriverInterface
|
||||
{
|
||||
/**
|
||||
* Return an array of arrays of post IDs, grouped by discussion ID, which
|
||||
* match the given string.
|
||||
*
|
||||
* @param string $string
|
||||
* @return array
|
||||
*/
|
||||
public function match($string);
|
||||
}
|
36
src/Discussion/Search/Fulltext/MySqlFulltextDriver.php
Normal file
36
src/Discussion/Search/Fulltext/MySqlFulltextDriver.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Search\Fulltext;
|
||||
|
||||
use Flarum\Core\Post;
|
||||
|
||||
class MySqlFulltextDriver implements DriverInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function match($string)
|
||||
{
|
||||
$discussionIds = Post::where('type', 'comment')
|
||||
->whereRaw('MATCH (`content`) AGAINST (? IN BOOLEAN MODE)', [$string])
|
||||
->orderByRaw('MATCH (`content`) AGAINST (?) DESC', [$string])
|
||||
->lists('discussion_id', 'id');
|
||||
|
||||
$relevantPostIds = [];
|
||||
|
||||
foreach ($discussionIds as $postId => $discussionId) {
|
||||
$relevantPostIds[$discussionId][] = $postId;
|
||||
}
|
||||
|
||||
return $relevantPostIds;
|
||||
}
|
||||
}
|
59
src/Discussion/Search/Gambit/AuthorGambit.php
Normal file
59
src/Discussion/Search/Gambit/AuthorGambit.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Search\Gambit;
|
||||
|
||||
use Flarum\User\UserRepository;
|
||||
use Flarum\Core\Search\AbstractRegexGambit;
|
||||
use Flarum\Core\Search\AbstractSearch;
|
||||
use Flarum\Discussion\Search\DiscussionSearch;
|
||||
use LogicException;
|
||||
|
||||
class AuthorGambit extends AbstractRegexGambit
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $pattern = 'author:(.+)';
|
||||
|
||||
/**
|
||||
* @var \Flarum\User\UserRepository
|
||||
*/
|
||||
protected $users;
|
||||
|
||||
/**
|
||||
* @param \Flarum\User\UserRepository $users
|
||||
*/
|
||||
public function __construct(UserRepository $users)
|
||||
{
|
||||
$this->users = $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(AbstractSearch $search, array $matches, $negate)
|
||||
{
|
||||
if (! $search instanceof DiscussionSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
|
||||
}
|
||||
|
||||
$usernames = trim($matches[1], '"');
|
||||
$usernames = explode(',', $usernames);
|
||||
|
||||
$ids = [];
|
||||
foreach ($usernames as $username) {
|
||||
$ids[] = $this->users->getIdForUsername($username);
|
||||
}
|
||||
|
||||
$search->getQuery()->whereIn('start_user_id', $ids, 'and', $negate);
|
||||
}
|
||||
}
|
45
src/Discussion/Search/Gambit/CreatedGambit.php
Normal file
45
src/Discussion/Search/Gambit/CreatedGambit.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Search\Gambit;
|
||||
|
||||
use Flarum\Core\Search\AbstractRegexGambit;
|
||||
use Flarum\Core\Search\AbstractSearch;
|
||||
use Flarum\Discussion\Search\DiscussionSearch;
|
||||
use LogicException;
|
||||
|
||||
class CreatedGambit extends AbstractRegexGambit
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $pattern = 'created:(\d{4}\-\d\d\-\d\d)(\.\.(\d{4}\-\d\d\-\d\d))?';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(AbstractSearch $search, array $matches, $negate)
|
||||
{
|
||||
if (! $search instanceof DiscussionSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
|
||||
}
|
||||
|
||||
// If we've just been provided with a single YYYY-MM-DD date, then find
|
||||
// discussions that were started on that exact date. But if we've been
|
||||
// provided with a YYYY-MM-DD..YYYY-MM-DD range, then find discussions
|
||||
// that were started during that period.
|
||||
if (empty($matches[3])) {
|
||||
$search->getQuery()->whereDate('start_time', $negate ? '!=' : '=', $matches[1]);
|
||||
} else {
|
||||
$search->getQuery()->whereBetween('start_time', [$matches[1], $matches[3]], 'and', $negate);
|
||||
}
|
||||
}
|
||||
}
|
54
src/Discussion/Search/Gambit/FulltextGambit.php
Normal file
54
src/Discussion/Search/Gambit/FulltextGambit.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Search\Gambit;
|
||||
|
||||
use Flarum\Core\Search\AbstractSearch;
|
||||
use Flarum\Discussion\Search\DiscussionSearch;
|
||||
use Flarum\Discussion\Search\Fulltext\DriverInterface;
|
||||
use Flarum\Core\Search\GambitInterface;
|
||||
use LogicException;
|
||||
|
||||
class FulltextGambit implements GambitInterface
|
||||
{
|
||||
/**
|
||||
* @var \Flarum\Discussion\Search\Fulltext\DriverInterface
|
||||
*/
|
||||
protected $fulltext;
|
||||
|
||||
/**
|
||||
* @param \Flarum\Discussion\Search\Fulltext\DriverInterface $fulltext
|
||||
*/
|
||||
public function __construct(DriverInterface $fulltext)
|
||||
{
|
||||
$this->fulltext = $fulltext;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(AbstractSearch $search, $bit)
|
||||
{
|
||||
if (! $search instanceof DiscussionSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
|
||||
}
|
||||
|
||||
$relevantPostIds = $this->fulltext->match($bit);
|
||||
|
||||
$discussionIds = array_keys($relevantPostIds);
|
||||
|
||||
$search->setRelevantPostIds($relevantPostIds);
|
||||
|
||||
$search->getQuery()->whereIn('id', $discussionIds);
|
||||
|
||||
$search->setDefaultSort(['id' => $discussionIds]);
|
||||
}
|
||||
}
|
43
src/Discussion/Search/Gambit/HiddenGambit.php
Normal file
43
src/Discussion/Search/Gambit/HiddenGambit.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Search\Gambit;
|
||||
|
||||
use Flarum\Core\Search\AbstractRegexGambit;
|
||||
use Flarum\Core\Search\AbstractSearch;
|
||||
use Flarum\Discussion\Search\DiscussionSearch;
|
||||
use LogicException;
|
||||
|
||||
class HiddenGambit extends AbstractRegexGambit
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $pattern = 'is:hidden';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(AbstractSearch $search, array $matches, $negate)
|
||||
{
|
||||
if (! $search instanceof DiscussionSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
|
||||
}
|
||||
|
||||
$search->getQuery()->where(function ($query) use ($negate) {
|
||||
if ($negate) {
|
||||
$query->whereNull('hide_time')->where('comments_count', '>', 0);
|
||||
} else {
|
||||
$query->whereNotNull('hide_time')->orWhere('comments_count', 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
63
src/Discussion/Search/Gambit/UnreadGambit.php
Normal file
63
src/Discussion/Search/Gambit/UnreadGambit.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Search\Gambit;
|
||||
|
||||
use Flarum\Discussion\DiscussionRepository;
|
||||
use Flarum\Core\Search\AbstractRegexGambit;
|
||||
use Flarum\Core\Search\AbstractSearch;
|
||||
use Flarum\Discussion\Search\DiscussionSearch;
|
||||
use LogicException;
|
||||
|
||||
class UnreadGambit extends AbstractRegexGambit
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $pattern = 'is:unread';
|
||||
|
||||
/**
|
||||
* @var \Flarum\Discussion\DiscussionRepository
|
||||
*/
|
||||
protected $discussions;
|
||||
|
||||
/**
|
||||
* @param \Flarum\Discussion\DiscussionRepository $discussions
|
||||
*/
|
||||
public function __construct(DiscussionRepository $discussions)
|
||||
{
|
||||
$this->discussions = $discussions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(AbstractSearch $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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user