1
0
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:
Franz Liedke
2017-06-24 13:48:04 +02:00
parent 66abd7ecfd
commit 4fb38d6458
46 changed files with 149 additions and 147 deletions

View 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;
}
}

View 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;
});
}
}
}

View 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);
}

View 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;
}
}

View 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);
}
}

View 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);
}
}
}

View 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]);
}
}

View 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);
}
});
}
}

View 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);
}
});
}
}
}