mirror of
https://github.com/flarum/core.git
synced 2025-08-13 20:04:24 +02:00
Compare commits
27 Commits
v0.1.0-bet
...
as/search-
Author | SHA1 | Date | |
---|---|---|---|
|
f2aa804753 | ||
|
baa5fdad95 | ||
|
0b2d01b75f | ||
|
84c4e8dd6e | ||
|
c4daeeb1f2 | ||
|
87d2f3d246 | ||
|
0172008693 | ||
|
76680d197d | ||
|
0c95774333 | ||
|
67741c7a6f | ||
|
f5cfec15e3 | ||
|
47d2eee9ce | ||
|
c10cc92deb | ||
|
529d2edcaf | ||
|
f0e77a5789 | ||
|
87c258b2f8 | ||
|
cee87848fe | ||
|
5842dd1200 | ||
|
b311512502 | ||
|
0b2a5fa5b8 | ||
|
52e45aacad | ||
|
21c2a4b2a4 | ||
|
12c03dc4e1 | ||
|
d2927cfdb9 | ||
|
24b7a21507 | ||
|
c9a04fe009 | ||
|
bd7fa11b5a |
@@ -76,11 +76,11 @@
|
||||
"psr/http-server-handler": "^1.0",
|
||||
"psr/http-server-middleware": "^1.0",
|
||||
"s9e/text-formatter": "^2.3.6",
|
||||
"symfony/config": "^3.3",
|
||||
"symfony/console": "^4.2",
|
||||
"symfony/event-dispatcher": "^4.3.2",
|
||||
"symfony/translation": "^3.3",
|
||||
"symfony/yaml": "^3.3",
|
||||
"symfony/config": "^4.3.4",
|
||||
"symfony/console": "^4.3.4",
|
||||
"symfony/event-dispatcher": "^4.3.4",
|
||||
"symfony/translation": "^4.3.4",
|
||||
"symfony/yaml": "^4.3.4",
|
||||
"tobscure/json-api": "^0.3.0",
|
||||
"wikimedia/less.php": "^3.0"
|
||||
},
|
||||
|
4
js/dist/admin.js
vendored
4
js/dist/admin.js
vendored
File diff suppressed because one or more lines are too long
2
js/dist/admin.js.map
vendored
2
js/dist/admin.js.map
vendored
File diff suppressed because one or more lines are too long
6
js/dist/forum.js
vendored
6
js/dist/forum.js
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum.js.map
vendored
2
js/dist/forum.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -83,7 +83,7 @@ export default class Store {
|
||||
*/
|
||||
find(type, id, query = {}, options = {}) {
|
||||
let params = query;
|
||||
let url = app.forum.attribute('apiUrl') + '/' + type;
|
||||
let url = app.forum.attribute('apiUrl') + (query.search ? '/search/' : '/') + type;
|
||||
|
||||
if (id instanceof Array) {
|
||||
url += '?filter[id]=' + id.join(',');
|
||||
|
@@ -19,6 +19,7 @@ import extract from './utils/extract';
|
||||
import ScrollListener from './utils/ScrollListener';
|
||||
import stringToColor from './utils/stringToColor';
|
||||
import subclassOf from './utils/subclassOf';
|
||||
import SuperTextarea from './utils/SuperTextarea';
|
||||
import patchMithril from './utils/patchMithril';
|
||||
import classList from './utils/classList';
|
||||
import extractText from './utils/extractText';
|
||||
@@ -90,6 +91,7 @@ export default {
|
||||
'utils/stringToColor': stringToColor,
|
||||
'utils/Stream': Stream,
|
||||
'utils/subclassOf': subclassOf,
|
||||
'utils/SuperTextarea': SuperTextarea,
|
||||
'utils/setRouteWithForcedRefresh': setRouteWithForcedRefresh,
|
||||
'utils/patchMithril': patchMithril,
|
||||
'utils/classList': classList,
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import dayjs from 'dayjs';
|
||||
import * as Mithril from 'mithril';
|
||||
|
||||
/**
|
||||
* The `fullTime` helper displays a formatted time string wrapped in a <time>
|
||||
* tag.
|
||||
*
|
||||
* @param {Date} time
|
||||
* @return {Object}
|
||||
*/
|
||||
export default function fullTime(time) {
|
||||
export default function fullTime(time: Date): Mithril.Vnode {
|
||||
const d = dayjs(time);
|
||||
|
||||
const datetime = d.format();
|
@@ -1,14 +1,13 @@
|
||||
import dayjs from 'dayjs';
|
||||
import * as Mithril from 'mithril';
|
||||
import humanTimeUtil from '../utils/humanTime';
|
||||
|
||||
/**
|
||||
* The `humanTime` helper displays a time in a human-friendly time-ago format
|
||||
* (e.g. '12 days ago'), wrapped in a <time> tag with other information about
|
||||
* the time.
|
||||
*
|
||||
* @param {Date} time
|
||||
* @return {Object}
|
||||
*/
|
||||
export default function humanTime(time) {
|
||||
export default function humanTime(time: Date): Mithril.Vnode {
|
||||
const d = dayjs(time);
|
||||
|
||||
const datetime = d.format();
|
@@ -1,3 +1,6 @@
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/plugin/relativeTime';
|
||||
|
||||
/**
|
||||
* The `humanTime` utility converts a date to a localized, human-readable time-
|
||||
* ago string.
|
||||
|
@@ -56,9 +56,7 @@ export default class CommentPost extends Post {
|
||||
]);
|
||||
}
|
||||
|
||||
onupdate(vnode) {
|
||||
super.onupdate();
|
||||
|
||||
refreshContent() {
|
||||
const contentHtml = this.isEditing() ? '' : this.attrs.post.contentHtml();
|
||||
|
||||
// If the post content has changed since the last render, we'll run through
|
||||
@@ -66,13 +64,28 @@ export default class CommentPost extends Post {
|
||||
// necessary because TextFormatter outputs them for e.g. syntax highlighting.
|
||||
if (this.contentHtml !== contentHtml) {
|
||||
this.$('.Post-body script').each(function () {
|
||||
eval.call(window, $(this).text());
|
||||
const script = document.createElement('script');
|
||||
script.textContent = this.textContent;
|
||||
Array.from(this.attributes).forEach((attr) => script.setAttribute(attr.name, attr.value));
|
||||
this.parentNode.replaceChild(script, this);
|
||||
});
|
||||
}
|
||||
|
||||
this.contentHtml = contentHtml;
|
||||
}
|
||||
|
||||
oncreate(vnode) {
|
||||
super.oncreate(vnode);
|
||||
|
||||
this.refreshContent();
|
||||
}
|
||||
|
||||
onupdate(vnode) {
|
||||
super.onupdate(vnode);
|
||||
|
||||
this.refreshContent();
|
||||
}
|
||||
|
||||
isEditing() {
|
||||
return app.composer.bodyMatches(EditPostComposer, { post: this.attrs.post });
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ export default class DiscussionsSearchSource {
|
||||
include: 'mostRelevantPost',
|
||||
};
|
||||
|
||||
return app.store.find('discussions', params).then((results) => (this.results[query] = results));
|
||||
return app.store.find('discussions', params, { search: query }).then((results) => (this.results[query] = results));
|
||||
}
|
||||
|
||||
view(query) {
|
||||
|
@@ -16,10 +16,14 @@ export default class UsersSearchResults {
|
||||
|
||||
search(query) {
|
||||
return app.store
|
||||
.find('users', {
|
||||
filter: { q: query },
|
||||
page: { limit: 5 },
|
||||
})
|
||||
.find(
|
||||
'users',
|
||||
{
|
||||
filter: { q: query },
|
||||
page: { limit: 5 },
|
||||
},
|
||||
{ search: query }
|
||||
)
|
||||
.then((results) => {
|
||||
this.results[query] = results;
|
||||
m.redraw();
|
||||
|
@@ -119,7 +119,7 @@ export default class DiscussionListState {
|
||||
params.page = { offset };
|
||||
params.include = params.include.join(',');
|
||||
|
||||
return this.app.store.find('discussions', params);
|
||||
return this.app.store.find('discussions', params, { search: params.filter.q });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -105,6 +105,10 @@
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.off.Checkbox--switch .Checkbox-display {
|
||||
background: @muted-more-color;
|
||||
}
|
||||
}
|
||||
.Modal-footer {
|
||||
border: 0;
|
||||
|
@@ -54,9 +54,10 @@ class AdminServiceProvider extends AbstractServiceProvider
|
||||
HttpMiddleware\StartSession::class,
|
||||
HttpMiddleware\RememberFromCookie::class,
|
||||
HttpMiddleware\AuthenticateWithSession::class,
|
||||
HttpMiddleware\CheckCsrfToken::class,
|
||||
HttpMiddleware\SetLocale::class,
|
||||
Middleware\RequireAdministrateAbility::class,
|
||||
'flarum.admin.route_resolver',
|
||||
HttpMiddleware\CheckCsrfToken::class,
|
||||
Middleware\RequireAdministrateAbility::class
|
||||
];
|
||||
});
|
||||
|
||||
@@ -68,6 +69,10 @@ class AdminServiceProvider extends AbstractServiceProvider
|
||||
);
|
||||
});
|
||||
|
||||
$this->app->bind('flarum.admin.route_resolver', function () {
|
||||
return new HttpMiddleware\ResolveRoute($this->app->make('flarum.admin.routes'));
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.admin.handler', function () {
|
||||
$pipe = new MiddlewarePipe;
|
||||
|
||||
@@ -75,7 +80,7 @@ class AdminServiceProvider extends AbstractServiceProvider
|
||||
$pipe->pipe($this->app->make($middleware));
|
||||
}
|
||||
|
||||
$pipe->pipe(new HttpMiddleware\DispatchRoute($this->app->make('flarum.admin.routes')));
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
|
||||
return $pipe;
|
||||
});
|
||||
|
@@ -51,8 +51,9 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
HttpMiddleware\RememberFromCookie::class,
|
||||
HttpMiddleware\AuthenticateWithSession::class,
|
||||
HttpMiddleware\AuthenticateWithHeader::class,
|
||||
HttpMiddleware\CheckCsrfToken::class,
|
||||
HttpMiddleware\SetLocale::class,
|
||||
'flarum.api.route_resolver',
|
||||
HttpMiddleware\CheckCsrfToken::class
|
||||
];
|
||||
});
|
||||
|
||||
@@ -64,6 +65,10 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
);
|
||||
});
|
||||
|
||||
$this->app->bind('flarum.api.route_resolver', function () {
|
||||
return new HttpMiddleware\ResolveRoute($this->app->make('flarum.api.routes'));
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.api.handler', function () {
|
||||
$pipe = new MiddlewarePipe;
|
||||
|
||||
@@ -71,10 +76,16 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
$pipe->pipe($this->app->make($middleware));
|
||||
}
|
||||
|
||||
$pipe->pipe(new HttpMiddleware\DispatchRoute($this->app->make('flarum.api.routes')));
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
|
||||
return $pipe;
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.api.notification_serializers', function () {
|
||||
return [
|
||||
'discussionRenamed' => BasicDiscussionSerializer::class
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,7 +93,7 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->registerNotificationSerializers();
|
||||
$this->setNotificationSerializers();
|
||||
|
||||
AbstractSerializeController::setContainer($this->app);
|
||||
AbstractSerializeController::setEventDispatcher($events = $this->app->make('events'));
|
||||
@@ -94,13 +105,12 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
/**
|
||||
* Register notification serializers.
|
||||
*/
|
||||
protected function registerNotificationSerializers()
|
||||
protected function setNotificationSerializers()
|
||||
{
|
||||
$blueprints = [];
|
||||
$serializers = [
|
||||
'discussionRenamed' => BasicDiscussionSerializer::class
|
||||
];
|
||||
$serializers = $this->app->make('flarum.api.notification_serializers');
|
||||
|
||||
// Deprecated in beta 15, remove in beta 16
|
||||
$this->app->make('events')->dispatch(
|
||||
new ConfigureNotificationTypes($blueprints, $serializers)
|
||||
);
|
||||
|
@@ -11,10 +11,10 @@ namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\DiscussionSerializer;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Discussion\DiscussionRepository;
|
||||
use Flarum\Discussion\Search\DiscussionSearcher;
|
||||
use Flarum\Filter\Filterer;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Search\SearchCriteria;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
@@ -49,9 +49,14 @@ class ListDiscussionsController extends AbstractListController
|
||||
public $sortFields = ['lastPostedAt', 'commentCount', 'createdAt'];
|
||||
|
||||
/**
|
||||
* @var DiscussionSearcher
|
||||
* @var DiscussionRepository
|
||||
*/
|
||||
protected $searcher;
|
||||
protected $discussions;
|
||||
|
||||
/**
|
||||
* @var Filterer
|
||||
*/
|
||||
protected $filterer;
|
||||
|
||||
/**
|
||||
* @var UrlGenerator
|
||||
@@ -62,9 +67,10 @@ class ListDiscussionsController extends AbstractListController
|
||||
* @param DiscussionSearcher $searcher
|
||||
* @param UrlGenerator $url
|
||||
*/
|
||||
public function __construct(DiscussionSearcher $searcher, UrlGenerator $url)
|
||||
public function __construct(DiscussionRepository $discussions, Filterer $filterer, UrlGenerator $url)
|
||||
{
|
||||
$this->searcher = $searcher;
|
||||
$this->discussions = $discussions;
|
||||
$this->filterer = $filterer;
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
@@ -74,16 +80,16 @@ class ListDiscussionsController extends AbstractListController
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
$query = Arr::get($this->extractFilter($request), 'q');
|
||||
$sort = $this->extractSort($request);
|
||||
|
||||
$criteria = new SearchCriteria($actor, $query, $sort);
|
||||
$filters = $this->extractFilter($request);
|
||||
$sort = $this->extractSort($request);
|
||||
$query = $this->discussions->query();
|
||||
|
||||
$limit = $this->extractLimit($request);
|
||||
$offset = $this->extractOffset($request);
|
||||
$load = array_merge($this->extractInclude($request), ['state']);
|
||||
|
||||
$results = $this->searcher->search($criteria, $limit, $offset);
|
||||
$results = $this->filterer->filter($actor, $query, $filters, $sort, $limit, $offset, $load);
|
||||
|
||||
$document->addPaginationLinks(
|
||||
$this->url->to('api')->route('discussions.index'),
|
||||
|
@@ -10,10 +10,9 @@
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\UserSerializer;
|
||||
use Flarum\Filter\Filterer;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Search\SearchCriteria;
|
||||
use Flarum\User\Search\UserSearcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Flarum\User\UserRepository;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
@@ -41,9 +40,9 @@ class ListUsersController extends AbstractListController
|
||||
];
|
||||
|
||||
/**
|
||||
* @var UserSearcher
|
||||
* @var Filterer
|
||||
*/
|
||||
protected $searcher;
|
||||
protected $filterer;
|
||||
|
||||
/**
|
||||
* @var UrlGenerator
|
||||
@@ -51,13 +50,20 @@ class ListUsersController extends AbstractListController
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* @param UserSearcher $searcher
|
||||
* @param UrlGenerator $url
|
||||
* @var UserRepository
|
||||
*/
|
||||
public function __construct(UserSearcher $searcher, UrlGenerator $url)
|
||||
protected $users;
|
||||
|
||||
/**
|
||||
* @param Filterer $filterer
|
||||
* @param UrlGenerator $url
|
||||
* @param UserRepository $users
|
||||
*/
|
||||
public function __construct(Filterer $filterer, UrlGenerator $url, UserRepository $users)
|
||||
{
|
||||
$this->searcher = $searcher;
|
||||
$this->filterer = $filterer;
|
||||
$this->url = $url;
|
||||
$this->users = $users;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,16 +75,16 @@ class ListUsersController extends AbstractListController
|
||||
|
||||
$actor->assertCan('viewUserList');
|
||||
|
||||
$query = Arr::get($this->extractFilter($request), 'q');
|
||||
$sort = $this->extractSort($request);
|
||||
$query = $this->users->query();
|
||||
|
||||
$criteria = new SearchCriteria($actor, $query, $sort);
|
||||
$filters = $this->extractFilter($request);
|
||||
$sort = $this->extractSort($request);
|
||||
|
||||
$limit = $this->extractLimit($request);
|
||||
$offset = $this->extractOffset($request);
|
||||
$load = $this->extractInclude($request);
|
||||
|
||||
$results = $this->searcher->search($criteria, $limit, $offset, $load);
|
||||
$results = $this->filterer->filter($actor, $query, $filters, $sort, $limit, $offset, $load);
|
||||
|
||||
$document->addPaginationLinks(
|
||||
$this->url->to('api')->route('users.index'),
|
||||
|
112
src/Api/Controller/SearchDiscussionsController.php
Normal file
112
src/Api/Controller/SearchDiscussionsController.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\DiscussionSerializer;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Discussion\Search\DiscussionSearcher;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Search\SearchCriteria;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
class SearchDiscussionsController extends AbstractListController
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $serializer = DiscussionSerializer::class;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $include = [
|
||||
'user',
|
||||
'lastPostedUser',
|
||||
'mostRelevantPost',
|
||||
'mostRelevantPost.user'
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $optionalInclude = [
|
||||
'firstPost',
|
||||
'lastPost'
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $sortFields = ['lastPostedAt', 'commentCount', 'createdAt'];
|
||||
|
||||
/**
|
||||
* @var DiscussionSearcher
|
||||
*/
|
||||
protected $searcher;
|
||||
|
||||
/**
|
||||
* @var UrlGenerator
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* @param DiscussionSearcher $searcher
|
||||
* @param UrlGenerator $url
|
||||
*/
|
||||
public function __construct(DiscussionSearcher $searcher, UrlGenerator $url)
|
||||
{
|
||||
$this->searcher = $searcher;
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
$query = Arr::get($this->extractFilter($request), 'q');
|
||||
$sort = $this->extractSort($request);
|
||||
|
||||
$criteria = new SearchCriteria($actor, $query, $sort);
|
||||
|
||||
$limit = $this->extractLimit($request);
|
||||
$offset = $this->extractOffset($request);
|
||||
$load = array_merge($this->extractInclude($request), ['state']);
|
||||
|
||||
$results = $this->searcher->search($criteria, $limit, $offset);
|
||||
|
||||
$document->addPaginationLinks(
|
||||
$this->url->to('api')->route('discussions.index'),
|
||||
$request->getQueryParams(),
|
||||
$offset,
|
||||
$limit,
|
||||
$results->areMoreResults() ? null : 0
|
||||
);
|
||||
|
||||
Discussion::setStateUser($actor);
|
||||
|
||||
$results = $results->getResults()->load($load);
|
||||
|
||||
if ($relations = array_intersect($load, ['firstPost', 'lastPost'])) {
|
||||
foreach ($results as $discussion) {
|
||||
foreach ($relations as $relation) {
|
||||
if ($discussion->$relation) {
|
||||
$discussion->$relation->discussion = $discussion;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
93
src/Api/Controller/SearchUsersController.php
Normal file
93
src/Api/Controller/SearchUsersController.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\UserSerializer;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Search\SearchCriteria;
|
||||
use Flarum\User\Search\UserSearcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
class SearchUsersController extends AbstractListController
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $serializer = UserSerializer::class;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $include = ['groups'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $sortFields = [
|
||||
'username',
|
||||
'commentCount',
|
||||
'discussionCount',
|
||||
'lastSeenAt',
|
||||
'joinedAt'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var UserSearcher
|
||||
*/
|
||||
protected $searcher;
|
||||
|
||||
/**
|
||||
* @var UrlGenerator
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* @param UserSearcher $searcher
|
||||
* @param UrlGenerator $url
|
||||
*/
|
||||
public function __construct(UserSearcher $searcher, UrlGenerator $url)
|
||||
{
|
||||
$this->searcher = $searcher;
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
|
||||
$actor->assertCan('viewUserList');
|
||||
|
||||
$query = Arr::get($this->extractFilter($request), 'q');
|
||||
$sort = $this->extractSort($request);
|
||||
|
||||
$criteria = new SearchCriteria($actor, $query, $sort);
|
||||
|
||||
$limit = $this->extractLimit($request);
|
||||
$offset = $this->extractOffset($request);
|
||||
$load = $this->extractInclude($request);
|
||||
|
||||
$results = $this->searcher->search($criteria, $limit, $offset, $load);
|
||||
|
||||
$document->addPaginationLinks(
|
||||
$this->url->to('api')->route('users.index'),
|
||||
$request->getQueryParams(),
|
||||
$offset,
|
||||
$limit,
|
||||
$results->areMoreResults() ? null : 0
|
||||
);
|
||||
|
||||
return $results->getResults();
|
||||
}
|
||||
}
|
@@ -95,6 +95,13 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
|
||||
$route->toController(Controller\SendConfirmationEmailController::class)
|
||||
);
|
||||
|
||||
// List users
|
||||
$map->get(
|
||||
'/search/users',
|
||||
'users.search',
|
||||
$route->toController(Controller\SearchUsersController::class)
|
||||
);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Notifications
|
||||
@@ -163,6 +170,13 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
|
||||
$route->toController(Controller\DeleteDiscussionController::class)
|
||||
);
|
||||
|
||||
// Search discussions
|
||||
$map->get(
|
||||
'/search/discussions',
|
||||
'discussions.search',
|
||||
$route->toController(Controller\SearchDiscussionsController::class)
|
||||
);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Posts
|
||||
|
@@ -13,6 +13,9 @@ use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use InvalidArgumentException;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* @deprecated in beta 15, removed in beta 16
|
||||
*/
|
||||
class ConfigureNotificationTypes
|
||||
{
|
||||
/**
|
||||
|
@@ -9,6 +9,9 @@
|
||||
|
||||
namespace Flarum\Event;
|
||||
|
||||
/**
|
||||
* @deprecated in beta 15, remove in beta 16. Use the Post extender instead.
|
||||
*/
|
||||
class ConfigurePostTypes
|
||||
{
|
||||
private $models;
|
||||
|
@@ -14,11 +14,28 @@ use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class Csrf implements ExtenderInterface
|
||||
{
|
||||
protected $csrfExemptPaths = [];
|
||||
protected $csrfExemptRoutes = [];
|
||||
|
||||
/**
|
||||
* Exempt a named route from CSRF checks.
|
||||
*
|
||||
* @param string $routeName
|
||||
*/
|
||||
public function exemptRoute(string $routeName)
|
||||
{
|
||||
$this->csrfExemptRoutes[] = $routeName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exempt a path from csrf checks. Wildcards are supported.
|
||||
*
|
||||
* @deprecated beta 15, remove beta 16. Exempt routes should be used instead.
|
||||
*/
|
||||
public function exemptPath(string $path)
|
||||
{
|
||||
$this->csrfExemptPaths[] = $path;
|
||||
$this->csrfExemptRoutes[] = $path;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -26,7 +43,7 @@ class Csrf implements ExtenderInterface
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$container->extend('flarum.http.csrfExemptPaths', function ($existingExemptPaths) {
|
||||
return array_merge($existingExemptPaths, $this->csrfExemptPaths);
|
||||
return array_merge($existingExemptPaths, $this->csrfExemptRoutes);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -25,7 +25,7 @@ class Event implements ExtenderInterface
|
||||
* - the class attribute of a class with a public `handle` method, which accepts an instance of the event as a parameter
|
||||
*
|
||||
* @param string $event
|
||||
* @param callable $listener
|
||||
* @param callable|string $listener
|
||||
*/
|
||||
public function listen(string $event, $listener)
|
||||
{
|
||||
|
63
src/Extend/Filter.php
Normal file
63
src/Extend/Filter.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Filter\Filterer;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class Filter implements ExtenderInterface
|
||||
{
|
||||
private $resource;
|
||||
private $filters = [];
|
||||
private $filterMutators = [];
|
||||
|
||||
/**
|
||||
* @param string $resource: The ::class attribute of the resource this applies to, which is typically an Eloquent model.
|
||||
*/
|
||||
public function __construct($resource)
|
||||
{
|
||||
$this->resource = $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a filter to run when the resource is filtered.
|
||||
*
|
||||
* @param string $filterClass: The ::class attribute of the filter you are adding.
|
||||
*/
|
||||
public function addFilter(string $filterClass)
|
||||
{
|
||||
$this->filters[] = $filterClass;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback through which to run all filter queries after filters have been applied.
|
||||
*/
|
||||
public function addFilterMutator($callback)
|
||||
{
|
||||
$this->filterMutators[] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
foreach ($this->filters as $filter) {
|
||||
Filterer::addFilter($this->resource, $container->make($filter));
|
||||
}
|
||||
|
||||
foreach ($this->filterMutators as $mutator) {
|
||||
Filterer::addFilterMutator($this->resource, ContainerUtil::wrapCallback($mutator, $container));
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,38 +10,93 @@
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Formatter\Event\Configuring;
|
||||
use Flarum\Formatter\Formatter as ActualFormatter;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Events\Dispatcher;
|
||||
|
||||
class Formatter implements ExtenderInterface, LifecycleInterface
|
||||
{
|
||||
private $callback;
|
||||
private $configurationCallbacks = [];
|
||||
private $parsingCallbacks = [];
|
||||
private $renderingCallbacks = [];
|
||||
|
||||
/**
|
||||
* Configure the formatter. This can be used to add support for custom markdown/bbcode/etc tags,
|
||||
* or otherwise change the formatter. Please see documentation for the s9e text formatter library for more
|
||||
* information on how to use this.
|
||||
*
|
||||
* @param callable|string $callback
|
||||
*
|
||||
* The callback can be a closure or invokable class, and should accept:
|
||||
* - \s9e\TextFormatter\Configurator $configurator
|
||||
*/
|
||||
public function configure($callback)
|
||||
{
|
||||
$this->callback = $callback;
|
||||
$this->configurationCallbacks[] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the system for parsing. This can be used to modify the text that will be parsed, or to modify the parser.
|
||||
* Please note that the text to be parsed must be returned, regardless of whether it's changed.
|
||||
*
|
||||
* @param callable|string $callback
|
||||
*
|
||||
* The callback can be a closure or invokable class, and should accept:
|
||||
* - \s9e\TextFormatter\Parser $parser
|
||||
* - mixed $context
|
||||
* - string $text: The text to be parsed.
|
||||
*
|
||||
* The callback should return:
|
||||
* - string $text: The text to be parsed.
|
||||
*/
|
||||
public function parse($callback)
|
||||
{
|
||||
$this->parsingCallbacks[] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the system for rendering. This can be used to modify the xml that will be rendered, or to modify the renderer.
|
||||
* Please note that the xml to be rendered must be returned, regardless of whether it's changed.
|
||||
*
|
||||
* @param callable|string $callback
|
||||
*
|
||||
* The callback can be a closure or invokable class, and should accept:
|
||||
* - \s9e\TextFormatter\Rendered $renderer
|
||||
* - mixed $context
|
||||
* - string $xml: The xml to be rendered.
|
||||
* - ServerRequestInterface $request
|
||||
*
|
||||
* The callback should return:
|
||||
* - string $xml: The xml to be rendered.
|
||||
*/
|
||||
public function render($callback)
|
||||
{
|
||||
$this->renderingCallbacks[] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$events = $container->make(Dispatcher::class);
|
||||
|
||||
$events->listen(
|
||||
Configuring::class,
|
||||
function (Configuring $event) use ($container) {
|
||||
if (is_string($this->callback)) {
|
||||
$callback = $container->make($this->callback);
|
||||
} else {
|
||||
$callback = $this->callback;
|
||||
}
|
||||
|
||||
$callback($event->configurator);
|
||||
$container->extend('flarum.formatter', function ($formatter, $container) {
|
||||
foreach ($this->configurationCallbacks as $callback) {
|
||||
$formatter->addConfigurationCallback(ContainerUtil::wrapCallback($callback, $container));
|
||||
}
|
||||
);
|
||||
|
||||
foreach ($this->parsingCallbacks as $callback) {
|
||||
$formatter->addParsingCallback(ContainerUtil::wrapCallback($callback, $container));
|
||||
}
|
||||
|
||||
foreach ($this->renderingCallbacks as $callback) {
|
||||
$formatter->addRenderingCallback(ContainerUtil::wrapCallback($callback, $container));
|
||||
}
|
||||
|
||||
return $formatter;
|
||||
});
|
||||
}
|
||||
|
||||
public function onEnable(Container $container, Extension $extension)
|
||||
|
@@ -12,6 +12,7 @@ namespace Flarum\Extend;
|
||||
use Flarum\Extension\Event\Disabled;
|
||||
use Flarum\Extension\Event\Enabled;
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Flarum\Foundation\Event\ClearingCache;
|
||||
use Flarum\Frontend\Assets;
|
||||
use Flarum\Frontend\Compiler\Source\SourceCollector;
|
||||
@@ -171,11 +172,7 @@ class Frontend implements ExtenderInterface
|
||||
"flarum.frontend.$this->frontend",
|
||||
function (ActualFrontend $frontend, Container $container) {
|
||||
foreach ($this->content as $content) {
|
||||
if (is_string($content)) {
|
||||
$content = $container->make($content);
|
||||
}
|
||||
|
||||
$frontend->content($content);
|
||||
$frontend->content(ContainerUtil::wrapCallback($content, $container));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@@ -11,12 +11,14 @@ namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Database\AbstractModel;
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class Model implements ExtenderInterface
|
||||
{
|
||||
private $modelClass;
|
||||
private $customRelations = [];
|
||||
|
||||
/**
|
||||
* @param string $modelClass The ::class attribute of the model you are modifying.
|
||||
@@ -48,7 +50,9 @@ class Model implements ExtenderInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a default value for a given attribute, which can be an explicit value, or a closure.
|
||||
* Add a default value for a given attribute, which can be an explicit value, a closure,
|
||||
* or an instance of an invokable class. Unlike with some other extenders,
|
||||
* it CANNOT be the `::class` attribute of an invokable class.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
@@ -157,7 +161,7 @@ class Model implements ExtenderInterface
|
||||
* @param string $name: The name of the relation. This doesn't have to be anything in particular,
|
||||
* but has to be unique from other relation names for this model, and should
|
||||
* work as the name of a method.
|
||||
* @param callable $callable
|
||||
* @param callable|string $callback
|
||||
*
|
||||
* The callable can be a closure or invokable class, and should accept:
|
||||
* - $instance: An instance of this model.
|
||||
@@ -168,15 +172,17 @@ class Model implements ExtenderInterface
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function relationship(string $name, callable $callable)
|
||||
public function relationship(string $name, $callback)
|
||||
{
|
||||
Arr::set(AbstractModel::$customRelations, "$this->modelClass.$name", $callable);
|
||||
$this->customRelations[$name] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
// Nothing needed here.
|
||||
foreach ($this->customRelations as $name => $callback) {
|
||||
Arr::set(AbstractModel::$customRelations, "$this->modelClass.$name", ContainerUtil::wrapCallback($callback, $container));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
78
src/Extend/Notification.php
Normal file
78
src/Extend/Notification.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class Notification implements ExtenderInterface
|
||||
{
|
||||
private $blueprints = [];
|
||||
private $serializers = [];
|
||||
private $drivers = [];
|
||||
private $typesEnabledByDefault = [];
|
||||
|
||||
/**
|
||||
* @param string $blueprint The ::class attribute of the blueprint class.
|
||||
* This blueprint should implement \Flarum\Notification\Blueprint\BlueprintInterface.
|
||||
* @param string $serializer The ::class attribute of the serializer class.
|
||||
* This serializer should extend from \Flarum\Api\Serializer\AbstractSerializer.
|
||||
* @param string[] $driversEnabledByDefault The names of the drivers enabled by default for this notification type.
|
||||
* (example: alert, email).
|
||||
* @return self
|
||||
*/
|
||||
public function type(string $blueprint, string $serializer, array $driversEnabledByDefault = [])
|
||||
{
|
||||
$this->blueprints[$blueprint] = $driversEnabledByDefault;
|
||||
$this->serializers[$blueprint::getType()] = $serializer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $driverName The name of the notification driver.
|
||||
* @param string $driver The ::class attribute of the driver class.
|
||||
* This driver should implement \Flarum\Notification\Driver\NotificationDriverInterface.
|
||||
* @param string[] $typesEnabledByDefault The names of blueprint classes of types enabled by default for this driver.
|
||||
* @return self
|
||||
*/
|
||||
public function driver(string $driverName, string $driver, array $typesEnabledByDefault = [])
|
||||
{
|
||||
$this->drivers[$driverName] = $driver;
|
||||
$this->typesEnabledByDefault[$driverName] = $typesEnabledByDefault;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$container->extend('flarum.notification.blueprints', function ($existingBlueprints) {
|
||||
$existingBlueprints = array_merge($existingBlueprints, $this->blueprints);
|
||||
|
||||
foreach ($this->typesEnabledByDefault as $driverName => $typesEnabledByDefault) {
|
||||
foreach ($typesEnabledByDefault as $blueprintClass) {
|
||||
if (isset($existingBlueprints[$blueprintClass]) && (! in_array($driverName, $existingBlueprints[$blueprintClass]))) {
|
||||
$existingBlueprints[$blueprintClass][] = $driverName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $existingBlueprints;
|
||||
});
|
||||
|
||||
$container->extend('flarum.api.notification_serializers', function ($existingSerializers) {
|
||||
return array_merge($existingSerializers, $this->serializers);
|
||||
});
|
||||
|
||||
$container->extend('flarum.notification.drivers', function ($existingDrivers) {
|
||||
return array_merge($existingDrivers, $this->drivers);
|
||||
});
|
||||
}
|
||||
}
|
39
src/Extend/Post.php
Normal file
39
src/Extend/Post.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Post\Post as PostModel;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class Post implements ExtenderInterface
|
||||
{
|
||||
private $postTypes = [];
|
||||
|
||||
/**
|
||||
* Register a new post type. This is generally done for custom 'event posts',
|
||||
* such as those that appear when a discussion is renamed.
|
||||
*
|
||||
* @param string $postType: The ::class attribute of the custom Post type that is being added.
|
||||
*/
|
||||
public function type(string $postType)
|
||||
{
|
||||
$this->postTypes[] = $postType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
foreach ($this->postTypes as $postType) {
|
||||
PostModel::setModel($postType::$type, $postType);
|
||||
}
|
||||
}
|
||||
}
|
40
src/Extend/ServiceProvider.php
Normal file
40
src/Extend/ServiceProvider.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class ServiceProvider implements ExtenderInterface
|
||||
{
|
||||
private $providers = [];
|
||||
|
||||
/**
|
||||
* Register a service provider.
|
||||
*
|
||||
* @param string $serviceProviderClass The ::class attribute of the service provider class.
|
||||
* @return self
|
||||
*/
|
||||
public function register(string $serviceProviderClass)
|
||||
{
|
||||
$this->providers[] = $serviceProviderClass;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$app = $container->make('flarum');
|
||||
|
||||
foreach ($this->providers as $provider) {
|
||||
$app->register($provider);
|
||||
}
|
||||
}
|
||||
}
|
@@ -35,7 +35,7 @@ class User implements ExtenderInterface
|
||||
* This can be used to give a user permissions for groups they aren't actually in, based on context.
|
||||
* It will not change the group badges displayed for the user.
|
||||
*
|
||||
* @param callable $callable
|
||||
* @param callable|string $callback
|
||||
*
|
||||
* The callable can be a closure or invokable class, and should accept:
|
||||
* - \Flarum\User\User $user: the user in question.
|
||||
@@ -44,9 +44,9 @@ class User implements ExtenderInterface
|
||||
* The callable should return:
|
||||
* - array $groupIds: an array of ids for the groups the user belongs to.
|
||||
*/
|
||||
public function permissionGroups(callable $callable)
|
||||
public function permissionGroups($callback)
|
||||
{
|
||||
$this->groupProcessors[] = $callable;
|
||||
$this->groupProcessors[] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
55
src/Extend/Validator.php
Normal file
55
src/Extend/Validator.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class Validator implements ExtenderInterface
|
||||
{
|
||||
private $configurationCallbacks = [];
|
||||
private $validator;
|
||||
|
||||
/**
|
||||
* @param string $validatorClass: The ::class attribute of the validator you are modifying.
|
||||
* The validator should inherit from \Flarum\Foundation\AbstractValidator.
|
||||
*/
|
||||
public function __construct($validatorClass)
|
||||
{
|
||||
$this->validator = $validatorClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator. This is often used to adjust validation rules, but can be
|
||||
* used to make other changes to the validator as well.
|
||||
*
|
||||
* @param callable $callable
|
||||
*
|
||||
* The callable can be a closure or invokable class, and should accept:
|
||||
* - \Flarum\Foundation\AbstractValidator $flarumValidator: The Flarum validator wrapper
|
||||
* - \Illuminate\Validation\Validator $validator: The Laravel validator instance
|
||||
*/
|
||||
public function configure($callback)
|
||||
{
|
||||
$this->configurationCallbacks[] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$container->resolving($this->validator, function ($validator, $container) {
|
||||
foreach ($this->configurationCallbacks as $callback) {
|
||||
$validator->addConfiguration(ContainerUtil::wrapCallback($callback, $container));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
26
src/Filter/FilterInterface.php
Normal file
26
src/Filter/FilterInterface.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Filter;
|
||||
|
||||
interface FilterInterface
|
||||
{
|
||||
/**
|
||||
* This filter will only be run when a query contains a filter param with this key.
|
||||
*/
|
||||
public function getKey(): string;
|
||||
|
||||
/**
|
||||
* Filters a query.
|
||||
*
|
||||
* @param WrappedFilter $filter
|
||||
* @param string $value The value of the requested filter
|
||||
*/
|
||||
public function apply(WrappedFilter $wrappedFilter, $filterValue);
|
||||
}
|
88
src/Filter/Filterer.php
Normal file
88
src/Filter/Filterer.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Filter;
|
||||
|
||||
use Flarum\Search\ApplySearchParametersTrait;
|
||||
use Flarum\Search\SearchResults;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class Filterer
|
||||
{
|
||||
use ApplySearchParametersTrait;
|
||||
|
||||
protected static $filters = [];
|
||||
|
||||
protected static $filterMutators = [];
|
||||
|
||||
public static function addFilter($resource, FilterInterface $filter)
|
||||
{
|
||||
if (! array_key_exists($resource, static::$filters)) {
|
||||
static::$filters[$resource] = [];
|
||||
}
|
||||
|
||||
if (! array_key_exists($filter->getKey(), static::$filters[$resource])) {
|
||||
static::$filters[$resource][$filter->getKey()] = [];
|
||||
}
|
||||
|
||||
static::$filters[$resource][$filter->getKey()][] = $filter;
|
||||
}
|
||||
|
||||
public static function addFilterMutator($resource, $mutator)
|
||||
{
|
||||
if (! array_key_exists($resource, static::$filterMutators)) {
|
||||
static::$filterMutators[$resource] = [];
|
||||
}
|
||||
|
||||
static::$filterMutators[$resource][] = $mutator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FilterCriteria $criteria
|
||||
* @param int|null $limit
|
||||
* @param int $offset
|
||||
*
|
||||
* @return FilterResults
|
||||
*/
|
||||
public function filter($actor, $query, $filters, $sort = null, $limit = null, $offset = 0, array $load = [])
|
||||
{
|
||||
$resource = get_class($query->getModel());
|
||||
|
||||
$query->whereVisibleTo($actor);
|
||||
|
||||
$wrappedFilter = new WrappedFilter($query->getQuery(), $actor);
|
||||
|
||||
foreach ($filters as $filterKey => $filterValue) {
|
||||
foreach (Arr::get(static::$filters, "$resource.$filterKey", []) as $filter) {
|
||||
$filter->apply($wrappedFilter, $filterValue);
|
||||
}
|
||||
}
|
||||
|
||||
$this->applySort($wrappedFilter, $sort);
|
||||
$this->applyOffset($wrappedFilter, $offset);
|
||||
$this->applyLimit($wrappedFilter, $limit + 1);
|
||||
|
||||
foreach (Arr::get(static::$filterMutators, $resource, []) as $mutator) {
|
||||
$mutator($query, $actor, $filters, $sort);
|
||||
}
|
||||
|
||||
// Execute the filter 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.
|
||||
$results = $query->get();
|
||||
|
||||
if ($areMoreResults = $limit > 0 && $results->count() > $limit) {
|
||||
$results->pop();
|
||||
}
|
||||
|
||||
$results->load($load);
|
||||
|
||||
return new SearchResults($results, $areMoreResults);
|
||||
}
|
||||
}
|
16
src/Filter/WrappedFilter.php
Normal file
16
src/Filter/WrappedFilter.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Filter;
|
||||
|
||||
use Flarum\Search\AbstractSearch;
|
||||
|
||||
class WrappedFilter extends AbstractSearch
|
||||
{
|
||||
}
|
@@ -11,6 +11,9 @@ namespace Flarum\Formatter\Event;
|
||||
|
||||
use s9e\TextFormatter\Configurator;
|
||||
|
||||
/**
|
||||
* @deprecated beta 15, removed beta 16. Use the Formatter extender instead.
|
||||
*/
|
||||
class Configuring
|
||||
{
|
||||
/**
|
||||
|
@@ -11,6 +11,9 @@ namespace Flarum\Formatter\Event;
|
||||
|
||||
use s9e\TextFormatter\Parser;
|
||||
|
||||
/**
|
||||
* @deprecated beta 15, removed beta 16. Use the Formatter extender instead.
|
||||
*/
|
||||
class Parsing
|
||||
{
|
||||
/**
|
||||
|
@@ -12,6 +12,9 @@ namespace Flarum\Formatter\Event;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use s9e\TextFormatter\Renderer;
|
||||
|
||||
/**
|
||||
* @deprecated beta 15, removed beta 16. Use the Formatter extender instead.
|
||||
*/
|
||||
class Rendering
|
||||
{
|
||||
/**
|
||||
|
@@ -20,6 +20,12 @@ use s9e\TextFormatter\Unparser;
|
||||
|
||||
class Formatter
|
||||
{
|
||||
protected $configurationCallbacks = [];
|
||||
|
||||
protected $parsingCallbacks = [];
|
||||
|
||||
protected $renderingCallbacks = [];
|
||||
|
||||
/**
|
||||
* @var Repository
|
||||
*/
|
||||
@@ -47,6 +53,21 @@ class Formatter
|
||||
$this->cacheDir = $cacheDir;
|
||||
}
|
||||
|
||||
public function addConfigurationCallback($callback)
|
||||
{
|
||||
$this->configurationCallbacks[] = $callback;
|
||||
}
|
||||
|
||||
public function addParsingCallback($callback)
|
||||
{
|
||||
$this->parsingCallbacks[] = $callback;
|
||||
}
|
||||
|
||||
public function addRenderingCallback($callback)
|
||||
{
|
||||
$this->renderingCallbacks[] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse text.
|
||||
*
|
||||
@@ -58,8 +79,13 @@ class Formatter
|
||||
{
|
||||
$parser = $this->getParser($context);
|
||||
|
||||
// Deprecated in beta 15, remove in beta 16
|
||||
$this->events->dispatch(new Parsing($parser, $context, $text));
|
||||
|
||||
foreach ($this->parsingCallbacks as $callback) {
|
||||
$text = $callback($parser, $context, $text);
|
||||
}
|
||||
|
||||
return $parser->parse($text);
|
||||
}
|
||||
|
||||
@@ -75,8 +101,13 @@ class Formatter
|
||||
{
|
||||
$renderer = $this->getRenderer();
|
||||
|
||||
// Deprecated in beta 15, remove in beta 16
|
||||
$this->events->dispatch(new Rendering($renderer, $context, $xml, $request));
|
||||
|
||||
foreach ($this->renderingCallbacks as $callback) {
|
||||
$xml = $callback($renderer, $context, $xml, $request);
|
||||
}
|
||||
|
||||
return $renderer->render($xml);
|
||||
}
|
||||
|
||||
@@ -122,8 +153,13 @@ class Formatter
|
||||
$configurator->Autolink;
|
||||
$configurator->tags->onDuplicate('replace');
|
||||
|
||||
// Deprecated in beta 15, remove in beta 16
|
||||
$this->events->dispatch(new Configuring($configurator));
|
||||
|
||||
foreach ($this->configurationCallbacks as $callback) {
|
||||
$callback($configurator);
|
||||
}
|
||||
|
||||
$this->configureExternalLinks($configurator);
|
||||
|
||||
return $configurator;
|
||||
|
@@ -11,6 +11,7 @@ namespace Flarum\Forum\Content;
|
||||
|
||||
use Flarum\Api\Client;
|
||||
use Flarum\Api\Controller\ListDiscussionsController;
|
||||
use Flarum\Api\Controller\SearchDiscussionsController;
|
||||
use Flarum\Frontend\Document;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
@@ -114,6 +115,6 @@ class Index
|
||||
*/
|
||||
private function getApiDocument(User $actor, array $params)
|
||||
{
|
||||
return json_decode($this->api->send(ListDiscussionsController::class, $actor, $params)->getBody());
|
||||
return json_decode($this->api->send(($params['filter']['q'] ? SearchDiscussionsController::class : ListDiscussionsController::class), $actor, $params)->getBody());
|
||||
}
|
||||
}
|
||||
|
@@ -64,8 +64,9 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
HttpMiddleware\StartSession::class,
|
||||
HttpMiddleware\RememberFromCookie::class,
|
||||
HttpMiddleware\AuthenticateWithSession::class,
|
||||
HttpMiddleware\CheckCsrfToken::class,
|
||||
HttpMiddleware\SetLocale::class,
|
||||
'flarum.forum.route_resolver',
|
||||
HttpMiddleware\CheckCsrfToken::class,
|
||||
HttpMiddleware\ShareErrorsFromSession::class
|
||||
];
|
||||
});
|
||||
@@ -78,6 +79,10 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
);
|
||||
});
|
||||
|
||||
$this->app->bind('flarum.forum.route_resolver', function () {
|
||||
return new HttpMiddleware\ResolveRoute($this->app->make('flarum.forum.routes'));
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.forum.handler', function () {
|
||||
$pipe = new MiddlewarePipe;
|
||||
|
||||
@@ -85,7 +90,7 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
$pipe->pipe($this->app->make($middleware));
|
||||
}
|
||||
|
||||
$pipe->pipe(new HttpMiddleware\DispatchRoute($this->app->make('flarum.forum.routes')));
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
|
||||
return $pipe;
|
||||
});
|
||||
@@ -198,8 +203,8 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
$factory = $this->app->make(RouteHandlerFactory::class);
|
||||
$defaultRoute = $this->app->make('flarum.settings')->get('default_route');
|
||||
|
||||
if (isset($routes->getRouteData()[0]['GET'][$defaultRoute])) {
|
||||
$toDefaultController = $routes->getRouteData()[0]['GET'][$defaultRoute];
|
||||
if (isset($routes->getRouteData()[0]['GET'][$defaultRoute]['handler'])) {
|
||||
$toDefaultController = $routes->getRouteData()[0]['GET'][$defaultRoute]['handler'];
|
||||
} else {
|
||||
$toDefaultController = $factory->toForum(Content\Index::class);
|
||||
}
|
||||
|
@@ -18,6 +18,16 @@ use Symfony\Component\Translation\TranslatorInterface;
|
||||
|
||||
abstract class AbstractValidator
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $configuration = [];
|
||||
|
||||
public function addConfiguration($callable)
|
||||
{
|
||||
$this->configuration[] = $callable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
@@ -92,10 +102,17 @@ abstract class AbstractValidator
|
||||
|
||||
$validator = $this->validator->make($attributes, $rules, $this->getMessages());
|
||||
|
||||
/**
|
||||
* @deprecated in beta 15, removed in beta 16.
|
||||
*/
|
||||
$this->events->dispatch(
|
||||
new Validating($this, $validator)
|
||||
);
|
||||
|
||||
foreach ($this->configuration as $callable) {
|
||||
$callable($this, $validator);
|
||||
}
|
||||
|
||||
return $validator;
|
||||
}
|
||||
}
|
||||
|
36
src/Foundation/ContainerUtil.php
Normal file
36
src/Foundation/ContainerUtil.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Foundation;
|
||||
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class ContainerUtil
|
||||
{
|
||||
/**
|
||||
* Wraps a callback so that string-based invokable classes get resolved only when actually used.
|
||||
*
|
||||
* @internal Backwards compatability not guaranteed.
|
||||
*
|
||||
* @param callable|string $callback: A callable, or a ::class attribute of an invokable class
|
||||
* @param Container $container
|
||||
*/
|
||||
public static function wrapCallback($callback, Container $container)
|
||||
{
|
||||
if (is_string($callback)) {
|
||||
$callback = function () use ($container, $callback) {
|
||||
$callback = $container->make($callback);
|
||||
|
||||
return call_user_func_array($callback, func_get_args());
|
||||
};
|
||||
}
|
||||
|
||||
return $callback;
|
||||
}
|
||||
}
|
@@ -13,6 +13,7 @@ use Flarum\Foundation\AbstractValidator;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
/**
|
||||
* @deprecated in Beta 15, remove in beta 16. Use the Validator extender instead.
|
||||
* The `Validating` event is called when a validator instance for a
|
||||
* model is being built. This event can be used to add custom rules/extensions
|
||||
* to the validator for when validation takes place.
|
||||
|
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace Flarum\Foundation;
|
||||
|
||||
use Flarum\Http\Middleware\DispatchRoute;
|
||||
use Flarum\Http\Middleware as HttpMiddleware;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
@@ -85,8 +85,9 @@ class InstalledApp implements AppInterface
|
||||
$pipe = new MiddlewarePipe;
|
||||
$pipe->pipe(new BasePath($this->basePath()));
|
||||
$pipe->pipe(
|
||||
new DispatchRoute($this->container->make('flarum.update.routes'))
|
||||
new HttpMiddleware\ResolveRoute($this->container->make('flarum.update.routes'))
|
||||
);
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
|
||||
return $pipe;
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ class HttpServiceProvider extends AbstractServiceProvider
|
||||
public function register()
|
||||
{
|
||||
$this->app->singleton('flarum.http.csrfExemptPaths', function () {
|
||||
return ['/api/token'];
|
||||
return ['token'];
|
||||
});
|
||||
|
||||
$this->app->bind(Middleware\CheckCsrfToken::class, function ($app) {
|
||||
|
@@ -28,7 +28,10 @@ class CheckCsrfToken implements Middleware
|
||||
{
|
||||
$path = $request->getAttribute('originalUri')->getPath();
|
||||
foreach ($this->exemptRoutes as $exemptRoute) {
|
||||
if (fnmatch($exemptRoute, $path)) {
|
||||
/**
|
||||
* @deprecated path match should be removed in beta 16, only route name match should be supported.
|
||||
*/
|
||||
if ($exemptRoute === $request->getAttribute('routeName') || fnmatch($exemptRoute, $path)) {
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
|
29
src/Http/Middleware/ExecuteRoute.php
Normal file
29
src/Http/Middleware/ExecuteRoute.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Http\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface as Middleware;
|
||||
use Psr\Http\Server\RequestHandlerInterface as Handler;
|
||||
|
||||
class ExecuteRoute implements Middleware
|
||||
{
|
||||
/**
|
||||
* Executes the route handler resolved in ResolveRoute.
|
||||
*/
|
||||
public function process(Request $request, Handler $handler): Response
|
||||
{
|
||||
$handler = $request->getAttribute('routeHandler');
|
||||
$parameters = $request->getAttribute('routeParameters');
|
||||
|
||||
return $handler($request, $parameters);
|
||||
}
|
||||
}
|
@@ -18,7 +18,7 @@ use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface as Middleware;
|
||||
use Psr\Http\Server\RequestHandlerInterface as Handler;
|
||||
|
||||
class DispatchRoute implements Middleware
|
||||
class ResolveRoute implements Middleware
|
||||
{
|
||||
/**
|
||||
* @var RouteCollection
|
||||
@@ -41,7 +41,7 @@ class DispatchRoute implements Middleware
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the given request to our route collection.
|
||||
* Resolve the given request from our route collection.
|
||||
*
|
||||
* @throws MethodNotAllowedException
|
||||
* @throws RouteNotFoundException
|
||||
@@ -59,10 +59,12 @@ class DispatchRoute implements Middleware
|
||||
case Dispatcher::METHOD_NOT_ALLOWED:
|
||||
throw new MethodNotAllowedException($method);
|
||||
case Dispatcher::FOUND:
|
||||
$handler = $routeInfo[1];
|
||||
$parameters = $routeInfo[2];
|
||||
$request = $request
|
||||
->withAttribute('routeName', $routeInfo[1]['name'])
|
||||
->withAttribute('routeHandler', $routeInfo[1]['handler'])
|
||||
->withAttribute('routeParameters', $routeInfo[2]);
|
||||
|
||||
return $handler($request, $parameters);
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
|
57
src/Http/RequestUtil.php
Normal file
57
src/Http/RequestUtil.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Http;
|
||||
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Session\Session;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class RequestUtil
|
||||
{
|
||||
public static function getActor(Request $request): User
|
||||
{
|
||||
return $request->getAttribute('actor');
|
||||
}
|
||||
|
||||
public function withActor(Request $request, User $actor): Request
|
||||
{
|
||||
return $request->withAttribute('actor', $actor);
|
||||
}
|
||||
|
||||
public function getSession(Request $request): Session
|
||||
{
|
||||
return $request->getAttribute('session');
|
||||
}
|
||||
|
||||
public function withSession(Request $request, Session $session): Request
|
||||
{
|
||||
return $request->withAttribute('session', $session);
|
||||
}
|
||||
|
||||
public function getLocale(Request $request): string
|
||||
{
|
||||
return $request->getAttribute('bypassCsrfToken');
|
||||
}
|
||||
|
||||
public function withLocale(Request $request, string $locale): Request
|
||||
{
|
||||
return $request->withAttribute('locale', $locale);
|
||||
}
|
||||
|
||||
public function getRouteName(Request $request): string
|
||||
{
|
||||
return $request->getAttribute('routeName');
|
||||
}
|
||||
|
||||
public function withRouteName(Request $request, string $routeName): Request
|
||||
{
|
||||
return $request->withAttribute('routeName', $routeName);
|
||||
}
|
||||
}
|
@@ -66,7 +66,7 @@ class RouteCollection
|
||||
$routeDatas = $this->routeParser->parse($path);
|
||||
|
||||
foreach ($routeDatas as $routeData) {
|
||||
$this->dataGenerator->addRoute($method, $routeData, $handler);
|
||||
$this->dataGenerator->addRoute($method, $routeData, ['name' => $name, 'handler' => $handler]);
|
||||
}
|
||||
|
||||
$this->reverse[$name] = $routeDatas;
|
||||
|
@@ -13,9 +13,7 @@ use Flarum\Foundation\AppInterface;
|
||||
use Flarum\Foundation\ErrorHandling\Registry;
|
||||
use Flarum\Foundation\ErrorHandling\Reporter;
|
||||
use Flarum\Foundation\ErrorHandling\WhoopsFormatter;
|
||||
use Flarum\Http\Middleware\DispatchRoute;
|
||||
use Flarum\Http\Middleware\HandleErrors;
|
||||
use Flarum\Http\Middleware\StartSession;
|
||||
use Flarum\Http\Middleware as HttpMiddleware;
|
||||
use Flarum\Install\Console\InstallCommand;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Laminas\Stratigility\MiddlewarePipe;
|
||||
@@ -38,15 +36,16 @@ class Installer implements AppInterface
|
||||
public function getRequestHandler()
|
||||
{
|
||||
$pipe = new MiddlewarePipe;
|
||||
$pipe->pipe(new HandleErrors(
|
||||
$pipe->pipe(new HttpMiddleware\HandleErrors(
|
||||
$this->container->make(Registry::class),
|
||||
$this->container->make(WhoopsFormatter::class),
|
||||
$this->container->tagged(Reporter::class)
|
||||
));
|
||||
$pipe->pipe($this->container->make(StartSession::class));
|
||||
$pipe->pipe($this->container->make(HttpMiddleware\StartSession::class));
|
||||
$pipe->pipe(
|
||||
new DispatchRoute($this->container->make('flarum.install.routes'))
|
||||
new HttpMiddleware\ResolveRoute($this->container->make('flarum.install.routes'))
|
||||
);
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
|
||||
return $pipe;
|
||||
}
|
||||
|
@@ -53,7 +53,7 @@ class WritablePaths implements PrerequisiteInterface
|
||||
})->map(function ($path, $index) {
|
||||
return [
|
||||
'message' => 'The '.$this->getAbsolutePath($path).' directory is not writable.',
|
||||
'detail' => 'Please chmod this directory'.(in_array($index, $this->wildcards) ? ' and its contents' : '').' to 0775.'
|
||||
'detail' => 'Please make sure your web server/PHP user has write access to this directory'.(in_array($index, $this->wildcards) ? ' and its contents' : '').'. Read the <a href="https://docs.flarum.org/install.html#folder-ownership">installation documentation</a> for a detailed explanation and steps to resolve this error.'
|
||||
];
|
||||
});
|
||||
}
|
||||
|
50
src/Notification/Driver/AlertNotificationDriver.php
Normal file
50
src/Notification/Driver/AlertNotificationDriver.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Notification\Driver;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use Flarum\Notification\Job\SendNotificationsJob;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Queue\Queue;
|
||||
|
||||
class AlertNotificationDriver implements NotificationDriverInterface
|
||||
{
|
||||
/**
|
||||
* @var Queue
|
||||
*/
|
||||
private $queue;
|
||||
|
||||
public function __construct(Queue $queue)
|
||||
{
|
||||
$this->queue = $queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function send(BlueprintInterface $blueprint, array $users): void
|
||||
{
|
||||
if (count($users)) {
|
||||
$this->queue->push(new SendNotificationsJob($blueprint, $users));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function registerType(string $blueprintClass, array $driversEnabledByDefault): void
|
||||
{
|
||||
User::addPreference(
|
||||
User::getNotificationPreferenceKey($blueprintClass::getType(), 'alert'),
|
||||
'boolval',
|
||||
in_array('alert', $driversEnabledByDefault)
|
||||
);
|
||||
}
|
||||
}
|
69
src/Notification/Driver/EmailNotificationDriver.php
Normal file
69
src/Notification/Driver/EmailNotificationDriver.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Notification\Driver;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use Flarum\Notification\Job\SendEmailNotificationJob;
|
||||
use Flarum\Notification\MailableInterface;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Queue\Queue;
|
||||
use ReflectionClass;
|
||||
|
||||
class EmailNotificationDriver implements NotificationDriverInterface
|
||||
{
|
||||
/**
|
||||
* @var Queue
|
||||
*/
|
||||
private $queue;
|
||||
|
||||
public function __construct(Queue $queue)
|
||||
{
|
||||
$this->queue = $queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function send(BlueprintInterface $blueprint, array $users): void
|
||||
{
|
||||
if ($blueprint instanceof MailableInterface) {
|
||||
$this->mailNotifications($blueprint, $users);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mail a notification to a list of users.
|
||||
*
|
||||
* @param MailableInterface $blueprint
|
||||
* @param User[] $recipients
|
||||
*/
|
||||
protected function mailNotifications(MailableInterface $blueprint, array $recipients)
|
||||
{
|
||||
foreach ($recipients as $user) {
|
||||
if ($user->shouldEmail($blueprint::getType())) {
|
||||
$this->queue->push(new SendEmailNotificationJob($blueprint, $user));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function registerType(string $blueprintClass, array $driversEnabledByDefault): void
|
||||
{
|
||||
if ((new ReflectionClass($blueprintClass))->implementsInterface(MailableInterface::class)) {
|
||||
User::addPreference(
|
||||
User::getNotificationPreferenceKey($blueprintClass::getType(), 'email'),
|
||||
'boolval',
|
||||
in_array('email', $driversEnabledByDefault)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
34
src/Notification/Driver/NotificationDriverInterface.php
Normal file
34
src/Notification/Driver/NotificationDriverInterface.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Notification\Driver;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use Flarum\User\User;
|
||||
|
||||
interface NotificationDriverInterface
|
||||
{
|
||||
/**
|
||||
* Conditionally sends a notification to users, generally using a queue.
|
||||
*
|
||||
* @param BlueprintInterface $blueprint
|
||||
* @param User[] $users
|
||||
* @return void
|
||||
*/
|
||||
public function send(BlueprintInterface $blueprint, array $users): void;
|
||||
|
||||
/**
|
||||
* Logic for registering a notification type, generally used for adding a user preference.
|
||||
*
|
||||
* @param string $blueprintClass
|
||||
* @param array $driversEnabledByDefault
|
||||
* @return void
|
||||
*/
|
||||
public function registerType(string $blueprintClass, array $driversEnabledByDefault): void;
|
||||
}
|
@@ -11,6 +11,9 @@ namespace Flarum\Notification\Event;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
|
||||
/**
|
||||
* @deprecated in beta 15, removed in beta 16
|
||||
*/
|
||||
class Sending
|
||||
{
|
||||
/**
|
||||
|
@@ -10,7 +10,6 @@
|
||||
namespace Flarum\Notification\Job;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use Flarum\Notification\Event\Sending;
|
||||
use Flarum\Notification\Notification;
|
||||
use Flarum\Queue\AbstractJob;
|
||||
use Flarum\User\User;
|
||||
@@ -35,8 +34,6 @@ class SendNotificationsJob extends AbstractJob
|
||||
|
||||
public function handle()
|
||||
{
|
||||
event(new Sending($this->blueprint, $this->recipients));
|
||||
|
||||
Notification::notify($this->recipients, $this->blueprint);
|
||||
}
|
||||
}
|
||||
|
@@ -12,51 +12,76 @@ namespace Flarum\Notification;
|
||||
use Flarum\Event\ConfigureNotificationTypes;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Notification\Blueprint\DiscussionRenamedBlueprint;
|
||||
use Flarum\User\User;
|
||||
use ReflectionClass;
|
||||
|
||||
class NotificationServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->app->singleton('flarum.notification.drivers', function () {
|
||||
return [
|
||||
'alert' => Driver\AlertNotificationDriver::class,
|
||||
'email' => Driver\EmailNotificationDriver::class,
|
||||
];
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.notification.blueprints', function () {
|
||||
return [
|
||||
DiscussionRenamedBlueprint::class => ['alert']
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->registerNotificationTypes();
|
||||
$this->setNotificationDrivers();
|
||||
$this->setNotificationTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register notification drivers.
|
||||
*/
|
||||
protected function setNotificationDrivers()
|
||||
{
|
||||
foreach ($this->app->make('flarum.notification.drivers') as $driverName => $driver) {
|
||||
NotificationSyncer::addNotificationDriver($driverName, $this->app->make($driver));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register notification types.
|
||||
*/
|
||||
public function registerNotificationTypes()
|
||||
protected function setNotificationTypes()
|
||||
{
|
||||
$blueprints = [
|
||||
DiscussionRenamedBlueprint::class => ['alert']
|
||||
];
|
||||
$blueprints = $this->app->make('flarum.notification.blueprints');
|
||||
|
||||
// Deprecated in beta 15, remove in beta 16
|
||||
$this->app->make('events')->dispatch(
|
||||
new ConfigureNotificationTypes($blueprints)
|
||||
);
|
||||
|
||||
foreach ($blueprints as $blueprint => $enabled) {
|
||||
Notification::setSubjectModel(
|
||||
$type = $blueprint::getType(),
|
||||
$blueprint::getSubjectModel()
|
||||
);
|
||||
foreach ($blueprints as $blueprint => $driversEnabledByDefault) {
|
||||
$this->addType($blueprint, $driversEnabledByDefault);
|
||||
}
|
||||
}
|
||||
|
||||
User::addPreference(
|
||||
User::getNotificationPreferenceKey($type, 'alert'),
|
||||
'boolval',
|
||||
in_array('alert', $enabled)
|
||||
);
|
||||
protected function addType(string $blueprint, array $driversEnabledByDefault)
|
||||
{
|
||||
Notification::setSubjectModel(
|
||||
$type = $blueprint::getType(),
|
||||
$blueprint::getSubjectModel()
|
||||
);
|
||||
|
||||
if ((new ReflectionClass($blueprint))->implementsInterface(MailableInterface::class)) {
|
||||
User::addPreference(
|
||||
User::getNotificationPreferenceKey($type, 'email'),
|
||||
'boolval',
|
||||
in_array('email', $enabled)
|
||||
);
|
||||
}
|
||||
foreach (NotificationSyncer::getNotificationDrivers() as $driverName => $driver) {
|
||||
$driver->registerType(
|
||||
$blueprint,
|
||||
$driversEnabledByDefault
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,10 +10,9 @@
|
||||
namespace Flarum\Notification;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use Flarum\Notification\Job\SendEmailNotificationJob;
|
||||
use Flarum\Notification\Job\SendNotificationsJob;
|
||||
use Flarum\Notification\Driver\NotificationDriverInterface;
|
||||
use Flarum\Notification\Event\Sending;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Queue\Queue;
|
||||
|
||||
/**
|
||||
* The Notification Syncer commits notification blueprints to the database, and
|
||||
@@ -38,14 +37,11 @@ class NotificationSyncer
|
||||
protected static $sentTo = [];
|
||||
|
||||
/**
|
||||
* @var Queue
|
||||
* A map of notification drivers.
|
||||
*
|
||||
* @var NotificationDriverInterface[]
|
||||
*/
|
||||
protected $queue;
|
||||
|
||||
public function __construct(Queue $queue)
|
||||
{
|
||||
$this->queue = $queue;
|
||||
}
|
||||
protected static $notificationDrivers = [];
|
||||
|
||||
/**
|
||||
* Sync a notification so that it is visible to the specified users, and not
|
||||
@@ -102,12 +98,13 @@ class NotificationSyncer
|
||||
// receiving this notification for the first time (we know because they
|
||||
// didn't have a record in the database). As both operations can be
|
||||
// intensive on resources (database and mail server), we queue them.
|
||||
if (count($newRecipients)) {
|
||||
$this->queue->push(new SendNotificationsJob($blueprint, $newRecipients));
|
||||
foreach (static::getNotificationDrivers() as $driverName => $driver) {
|
||||
$driver->send($blueprint, $newRecipients);
|
||||
}
|
||||
|
||||
if ($blueprint instanceof MailableInterface) {
|
||||
$this->mailNotifications($blueprint, $newRecipients);
|
||||
if (count($newRecipients)) {
|
||||
// Deprecated in beta 15, removed in beta 16
|
||||
event(new Sending($blueprint, $newRecipients));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,21 +147,6 @@ class NotificationSyncer
|
||||
static::$onePerUser = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mail a notification to a list of users.
|
||||
*
|
||||
* @param MailableInterface $blueprint
|
||||
* @param User[] $recipients
|
||||
*/
|
||||
protected function mailNotifications(MailableInterface $blueprint, array $recipients)
|
||||
{
|
||||
foreach ($recipients as $user) {
|
||||
if ($user->shouldEmail($blueprint::getType())) {
|
||||
$this->queue->push(new SendEmailNotificationJob($blueprint, $user));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the deleted status of a list of notification records.
|
||||
*
|
||||
@@ -175,4 +157,23 @@ class NotificationSyncer
|
||||
{
|
||||
Notification::whereIn('id', $ids)->update(['is_deleted' => $isDeleted]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a notification driver to the list.
|
||||
*
|
||||
* @param string $driverName
|
||||
* @param NotificationDriverInterface $driver
|
||||
*/
|
||||
public static function addNotificationDriver(string $driverName, NotificationDriverInterface $driver)
|
||||
{
|
||||
static::$notificationDrivers[$driverName] = $driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NotificationDriverInterface[]
|
||||
*/
|
||||
public static function getNotificationDrivers(): array
|
||||
{
|
||||
return static::$notificationDrivers;
|
||||
}
|
||||
}
|
||||
|
@@ -218,7 +218,7 @@ class Post extends AbstractModel
|
||||
* @param string $model The class name of the model for that type.
|
||||
* @return void
|
||||
*/
|
||||
public static function setModel($type, $model)
|
||||
public static function setModel(string $type, string $model)
|
||||
{
|
||||
static::$models[$type] = $model;
|
||||
}
|
||||
|
@@ -21,19 +21,20 @@ class PostServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
CommentPost::setFormatter($this->app->make('flarum.formatter'));
|
||||
|
||||
$this->registerPostTypes();
|
||||
$this->setPostTypes();
|
||||
|
||||
$events = $this->app->make('events');
|
||||
$events->subscribe(PostPolicy::class);
|
||||
}
|
||||
|
||||
public function registerPostTypes()
|
||||
protected function setPostTypes()
|
||||
{
|
||||
$models = [
|
||||
CommentPost::class,
|
||||
DiscussionRenamedPost::class
|
||||
];
|
||||
|
||||
// Deprecated in beta 15, remove in beta 16.
|
||||
$this->app->make('events')->dispatch(
|
||||
new ConfigurePostTypes($models)
|
||||
);
|
||||
|
@@ -44,13 +44,13 @@ class UpdateServiceProvider extends AbstractServiceProvider
|
||||
$route = $this->app->make(RouteHandlerFactory::class);
|
||||
|
||||
$routes->get(
|
||||
'/',
|
||||
'/{path:.*}',
|
||||
'index',
|
||||
$route->toController(Controller\IndexController::class)
|
||||
);
|
||||
|
||||
$routes->post(
|
||||
'/',
|
||||
'/{path:.*}',
|
||||
'update',
|
||||
$route->toController(Controller\UpdateController::class)
|
||||
);
|
||||
|
@@ -11,6 +11,7 @@ namespace Flarum\User;
|
||||
|
||||
use Flarum\Event\ConfigureUserPreferences;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\User\DisplayName\DriverInterface;
|
||||
use Flarum\User\DisplayName\UsernameDriver;
|
||||
@@ -77,11 +78,7 @@ class UserServiceProvider extends AbstractServiceProvider
|
||||
public function boot()
|
||||
{
|
||||
foreach ($this->app->make('flarum.user.group_processors') as $callback) {
|
||||
if (is_string($callback)) {
|
||||
$callback = $this->app->make($callback);
|
||||
}
|
||||
|
||||
User::addGroupProcessor($callback);
|
||||
User::addGroupProcessor(ContainerUtil::wrapCallback($callback, $this->app));
|
||||
}
|
||||
|
||||
User::setHasher($this->app->make('hash'));
|
||||
|
@@ -56,111 +56,19 @@ class ListTest extends TestCase
|
||||
$this->assertEquals(1, count($data['data']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function can_search_for_author()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => 'author:normal foo'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
// /**
|
||||
// * @test
|
||||
// */
|
||||
// public function can_search_for_author()
|
||||
// {
|
||||
// $response = $this->send(
|
||||
// $this->request('GET', '/api/search/discussions')
|
||||
// ->withQueryParams([
|
||||
// 'filter' => ['q' => 'author:normal foo'],
|
||||
// 'include' => 'mostRelevantPost',
|
||||
// ])
|
||||
// );
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function can_search_for_word_in_post()
|
||||
{
|
||||
$this->database()->table('discussions')->insert([
|
||||
['id' => 2, 'title' => 'lightsail in title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'comment_count' => 1],
|
||||
['id' => 3, 'title' => 'not in title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'comment_count' => 1],
|
||||
]);
|
||||
|
||||
$this->database()->table('posts')->insert([
|
||||
['id' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>not in text</p></t>'],
|
||||
['id' => 3, 'discussion_id' => 3, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>lightsail in text</p></t>'],
|
||||
]);
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => 'lightsail'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
$ids = array_map(function ($row) {
|
||||
return $row['id'];
|
||||
}, $data['data']);
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['3'], $ids, 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function ignores_non_word_characters_when_searching()
|
||||
{
|
||||
$this->database()->table('discussions')->insert([
|
||||
['id' => 2, 'title' => 'lightsail in title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'comment_count' => 1],
|
||||
['id' => 3, 'title' => 'not in title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'comment_count' => 1],
|
||||
]);
|
||||
|
||||
$this->database()->table('posts')->insert([
|
||||
['id' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>not in text</p></t>'],
|
||||
['id' => 3, 'discussion_id' => 3, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>lightsail in text</p></t>'],
|
||||
]);
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => 'lightsail+'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
$ids = array_map(function ($row) {
|
||||
return $row['id'];
|
||||
}, $data['data']);
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['3'], $ids, 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function search_for_special_characters_gives_empty_result()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => '*'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
$this->assertEquals([], $data['data']);
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => '@'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
$this->assertEquals([], $data['data']);
|
||||
}
|
||||
// $this->assertEquals(200, $response->getStatusCode());
|
||||
// }
|
||||
}
|
||||
|
166
tests/integration/api/discussions/SearchTest.php
Normal file
166
tests/integration/api/discussions/SearchTest.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Tests\integration\api\discussions;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Tests\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Tests\integration\TestCase;
|
||||
|
||||
class SearchTest extends TestCase
|
||||
{
|
||||
use RetrievesAuthorizedUsers;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->prepareDatabase([
|
||||
'discussions' => [
|
||||
['id' => 1, 'title' => __CLASS__, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => 1, 'comment_count' => 1],
|
||||
],
|
||||
'posts' => [
|
||||
['id' => 1, 'discussion_id' => 1, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>foo bar</p></t>'],
|
||||
],
|
||||
'users' => [
|
||||
$this->normalUser(),
|
||||
],
|
||||
'groups' => [
|
||||
$this->memberGroup(),
|
||||
$this->guestGroup(),
|
||||
],
|
||||
'group_permission' => [
|
||||
['permission' => 'viewDiscussions', 'group_id' => 2],
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function shows_index_for_guest()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/search/discussions')
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
$this->assertEquals(1, count($data['data']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function can_search_for_author()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/search/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => 'author:normal foo'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function can_search_for_word_in_post()
|
||||
{
|
||||
$this->database()->table('discussions')->insert([
|
||||
['id' => 2, 'title' => 'lightsail in title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'comment_count' => 1],
|
||||
['id' => 3, 'title' => 'not in title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'comment_count' => 1],
|
||||
]);
|
||||
|
||||
$this->database()->table('posts')->insert([
|
||||
['id' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>not in text</p></t>'],
|
||||
['id' => 3, 'discussion_id' => 3, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>lightsail in text</p></t>'],
|
||||
]);
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/search/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => 'lightsail'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
$ids = array_map(function ($row) {
|
||||
return $row['id'];
|
||||
}, $data['data']);
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['3'], $ids, 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function ignores_non_word_characters_when_searching()
|
||||
{
|
||||
$this->database()->table('discussions')->insert([
|
||||
['id' => 2, 'title' => 'lightsail in title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'comment_count' => 1],
|
||||
['id' => 3, 'title' => 'not in title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'comment_count' => 1],
|
||||
]);
|
||||
|
||||
$this->database()->table('posts')->insert([
|
||||
['id' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>not in text</p></t>'],
|
||||
['id' => 3, 'discussion_id' => 3, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>lightsail in text</p></t>'],
|
||||
]);
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/search/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => 'lightsail+'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
$ids = array_map(function ($row) {
|
||||
return $row['id'];
|
||||
}, $data['data']);
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['3'], $ids, 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function search_for_special_characters_gives_empty_result()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/search/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => '*'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
$this->assertEquals([], $data['data']);
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/search/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => '@'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
$this->assertEquals([], $data['data']);
|
||||
}
|
||||
}
|
83
tests/integration/api/users/SearchTest.php
Normal file
83
tests/integration/api/users/SearchTest.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Tests\integration\api\users;
|
||||
|
||||
use Flarum\Group\Permission;
|
||||
use Flarum\Tests\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Tests\integration\TestCase;
|
||||
|
||||
class SearchTest extends TestCase
|
||||
{
|
||||
use RetrievesAuthorizedUsers;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->prepareDatabase([
|
||||
'users' => [
|
||||
$this->adminUser(),
|
||||
],
|
||||
'groups' => [
|
||||
$this->adminGroup(),
|
||||
$this->guestGroup(),
|
||||
],
|
||||
'group_permission' => [],
|
||||
'group_user' => [
|
||||
['user_id' => 1, 'group_id' => 1],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function disallows_index_for_guest()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/search/users')
|
||||
);
|
||||
|
||||
$this->assertEquals(403, $response->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function shows_index_for_guest_when_they_have_permission()
|
||||
{
|
||||
Permission::unguarded(function () {
|
||||
Permission::create([
|
||||
'permission' => 'viewUserList',
|
||||
'group_id' => 2,
|
||||
]);
|
||||
});
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/search/users')
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function shows_index_for_admin()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/search/users', [
|
||||
'authenticatedAs' => 1,
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
}
|
||||
}
|
@@ -50,6 +50,7 @@ class CsrfTest extends TestCase
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @deprecated
|
||||
*/
|
||||
public function create_user_post_doesnt_need_csrf_token_if_whitelisted()
|
||||
{
|
||||
@@ -82,19 +83,37 @@ class CsrfTest extends TestCase
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function post_to_unknown_route_will_cause_400_error_without_csrf_override()
|
||||
public function create_user_post_doesnt_need_csrf_token_if_whitelisted_via_routename()
|
||||
{
|
||||
$this->extend(
|
||||
(new Extend\Csrf)
|
||||
->exemptRoute('users.create')
|
||||
);
|
||||
|
||||
$this->prepDb();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/fake/route/i/made/up')
|
||||
$this->request('POST', '/api/users', [
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => $this->testUser
|
||||
]
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(400, $response->getStatusCode());
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
|
||||
$user = User::where('username', $this->testUser['username'])->firstOrFail();
|
||||
|
||||
$this->assertEquals(0, $user->is_email_confirmed);
|
||||
$this->assertEquals($this->testUser['username'], $user->username);
|
||||
$this->assertEquals($this->testUser['email'], $user->email);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @deprecated
|
||||
*/
|
||||
public function csrf_matches_wildcards_properly()
|
||||
{
|
||||
|
135
tests/integration/extenders/FilterTest.php
Normal file
135
tests/integration/extenders/FilterTest.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Tests\integration\extenders;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Extend;
|
||||
use Flarum\Filter\FilterInterface;
|
||||
use Flarum\Filter\WrappedFilter;
|
||||
use Flarum\Tests\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Tests\integration\TestCase;
|
||||
|
||||
class FilterTest extends TestCase
|
||||
{
|
||||
use RetrievesAuthorizedUsers;
|
||||
|
||||
public function prepDb()
|
||||
{
|
||||
$this->prepareDatabase([
|
||||
'discussions' => [
|
||||
['id' => 1, 'title' => 'DISCUSSION 1', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => 1, 'comment_count' => 1],
|
||||
['id' => 2, 'title' => 'DISCUSSION 2', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => 2, 'comment_count' => 1],
|
||||
],
|
||||
'posts' => [
|
||||
['id' => 1, 'discussion_id' => 1, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>foo bar</p></t>'],
|
||||
['id' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>foo bar not the same</p></t>'],
|
||||
],
|
||||
'users' => [
|
||||
$this->adminUser(),
|
||||
$this->normalUser(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function filterDiscussions($filters, $limit = null)
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions', [
|
||||
'authenticatedAs' => 1,
|
||||
])->withQueryParams([
|
||||
'filter' => $filters,
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
return json_decode($response->getBody()->getContents(), true)['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function works_as_expected_with_no_modifications()
|
||||
{
|
||||
$this->prepDb();
|
||||
|
||||
$searchForAll = json_encode($this->filterDiscussions([], 5));
|
||||
$this->assertContains('DISCUSSION 1', $searchForAll);
|
||||
$this->assertContains('DISCUSSION 2', $searchForAll);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_filter_gambit_has_effect_if_added()
|
||||
{
|
||||
$this->extend((new Extend\Filter(Discussion::class))->addFilter(NoResultFilter::class));
|
||||
|
||||
$this->prepDb();
|
||||
|
||||
$withResultSearch = json_encode($this->filterDiscussions(['noResult' => 0], 5));
|
||||
$this->assertContains('DISCUSSION 1', $withResultSearch);
|
||||
$this->assertContains('DISCUSSION 2', $withResultSearch);
|
||||
$this->assertEquals([], $this->filterDiscussions(['noResult' => 1], 5));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function filter_mutator_has_effect_if_added()
|
||||
{
|
||||
$this->extend((new Extend\Filter(Discussion::class))->addFilterMutator(function ($query, $actor, $filters, $sort) {
|
||||
$query->getQuery()->whereRaw('1=0');
|
||||
}));
|
||||
|
||||
$this->prepDb();
|
||||
|
||||
$this->assertEquals([], $this->filterDiscussions([], 5));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function filter_mutator_has_effect_if_added_with_invokable_class()
|
||||
{
|
||||
$this->extend((new Extend\Filter(Discussion::class))->addFilterMutator(CustomFilterMutator::class));
|
||||
|
||||
$this->prepDb();
|
||||
|
||||
$this->assertEquals([], $this->filterDiscussions([], 5));
|
||||
}
|
||||
}
|
||||
|
||||
class NoResultFilter implements FilterInterface
|
||||
{
|
||||
public function getKey(): string
|
||||
{
|
||||
return 'noResult';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(WrappedFilter $wrappedFilter, $filterValue)
|
||||
{
|
||||
if ($filterValue) {
|
||||
$wrappedFilter->getQuery()
|
||||
->whereRaw('0=1');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CustomFilterMutator
|
||||
{
|
||||
public function __invoke($query, $actor, $filters, $sort)
|
||||
{
|
||||
$query->getQuery()->whereRaw('1=0');
|
||||
}
|
||||
}
|
145
tests/integration/extenders/FormatterTest.php
Normal file
145
tests/integration/extenders/FormatterTest.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Tests\integration\extenders;
|
||||
|
||||
use Flarum\Extend;
|
||||
use Flarum\Formatter\Formatter;
|
||||
use Flarum\Tests\integration\TestCase;
|
||||
|
||||
class FormatterTest extends TestCase
|
||||
{
|
||||
protected function getFormatter()
|
||||
{
|
||||
$formatter = $this->app()->getContainer()->make(Formatter::class);
|
||||
$formatter->flush();
|
||||
|
||||
return $formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_config_doesnt_work_by_default()
|
||||
{
|
||||
$formatter = $this->getFormatter();
|
||||
|
||||
$this->assertEquals('<t>[B]something[/B]</t>', $formatter->parse('[B]something[/B]'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_config_works_if_added_with_closure()
|
||||
{
|
||||
$this->extend((new Extend\Formatter)->configure(function ($config) {
|
||||
$config->BBCodes->addFromRepository('B');
|
||||
}));
|
||||
|
||||
$formatter = $this->getFormatter();
|
||||
|
||||
$this->assertEquals('<b>something</b>', $formatter->render($formatter->parse('[B]something[/B]')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_config_works_if_added_with_invokable_class()
|
||||
{
|
||||
$this->extend((new Extend\Formatter)->configure(InvokableConfig::class));
|
||||
|
||||
$formatter = $this->getFormatter();
|
||||
|
||||
$this->assertEquals('<b>something</b>', $formatter->render($formatter->parse('[B]something[/B]')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_parsing_doesnt_work_by_default()
|
||||
{
|
||||
$this->assertEquals('<t>Text<a></t>', $this->getFormatter()->parse('Text<a>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_parsing_works_if_added_with_closure()
|
||||
{
|
||||
$this->extend((new Extend\Formatter)->parse(function ($parser, $context, $text) {
|
||||
return 'ReplacedText<a>';
|
||||
}));
|
||||
|
||||
$this->assertEquals('<t>ReplacedText<a></t>', $this->getFormatter()->parse('Text<a>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_parsing_works_if_added_with_invokable_class()
|
||||
{
|
||||
$this->extend((new Extend\Formatter)->parse(InvokableParsing::class));
|
||||
|
||||
$this->assertEquals('<t>ReplacedText<a></t>', $this->getFormatter()->parse('Text<a>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_rendering_doesnt_work_by_default()
|
||||
{
|
||||
$this->assertEquals('Text', $this->getFormatter()->render('<p>Text</p>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_rendering_works_if_added_with_closure()
|
||||
{
|
||||
$this->extend((new Extend\Formatter)->render(function ($renderer, $context, $xml, $request) {
|
||||
return '<html>ReplacedText</html>';
|
||||
}));
|
||||
|
||||
$this->assertEquals('ReplacedText', $this->getFormatter()->render('<html>Text</html>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_rendering_works_if_added_with_invokable_class()
|
||||
{
|
||||
$this->extend((new Extend\Formatter)->render(InvokableRendering::class));
|
||||
|
||||
$this->assertEquals('ReplacedText', $this->getFormatter()->render('<html>Text</html>'));
|
||||
}
|
||||
}
|
||||
|
||||
class InvokableConfig
|
||||
{
|
||||
public function __invoke($config)
|
||||
{
|
||||
$config->BBCodes->addFromRepository('B');
|
||||
}
|
||||
}
|
||||
|
||||
class InvokableParsing
|
||||
{
|
||||
public function __invoke($parser, $context, $text)
|
||||
{
|
||||
return 'ReplacedText<a>';
|
||||
}
|
||||
}
|
||||
|
||||
class InvokableRendering
|
||||
{
|
||||
public function __invoke($renderer, $context, $xml, $request)
|
||||
{
|
||||
return '<html>ReplacedText</html>';
|
||||
}
|
||||
}
|
@@ -134,6 +134,23 @@ class ModelTest extends TestCase
|
||||
$this->assertEquals([], $user->customRelation()->get()->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_relationship_can_be_invokable_class()
|
||||
{
|
||||
$this->extend(
|
||||
(new Extend\Model(User::class))
|
||||
->relationship('customRelation', CustomRelationClass::class)
|
||||
);
|
||||
|
||||
$this->prepDB();
|
||||
|
||||
$user = User::find(1);
|
||||
|
||||
$this->assertEquals([], $user->customRelation()->get()->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
@@ -326,7 +343,7 @@ class ModelTest extends TestCase
|
||||
|
||||
$this->app();
|
||||
|
||||
$post = new CustomPost;
|
||||
$post = new ModelTestCustomPost;
|
||||
|
||||
$this->assertEquals(42, $post->answer);
|
||||
|
||||
@@ -416,10 +433,18 @@ class ModelTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
class CustomPost extends AbstractEventPost
|
||||
class ModelTestCustomPost extends AbstractEventPost
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $type = 'customPost';
|
||||
}
|
||||
|
||||
class CustomRelationClass
|
||||
{
|
||||
public function __invoke(User $user)
|
||||
{
|
||||
return $user->hasMany(Discussion::class, 'user_id');
|
||||
}
|
||||
}
|
||||
|
184
tests/integration/extenders/NotificationTest.php
Normal file
184
tests/integration/extenders/NotificationTest.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Tests\integration\extenders;
|
||||
|
||||
use Flarum\Extend;
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use Flarum\Notification\Driver\NotificationDriverInterface;
|
||||
use Flarum\Notification\Notification;
|
||||
use Flarum\Notification\NotificationSyncer;
|
||||
use Flarum\Tests\integration\TestCase;
|
||||
|
||||
class NotificationTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function notification_type_doesnt_exist_by_default()
|
||||
{
|
||||
$this->assertArrayNotHasKey('customNotificationType', Notification::getSubjectModels());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function notification_serializer_doesnt_exist_by_default()
|
||||
{
|
||||
$this->app();
|
||||
|
||||
$this->assertNotContains(
|
||||
'customNotificationTypeSerializer',
|
||||
$this->app->getContainer()->make('flarum.api.notification_serializers')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function notification_driver_doesnt_exist_by_default()
|
||||
{
|
||||
$this->assertArrayNotHasKey('customNotificationDriver', NotificationSyncer::getNotificationDrivers());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function notification_type_exists_if_added()
|
||||
{
|
||||
$this->extend((new Extend\Notification)->type(
|
||||
CustomNotificationType::class,
|
||||
'customNotificationTypeSerializer'
|
||||
));
|
||||
|
||||
$this->app();
|
||||
|
||||
$this->assertArrayHasKey('customNotificationType', Notification::getSubjectModels());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function notification_serializer_exists_if_added()
|
||||
{
|
||||
$this->extend((new Extend\Notification)->type(
|
||||
CustomNotificationType::class,
|
||||
'customNotificationTypeSerializer'
|
||||
));
|
||||
|
||||
$this->app();
|
||||
|
||||
$this->assertContains(
|
||||
'customNotificationTypeSerializer',
|
||||
$this->app->getContainer()->make('flarum.api.notification_serializers')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function notification_driver_exists_if_added()
|
||||
{
|
||||
$this->extend((new Extend\Notification())->driver(
|
||||
'customNotificationDriver',
|
||||
CustomNotificationDriver::class
|
||||
));
|
||||
|
||||
$this->app();
|
||||
|
||||
$this->assertArrayHasKey('customNotificationDriver', NotificationSyncer::getNotificationDrivers());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function notification_driver_enabled_types_exist_if_added()
|
||||
{
|
||||
$this->extend(
|
||||
(new Extend\Notification())
|
||||
->type(CustomNotificationType::class, 'customSerializer')
|
||||
->type(SecondCustomNotificationType::class, 'secondCustomSerializer', ['customDriver'])
|
||||
->type(ThirdCustomNotificationType::class, 'thirdCustomSerializer')
|
||||
->driver('customDriver', CustomNotificationDriver::class, [CustomNotificationType::class])
|
||||
->driver('secondCustomDriver', SecondCustomNotificationDriver::class, [SecondCustomNotificationType::class])
|
||||
);
|
||||
|
||||
$this->app();
|
||||
|
||||
$blueprints = $this->app->getContainer()->make('flarum.notification.blueprints');
|
||||
|
||||
$this->assertContains('customDriver', $blueprints[CustomNotificationType::class]);
|
||||
$this->assertCount(1, $blueprints[CustomNotificationType::class]);
|
||||
$this->assertContains('customDriver', $blueprints[SecondCustomNotificationType::class]);
|
||||
$this->assertContains('secondCustomDriver', $blueprints[SecondCustomNotificationType::class]);
|
||||
$this->assertEmpty($blueprints[ThirdCustomNotificationType::class]);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomNotificationType implements BlueprintInterface
|
||||
{
|
||||
public function getFromUser()
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
public function getSubject()
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
public function getData()
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
public static function getType()
|
||||
{
|
||||
return 'customNotificationType';
|
||||
}
|
||||
|
||||
public static function getSubjectModel()
|
||||
{
|
||||
return 'customNotificationTypeSubjectModel';
|
||||
}
|
||||
}
|
||||
|
||||
class SecondCustomNotificationType extends CustomNotificationType
|
||||
{
|
||||
public static function getType()
|
||||
{
|
||||
return 'secondCustomNotificationType';
|
||||
}
|
||||
}
|
||||
|
||||
class ThirdCustomNotificationType extends CustomNotificationType
|
||||
{
|
||||
public static function getType()
|
||||
{
|
||||
return 'thirdCustomNotificationType';
|
||||
}
|
||||
}
|
||||
|
||||
class CustomNotificationDriver implements NotificationDriverInterface
|
||||
{
|
||||
public function send(BlueprintInterface $blueprint, array $users): void
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
public function registerType(string $blueprintClass, array $driversEnabledByDefault): void
|
||||
{
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
class SecondCustomNotificationDriver extends CustomNotificationDriver
|
||||
{
|
||||
// ...
|
||||
}
|
58
tests/integration/extenders/PostTest.php
Normal file
58
tests/integration/extenders/PostTest.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Tests\integration\extenders;
|
||||
|
||||
use Flarum\Extend;
|
||||
use Flarum\Post\AbstractEventPost;
|
||||
use Flarum\Post\MergeableInterface;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Tests\integration\TestCase;
|
||||
|
||||
class PostTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_post_type_doesnt_exist_by_default()
|
||||
{
|
||||
$this->assertArrayNotHasKey('customPost', Post::getModels());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_post_type_exists_if_added()
|
||||
{
|
||||
$this->extend((new Extend\Post)->type(PostTestCustomPost::class));
|
||||
|
||||
// Needed for extenders to be booted
|
||||
$this->app();
|
||||
|
||||
$this->assertArrayHasKey('customPost', Post::getModels());
|
||||
}
|
||||
}
|
||||
|
||||
class PostTestCustomPost extends AbstractEventPost implements MergeableInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $type = 'customPost';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveAfter(Post $previous = null)
|
||||
{
|
||||
$this->save();
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
119
tests/integration/extenders/ServiceProviderTest.php
Normal file
119
tests/integration/extenders/ServiceProviderTest.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Tests\integration\extenders;
|
||||
|
||||
use Flarum\Extend;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Tests\integration\TestCase;
|
||||
|
||||
class ServiceProviderTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function providers_dont_work_by_default()
|
||||
{
|
||||
$this->app();
|
||||
|
||||
$this->assertIsArray(
|
||||
$this->app->getContainer()->make('flarum.forum.middleware')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function providers_first_register_order_is_correct()
|
||||
{
|
||||
$this->extend(
|
||||
(new Extend\ServiceProvider())
|
||||
->register(CustomServiceProvider::class)
|
||||
);
|
||||
|
||||
$this->app();
|
||||
|
||||
$this->assertEquals(
|
||||
'overriden_by_custom_provider_register',
|
||||
$this->app->getContainer()->make('flarum.forum.middleware')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function providers_second_register_order_is_correct()
|
||||
{
|
||||
$this->extend(
|
||||
(new Extend\ServiceProvider())
|
||||
->register(CustomServiceProvider::class)
|
||||
->register(SecondCustomServiceProvider::class)
|
||||
);
|
||||
|
||||
$this->app();
|
||||
|
||||
$this->assertEquals(
|
||||
'overriden_by_second_custom_provider_register',
|
||||
$this->app->getContainer()->make('flarum.forum.middleware')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function providers_boot_order_is_correct()
|
||||
{
|
||||
$this->extend(
|
||||
(new Extend\ServiceProvider())
|
||||
->register(ThirdCustomProvider::class)
|
||||
->register(CustomServiceProvider::class)
|
||||
->register(SecondCustomServiceProvider::class)
|
||||
);
|
||||
|
||||
$this->app();
|
||||
|
||||
$this->assertEquals(
|
||||
'overriden_by_third_custom_provider_boot',
|
||||
$this->app->getContainer()->make('flarum.forum.middleware')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
// First we override the singleton here.
|
||||
$this->app->extend('flarum.forum.middleware', function () {
|
||||
return 'overriden_by_custom_provider_register';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class SecondCustomServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
// Second we check that the singleton was overriden here.
|
||||
$this->app->extend('flarum.forum.middleware', function ($forumRoutes) {
|
||||
return 'overriden_by_second_custom_provider_register';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class ThirdCustomProvider extends AbstractServiceProvider
|
||||
{
|
||||
public function boot()
|
||||
{
|
||||
// Third we override one last time here, to make sure this is the final result.
|
||||
$this->app->extend('flarum.forum.middleware', function ($forumRoutes) {
|
||||
return 'overriden_by_third_custom_provider_boot';
|
||||
});
|
||||
}
|
||||
}
|
@@ -88,6 +88,19 @@ class UserTest extends TestCase
|
||||
|
||||
$this->assertNotContains('viewUserList', $user->getPermissions());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function processor_can_be_invokable_class()
|
||||
{
|
||||
$this->extend((new Extend\User)->permissionGroups(CustomGroupProcessorClass::class));
|
||||
|
||||
$this->prepDb();
|
||||
$user = User::find(2);
|
||||
|
||||
$this->assertNotContains('viewUserList', $user->getPermissions());
|
||||
}
|
||||
}
|
||||
|
||||
class CustomDisplayNameDriver implements DriverInterface
|
||||
@@ -97,3 +110,13 @@ class CustomDisplayNameDriver implements DriverInterface
|
||||
return $user->email.'$$$suffix';
|
||||
}
|
||||
}
|
||||
|
||||
class CustomGroupProcessorClass
|
||||
{
|
||||
public function __invoke(User $user, array $groupIds)
|
||||
{
|
||||
return array_filter($groupIds, function ($id) {
|
||||
return $id != 3;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
97
tests/integration/extenders/ValidatorTest.php
Normal file
97
tests/integration/extenders/ValidatorTest.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Tests\integration\extenders;
|
||||
|
||||
use Flarum\Extend;
|
||||
use Flarum\Group\GroupValidator;
|
||||
use Flarum\Tests\integration\TestCase;
|
||||
use Flarum\User\UserValidator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class ValidatorTest extends TestCase
|
||||
{
|
||||
private function extendToRequireLongPassword()
|
||||
{
|
||||
$this->extend((new Extend\Validator(UserValidator::class))->configure(function ($flarumValidator, $validator) {
|
||||
$validator->setRules([
|
||||
'password' => [
|
||||
'required',
|
||||
'min:20'
|
||||
]
|
||||
] + $validator->getRules());
|
||||
}));
|
||||
}
|
||||
|
||||
private function extendToRequireLongPasswordViaInvokableClass()
|
||||
{
|
||||
$this->extend((new Extend\Validator(UserValidator::class))->configure(CustomValidatorClass::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_validation_rule_does_not_exist_by_default()
|
||||
{
|
||||
$this->app()->getContainer()->make(UserValidator::class)->assertValid(['password' => 'simplePassword']);
|
||||
|
||||
// If we have gotten this far, no validation exception has been thrown, so the test is succesful.
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_validation_rule_exists_if_added()
|
||||
{
|
||||
$this->extendToRequireLongPassword();
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
|
||||
$this->app()->getContainer()->make(UserValidator::class)->assertValid(['password' => 'simplePassword']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_validation_rule_exists_if_added_via_invokable_class()
|
||||
{
|
||||
$this->extendToRequireLongPasswordViaInvokableClass();
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
|
||||
$this->app()->getContainer()->make(UserValidator::class)->assertValid(['password' => 'simplePassword']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_validation_rule_doesnt_affect_other_validators()
|
||||
{
|
||||
$this->extendToRequireLongPassword();
|
||||
|
||||
$this->app()->getContainer()->make(GroupValidator::class)->assertValid(['password' => 'simplePassword']);
|
||||
|
||||
// If we have gotten this far, no validation exception has been thrown, so the test is succesful.
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomValidatorClass
|
||||
{
|
||||
public function __invoke($flarumValidator, $validator)
|
||||
{
|
||||
$validator->setRules([
|
||||
'password' => [
|
||||
'required',
|
||||
'min:20'
|
||||
]
|
||||
] + $validator->getRules());
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user