mirror of
https://github.com/flarum/framework.git
synced 2024-11-22 14:04:03 +08:00
Slug Driver Support (#2456)
- Support slug drivers for core's sluggable models, easily extends to other models - Add automated testing for affected single-model API routes - Fix nickname selection UI - Serialize slugs as `slug` attribute - Make min search length a constant
This commit is contained in:
parent
ef4bf8128e
commit
4679448300
|
@ -25,10 +25,6 @@ export default class BasicsPage extends Page {
|
||||||
'welcome_message',
|
'welcome_message',
|
||||||
'display_name_driver',
|
'display_name_driver',
|
||||||
];
|
];
|
||||||
this.values = {};
|
|
||||||
|
|
||||||
const settings = app.data.settings;
|
|
||||||
this.fields.forEach((key) => (this.values[key] = Stream(settings[key])));
|
|
||||||
|
|
||||||
this.localeOptions = {};
|
this.localeOptions = {};
|
||||||
const locales = app.data.locales;
|
const locales = app.data.locales;
|
||||||
|
@ -42,8 +38,29 @@ export default class BasicsPage extends Page {
|
||||||
this.displayNameOptions[identifier] = identifier;
|
this.displayNameOptions[identifier] = identifier;
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
|
this.slugDriverOptions = {};
|
||||||
|
Object.keys(app.data.slugDrivers).forEach((model) => {
|
||||||
|
this.fields.push(`slug_driver_${model}`);
|
||||||
|
this.slugDriverOptions[model] = {};
|
||||||
|
|
||||||
|
app.data.slugDrivers[model].forEach((option) => {
|
||||||
|
this.slugDriverOptions[model][option] = option;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.values = {};
|
||||||
|
|
||||||
|
const settings = app.data.settings;
|
||||||
|
this.fields.forEach((key) => (this.values[key] = Stream(settings[key])));
|
||||||
|
|
||||||
if (!this.values.display_name_driver() && displayNameDrivers.includes('username')) this.values.display_name_driver('username');
|
if (!this.values.display_name_driver() && displayNameDrivers.includes('username')) this.values.display_name_driver('username');
|
||||||
|
|
||||||
|
Object.keys(app.data.slugDrivers).forEach((model) => {
|
||||||
|
if (!this.values[`slug_driver_${model}`]() && 'default' in this.slugDriverOptions[model]) {
|
||||||
|
this.values[`slug_driver_${model}`]('default');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (typeof this.values.show_language_selector() !== 'number') this.values.show_language_selector(1);
|
if (typeof this.values.show_language_selector() !== 'number') this.values.show_language_selector(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,20 +149,30 @@ export default class BasicsPage extends Page {
|
||||||
]
|
]
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{Object.keys(this.displayNameOptions).length > 1
|
{Object.keys(this.displayNameOptions).length > 1 ? (
|
||||||
? FieldSet.component(
|
<FieldSet label={app.translator.trans('core.admin.basics.display_name_heading')}>
|
||||||
{
|
<div className="helpText">{app.translator.trans('core.admin.basics.display_name_text')}</div>
|
||||||
label: app.translator.trans('core.admin.basics.display_name_heading'),
|
<Select
|
||||||
},
|
options={this.displayNameOptions}
|
||||||
[
|
value={this.values.display_name_driver()}
|
||||||
<div className="helpText">{app.translator.trans('core.admin.basics.display_name_text')}</div>,
|
onchange={this.values.display_name_driver}
|
||||||
Select.component({
|
></Select>
|
||||||
options: this.displayNameOptions,
|
</FieldSet>
|
||||||
bidi: this.values.display_name_driver,
|
) : (
|
||||||
}),
|
''
|
||||||
]
|
)}
|
||||||
)
|
|
||||||
: ''}
|
{Object.keys(this.slugDriverOptions).map((model) => {
|
||||||
|
const options = this.slugDriverOptions[model];
|
||||||
|
if (Object.keys(options).length > 1) {
|
||||||
|
return (
|
||||||
|
<FieldSet label={app.translator.trans('core.admin.basics.slug_driver_heading', { model })}>
|
||||||
|
<div className="helpText">{app.translator.trans('core.admin.basics.slug_driver_text', { model })}</div>
|
||||||
|
<Select options={options} value={this.values[`slug_driver_${model}`]()} onchange={this.values[`slug_driver_${model}`]}></Select>
|
||||||
|
</FieldSet>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
|
||||||
{Button.component(
|
{Button.component(
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,6 +10,7 @@ export default class User extends Model {}
|
||||||
|
|
||||||
Object.assign(User.prototype, {
|
Object.assign(User.prototype, {
|
||||||
username: Model.attribute('username'),
|
username: Model.attribute('username'),
|
||||||
|
slug: Model.attribute('slug'),
|
||||||
displayName: Model.attribute('displayName'),
|
displayName: Model.attribute('displayName'),
|
||||||
email: Model.attribute('email'),
|
email: Model.attribute('email'),
|
||||||
isEmailConfirmed: Model.attribute('isEmailConfirmed'),
|
isEmailConfirmed: Model.attribute('isEmailConfirmed'),
|
||||||
|
|
|
@ -14,6 +14,7 @@ import DiscussionControls from '../utils/DiscussionControls';
|
||||||
import slidable from '../utils/slidable';
|
import slidable from '../utils/slidable';
|
||||||
import extractText from '../../common/utils/extractText';
|
import extractText from '../../common/utils/extractText';
|
||||||
import classList from '../../common/utils/classList';
|
import classList from '../../common/utils/classList';
|
||||||
|
import DiscussionPage from './DiscussionPage';
|
||||||
|
|
||||||
import { escapeRegExp } from 'lodash-es';
|
import { escapeRegExp } from 'lodash-es';
|
||||||
/**
|
/**
|
||||||
|
@ -156,9 +157,7 @@ export default class DiscussionListItem extends Component {
|
||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
active() {
|
active() {
|
||||||
const idParam = m.route.param('id');
|
return app.current.matches(DiscussionPage, { discussion: this.attrs.discussion });
|
||||||
|
|
||||||
return idParam && idParam.split('-')[0] === this.attrs.discussion.id();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -109,7 +109,7 @@ export default class DiscussionPage extends Page {
|
||||||
} else {
|
} else {
|
||||||
const params = this.requestParams();
|
const params = this.requestParams();
|
||||||
|
|
||||||
app.store.find('discussions', m.route.param('id').split('-')[0], params).then(this.show.bind(this));
|
app.store.find('discussions', m.route.param('id'), params).then(this.show.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
m.redraw();
|
m.redraw();
|
||||||
|
@ -123,6 +123,7 @@ export default class DiscussionPage extends Page {
|
||||||
*/
|
*/
|
||||||
requestParams() {
|
requestParams() {
|
||||||
return {
|
return {
|
||||||
|
bySlug: true,
|
||||||
page: { near: this.near },
|
page: { near: this.near },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ import UsersSearchSource from './UsersSearchSource';
|
||||||
* - state: SearchState instance.
|
* - state: SearchState instance.
|
||||||
*/
|
*/
|
||||||
export default class Search extends Component {
|
export default class Search extends Component {
|
||||||
|
static MIN_SEARCH_LEN = 3;
|
||||||
|
|
||||||
oninit(vnode) {
|
oninit(vnode) {
|
||||||
super.oninit(vnode);
|
super.oninit(vnode);
|
||||||
this.state = this.attrs.state;
|
this.state = this.attrs.state;
|
||||||
|
@ -152,7 +154,7 @@ export default class Search extends Component {
|
||||||
search.searchTimeout = setTimeout(() => {
|
search.searchTimeout = setTimeout(() => {
|
||||||
if (state.isCached(query)) return;
|
if (state.isCached(query)) return;
|
||||||
|
|
||||||
if (query.length >= 3) {
|
if (query.length >= Search.MIN_SEARCH_LEN) {
|
||||||
search.sources.map((source) => {
|
search.sources.map((source) => {
|
||||||
if (!source.search) return;
|
if (!source.search) return;
|
||||||
|
|
||||||
|
|
|
@ -102,7 +102,7 @@ export default class UserPage extends Page {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.user) {
|
if (!this.user) {
|
||||||
app.store.find('users', username).then(this.show.bind(this));
|
app.store.find('users', username, { bySlug: true }).then(this.show.bind(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,6 @@
|
||||||
import DefaultResolver from '../../common/resolvers/DefaultResolver';
|
import DefaultResolver from '../../common/resolvers/DefaultResolver';
|
||||||
import DiscussionPage from '../components/DiscussionPage';
|
import DiscussionPage from '../components/DiscussionPage';
|
||||||
|
|
||||||
/**
|
|
||||||
* This isn't exported as it is a temporary measure.
|
|
||||||
* A more robust system will be implemented alongside UTF-8 support in beta 15.
|
|
||||||
*/
|
|
||||||
function getDiscussionIdFromSlug(slug: string | undefined) {
|
|
||||||
if (!slug) return;
|
|
||||||
return slug.split('-')[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A custom route resolver for DiscussionPage that generates the same key to all posts
|
* A custom route resolver for DiscussionPage that generates the same key to all posts
|
||||||
* on the same discussion. It triggers a scroll when going from one post to another
|
* on the same discussion. It triggers a scroll when going from one post to another
|
||||||
|
@ -18,17 +9,32 @@ function getDiscussionIdFromSlug(slug: string | undefined) {
|
||||||
export default class DiscussionPageResolver extends DefaultResolver {
|
export default class DiscussionPageResolver extends DefaultResolver {
|
||||||
static scrollToPostNumber: string | null = null;
|
static scrollToPostNumber: string | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove optional parts of a discussion's slug to keep the substring
|
||||||
|
* that bijectively maps to a discussion object. By default this just
|
||||||
|
* extracts the numerical ID from the slug. If a custom discussion
|
||||||
|
* slugging driver is used, this may need to be overriden.
|
||||||
|
* @param slug
|
||||||
|
*/
|
||||||
|
canonicalizeDiscussionSlug(slug: string | undefined) {
|
||||||
|
if (!slug) return;
|
||||||
|
return slug.split('-')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
makeKey() {
|
makeKey() {
|
||||||
const params = { ...m.route.param() };
|
const params = { ...m.route.param() };
|
||||||
if ('near' in params) {
|
if ('near' in params) {
|
||||||
delete params.near;
|
delete params.near;
|
||||||
}
|
}
|
||||||
params.id = getDiscussionIdFromSlug(params.id);
|
params.id = this.canonicalizeDiscussionSlug(params.id);
|
||||||
return this.routeName.replace('.near', '') + JSON.stringify(params);
|
return this.routeName.replace('.near', '') + JSON.stringify(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
onmatch(args, requestedPath, route) {
|
onmatch(args, requestedPath, route) {
|
||||||
if (app.current.matches(DiscussionPage) && getDiscussionIdFromSlug(args.id) === getDiscussionIdFromSlug(m.route.param('id'))) {
|
if (app.current.matches(DiscussionPage) && this.canonicalizeDiscussionSlug(args.id) === this.canonicalizeDiscussionSlug(m.route.param('id'))) {
|
||||||
// By default, the first post number of any discussion is 1
|
// By default, the first post number of any discussion is 1
|
||||||
DiscussionPageResolver.scrollToPostNumber = args.near || '1';
|
DiscussionPageResolver.scrollToPostNumber = args.near || '1';
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,9 +34,8 @@ export default function (app) {
|
||||||
* @return {String}
|
* @return {String}
|
||||||
*/
|
*/
|
||||||
app.route.discussion = (discussion, near) => {
|
app.route.discussion = (discussion, near) => {
|
||||||
const slug = discussion.slug();
|
|
||||||
return app.route(near && near !== 1 ? 'discussion.near' : 'discussion', {
|
return app.route(near && near !== 1 ? 'discussion.near' : 'discussion', {
|
||||||
id: discussion.id() + (slug.trim() ? '-' + slug : ''),
|
id: discussion.slug(),
|
||||||
near: near && near !== 1 ? near : undefined,
|
near: near && near !== 1 ? near : undefined,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -59,7 +58,7 @@ export default function (app) {
|
||||||
*/
|
*/
|
||||||
app.route.user = (user) => {
|
app.route.user = (user) => {
|
||||||
return app.route('user', {
|
return app.route('user', {
|
||||||
username: user.username(),
|
username: user.slug(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,9 @@ class AdminPayload
|
||||||
$document->payload['extensions'] = $this->extensions->getExtensions()->toArray();
|
$document->payload['extensions'] = $this->extensions->getExtensions()->toArray();
|
||||||
|
|
||||||
$document->payload['displayNameDrivers'] = array_keys($this->container->make('flarum.user.display_name.supported_drivers'));
|
$document->payload['displayNameDrivers'] = array_keys($this->container->make('flarum.user.display_name.supported_drivers'));
|
||||||
|
$document->payload['slugDrivers'] = array_map(function ($resourceDrivers) {
|
||||||
|
return array_keys($resourceDrivers);
|
||||||
|
}, $this->container->make('flarum.http.slugDrivers'));
|
||||||
|
|
||||||
$document->payload['phpVersion'] = PHP_VERSION;
|
$document->payload['phpVersion'] = PHP_VERSION;
|
||||||
$document->payload['mysqlVersion'] = $this->db->selectOne('select version() as version')->version;
|
$document->payload['mysqlVersion'] = $this->db->selectOne('select version() as version')->version;
|
||||||
|
|
|
@ -12,6 +12,7 @@ namespace Flarum\Api\Controller;
|
||||||
use Flarum\Api\Serializer\DiscussionSerializer;
|
use Flarum\Api\Serializer\DiscussionSerializer;
|
||||||
use Flarum\Discussion\Discussion;
|
use Flarum\Discussion\Discussion;
|
||||||
use Flarum\Discussion\DiscussionRepository;
|
use Flarum\Discussion\DiscussionRepository;
|
||||||
|
use Flarum\Http\SlugManager;
|
||||||
use Flarum\Post\PostRepository;
|
use Flarum\Post\PostRepository;
|
||||||
use Flarum\User\User;
|
use Flarum\User\User;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
@ -31,6 +32,11 @@ class ShowDiscussionController extends AbstractShowController
|
||||||
*/
|
*/
|
||||||
protected $posts;
|
protected $posts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var SlugManager
|
||||||
|
*/
|
||||||
|
protected $slugManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
@ -61,11 +67,13 @@ class ShowDiscussionController extends AbstractShowController
|
||||||
/**
|
/**
|
||||||
* @param \Flarum\Discussion\DiscussionRepository $discussions
|
* @param \Flarum\Discussion\DiscussionRepository $discussions
|
||||||
* @param \Flarum\Post\PostRepository $posts
|
* @param \Flarum\Post\PostRepository $posts
|
||||||
|
* @param \Flarum\Http\SlugManager $slugManager
|
||||||
*/
|
*/
|
||||||
public function __construct(DiscussionRepository $discussions, PostRepository $posts)
|
public function __construct(DiscussionRepository $discussions, PostRepository $posts, SlugManager $slugManager)
|
||||||
{
|
{
|
||||||
$this->discussions = $discussions;
|
$this->discussions = $discussions;
|
||||||
$this->posts = $posts;
|
$this->posts = $posts;
|
||||||
|
$this->slugManager = $slugManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,7 +85,11 @@ class ShowDiscussionController extends AbstractShowController
|
||||||
$actor = $request->getAttribute('actor');
|
$actor = $request->getAttribute('actor');
|
||||||
$include = $this->extractInclude($request);
|
$include = $this->extractInclude($request);
|
||||||
|
|
||||||
$discussion = $this->discussions->findOrFail($discussionId, $actor);
|
if (Arr::get($request->getQueryParams(), 'bySlug', false)) {
|
||||||
|
$discussion = $this->slugManager->forResource(Discussion::class)->fromSlug($discussionId, $actor);
|
||||||
|
} else {
|
||||||
|
$discussion = $this->discussions->findOrFail($discussionId, $actor);
|
||||||
|
}
|
||||||
|
|
||||||
if (in_array('posts', $include)) {
|
if (in_array('posts', $include)) {
|
||||||
$postRelationships = $this->getPostRelationships($include);
|
$postRelationships = $this->getPostRelationships($include);
|
||||||
|
|
|
@ -11,6 +11,8 @@ namespace Flarum\Api\Controller;
|
||||||
|
|
||||||
use Flarum\Api\Serializer\CurrentUserSerializer;
|
use Flarum\Api\Serializer\CurrentUserSerializer;
|
||||||
use Flarum\Api\Serializer\UserSerializer;
|
use Flarum\Api\Serializer\UserSerializer;
|
||||||
|
use Flarum\Http\SlugManager;
|
||||||
|
use Flarum\User\User;
|
||||||
use Flarum\User\UserRepository;
|
use Flarum\User\UserRepository;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
@ -29,15 +31,22 @@ class ShowUserController extends AbstractShowController
|
||||||
public $include = ['groups'];
|
public $include = ['groups'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Flarum\User\UserRepository
|
* @var SlugManager
|
||||||
|
*/
|
||||||
|
protected $slugManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var UserRepository
|
||||||
*/
|
*/
|
||||||
protected $users;
|
protected $users;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Flarum\User\UserRepository $users
|
* @param SlugManager $slugManager
|
||||||
|
* @param UserRepository $users
|
||||||
*/
|
*/
|
||||||
public function __construct(UserRepository $users)
|
public function __construct(SlugManager $slugManager, UserRepository $users)
|
||||||
{
|
{
|
||||||
|
$this->slugManager = $slugManager;
|
||||||
$this->users = $users;
|
$this->users = $users;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,17 +56,18 @@ class ShowUserController extends AbstractShowController
|
||||||
protected function data(ServerRequestInterface $request, Document $document)
|
protected function data(ServerRequestInterface $request, Document $document)
|
||||||
{
|
{
|
||||||
$id = Arr::get($request->getQueryParams(), 'id');
|
$id = Arr::get($request->getQueryParams(), 'id');
|
||||||
|
|
||||||
if (! is_numeric($id)) {
|
|
||||||
$id = $this->users->getIdForUsername($id);
|
|
||||||
}
|
|
||||||
|
|
||||||
$actor = $request->getAttribute('actor');
|
$actor = $request->getAttribute('actor');
|
||||||
|
|
||||||
if ($actor->id == $id) {
|
if (Arr::get($request->getQueryParams(), 'bySlug', false)) {
|
||||||
|
$user = $this->slugManager->forResource(User::class)->fromSlug($id, $actor);
|
||||||
|
} else {
|
||||||
|
$user = $this->users->findOrFail($id, $actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($actor->id === $user->id) {
|
||||||
$this->serializer = CurrentUserSerializer::class;
|
$this->serializer = CurrentUserSerializer::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->users->findOrFail($id, $actor);
|
return $user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
namespace Flarum\Api\Serializer;
|
namespace Flarum\Api\Serializer;
|
||||||
|
|
||||||
use Flarum\Discussion\Discussion;
|
use Flarum\Discussion\Discussion;
|
||||||
|
use Flarum\Http\SlugManager;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
|
||||||
class BasicDiscussionSerializer extends AbstractSerializer
|
class BasicDiscussionSerializer extends AbstractSerializer
|
||||||
|
@ -19,6 +20,16 @@ class BasicDiscussionSerializer extends AbstractSerializer
|
||||||
*/
|
*/
|
||||||
protected $type = 'discussions';
|
protected $type = 'discussions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var SlugManager
|
||||||
|
*/
|
||||||
|
protected $slugManager;
|
||||||
|
|
||||||
|
public function __construct(SlugManager $slugManager)
|
||||||
|
{
|
||||||
|
$this->slugManager = $slugManager;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*
|
*
|
||||||
|
@ -35,7 +46,7 @@ class BasicDiscussionSerializer extends AbstractSerializer
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'title' => $discussion->title,
|
'title' => $discussion->title,
|
||||||
'slug' => $discussion->slug,
|
'slug' => $this->slugManager->forResource(Discussion::class)->toSlug($discussion),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
namespace Flarum\Api\Serializer;
|
namespace Flarum\Api\Serializer;
|
||||||
|
|
||||||
|
use Flarum\Http\SlugManager;
|
||||||
use Flarum\User\User;
|
use Flarum\User\User;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
@ -19,6 +20,16 @@ class BasicUserSerializer extends AbstractSerializer
|
||||||
*/
|
*/
|
||||||
protected $type = 'users';
|
protected $type = 'users';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var SlugManager
|
||||||
|
*/
|
||||||
|
protected $slugManager;
|
||||||
|
|
||||||
|
public function __construct(SlugManager $slugManager)
|
||||||
|
{
|
||||||
|
$this->slugManager = $slugManager;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*
|
*
|
||||||
|
@ -36,7 +47,8 @@ class BasicUserSerializer extends AbstractSerializer
|
||||||
return [
|
return [
|
||||||
'username' => $user->username,
|
'username' => $user->username,
|
||||||
'displayName' => $user->display_name,
|
'displayName' => $user->display_name,
|
||||||
'avatarUrl' => $user->avatar_url
|
'avatarUrl' => $user->avatar_url,
|
||||||
|
'slug' => $this->slugManager->forResource(User::class)->toSlug($user)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
42
src/Discussion/IdWithTransliteratedSlugDriver.php
Normal file
42
src/Discussion/IdWithTransliteratedSlugDriver.php
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<?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\Discussion;
|
||||||
|
|
||||||
|
use Flarum\Database\AbstractModel;
|
||||||
|
use Flarum\Http\SlugDriverInterface;
|
||||||
|
use Flarum\User\User;
|
||||||
|
|
||||||
|
class IdWithTransliteratedSlugDriver implements SlugDriverInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var DiscussionRepository
|
||||||
|
*/
|
||||||
|
protected $discussions;
|
||||||
|
|
||||||
|
public function __construct(DiscussionRepository $discussions)
|
||||||
|
{
|
||||||
|
$this->discussions = $discussions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toSlug(AbstractModel $instance): string
|
||||||
|
{
|
||||||
|
return $instance->id.(trim($instance->slug) ? '-'.$instance->slug : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fromSlug(string $slug, User $actor): AbstractModel
|
||||||
|
{
|
||||||
|
if (strpos($slug, '-')) {
|
||||||
|
$slug_array = explode('-', $slug);
|
||||||
|
$slug = $slug_array[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->discussions->findOrFail($slug, $actor);
|
||||||
|
}
|
||||||
|
}
|
54
src/Extend/ModelUrl.php
Normal file
54
src/Extend/ModelUrl.php
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<?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;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
class ModelUrl implements ExtenderInterface
|
||||||
|
{
|
||||||
|
private $modelClass;
|
||||||
|
private $slugDrivers = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $modelClass The ::class attribute of the model you are modifying.
|
||||||
|
* This model should extend from \Flarum\Database\AbstractModel.
|
||||||
|
*/
|
||||||
|
public function __construct(string $modelClass)
|
||||||
|
{
|
||||||
|
$this->modelClass = $modelClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a slug driver.
|
||||||
|
*
|
||||||
|
* @param string $identifier Identifier for slug driver.
|
||||||
|
* @param string $driver ::class attribute of driver class, which must implement Flarum\Http\SlugDriverInterface
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function addSlugDriver(string $identifier, string $driver)
|
||||||
|
{
|
||||||
|
$this->slugDrivers[$identifier] = $driver;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function extend(Container $container, Extension $extension = null)
|
||||||
|
{
|
||||||
|
if ($this->slugDrivers) {
|
||||||
|
$container->extend('flarum.http.slugDrivers', function ($existingDrivers) {
|
||||||
|
$existingDrivers[$this->modelClass] = array_merge(Arr::get($existingDrivers, $this->modelClass, []), $this->slugDrivers);
|
||||||
|
|
||||||
|
return $existingDrivers;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -74,9 +74,7 @@ class Discussion
|
||||||
unset($newQueryParams['id']);
|
unset($newQueryParams['id']);
|
||||||
$queryString = http_build_query($newQueryParams);
|
$queryString = http_build_query($newQueryParams);
|
||||||
|
|
||||||
$idWithSlug = $apiDocument->data->id.(trim($apiDocument->data->attributes->slug) ? '-'.$apiDocument->data->attributes->slug : '');
|
return $this->url->to('forum')->route('discussion', ['id' => $apiDocument->data->attributes->slug]).
|
||||||
|
|
||||||
return $this->url->to('forum')->route('discussion', ['id' => $idWithSlug]).
|
|
||||||
($queryString ? '?'.$queryString : '');
|
($queryString ? '?'.$queryString : '');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -106,6 +104,7 @@ class Discussion
|
||||||
*/
|
*/
|
||||||
protected function getApiDocument(User $actor, array $params)
|
protected function getApiDocument(User $actor, array $params)
|
||||||
{
|
{
|
||||||
|
$params['bySlug'] = true;
|
||||||
$response = $this->api->send('Flarum\Api\Controller\ShowDiscussionController', $actor, $params);
|
$response = $this->api->send('Flarum\Api\Controller\ShowDiscussionController', $actor, $params);
|
||||||
$statusCode = $response->getStatusCode();
|
$statusCode = $response->getStatusCode();
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ class User
|
||||||
$user = $apiDocument->data->attributes;
|
$user = $apiDocument->data->attributes;
|
||||||
|
|
||||||
$document->title = $user->displayName;
|
$document->title = $user->displayName;
|
||||||
$document->canonicalUrl = $this->url->to('forum')->route('user', ['username' => $user->username]);
|
$document->canonicalUrl = $this->url->to('forum')->route('user', ['username' => $user->slug]);
|
||||||
$document->payload['apiDocument'] = $apiDocument;
|
$document->payload['apiDocument'] = $apiDocument;
|
||||||
|
|
||||||
return $document;
|
return $document;
|
||||||
|
@ -70,6 +70,7 @@ class User
|
||||||
*/
|
*/
|
||||||
protected function getApiDocument(FlarumUser $actor, array $params)
|
protected function getApiDocument(FlarumUser $actor, array $params)
|
||||||
{
|
{
|
||||||
|
$params['bySlug'] = true;
|
||||||
$response = $this->api->send(ShowUserController::class, $actor, $params);
|
$response = $this->api->send(ShowUserController::class, $actor, $params);
|
||||||
$statusCode = $response->getStatusCode();
|
$statusCode = $response->getStatusCode();
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,13 @@
|
||||||
|
|
||||||
namespace Flarum\Http;
|
namespace Flarum\Http;
|
||||||
|
|
||||||
|
use Flarum\Discussion\Discussion;
|
||||||
|
use Flarum\Discussion\IdWithTransliteratedSlugDriver;
|
||||||
use Flarum\Foundation\AbstractServiceProvider;
|
use Flarum\Foundation\AbstractServiceProvider;
|
||||||
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
|
use Flarum\User\User;
|
||||||
|
use Flarum\User\UsernameSlugDriver;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
class HttpServiceProvider extends AbstractServiceProvider
|
class HttpServiceProvider extends AbstractServiceProvider
|
||||||
{
|
{
|
||||||
|
@ -25,5 +31,35 @@ class HttpServiceProvider extends AbstractServiceProvider
|
||||||
$this->app->bind(Middleware\CheckCsrfToken::class, function ($app) {
|
$this->app->bind(Middleware\CheckCsrfToken::class, function ($app) {
|
||||||
return new Middleware\CheckCsrfToken($app->make('flarum.http.csrfExemptPaths'));
|
return new Middleware\CheckCsrfToken($app->make('flarum.http.csrfExemptPaths'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$this->app->singleton('flarum.http.slugDrivers', function () {
|
||||||
|
return [
|
||||||
|
Discussion::class => [
|
||||||
|
'default' => IdWithTransliteratedSlugDriver::class
|
||||||
|
],
|
||||||
|
User::class => [
|
||||||
|
'default' => UsernameSlugDriver::class
|
||||||
|
],
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->app->singleton('flarum.http.selectedSlugDrivers', function () {
|
||||||
|
$settings = $this->app->make(SettingsRepositoryInterface::class);
|
||||||
|
|
||||||
|
$compiledDrivers = [];
|
||||||
|
|
||||||
|
foreach ($this->app->make('flarum.http.slugDrivers') as $resourceClass => $resourceDrivers) {
|
||||||
|
$driverKey = $settings->get("slug_driver_$resourceClass", 'default');
|
||||||
|
|
||||||
|
$driverClass = Arr::get($resourceDrivers, $driverKey, $resourceDrivers['default']);
|
||||||
|
|
||||||
|
$compiledDrivers[$resourceClass] = $this->app->make($driverClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $compiledDrivers;
|
||||||
|
});
|
||||||
|
$this->app->bind(SlugManager::class, function () {
|
||||||
|
return new SlugManager($this->app->make('flarum.http.selectedSlugDrivers'));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
20
src/Http/SlugDriverInterface.php
Normal file
20
src/Http/SlugDriverInterface.php
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?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\Database\AbstractModel;
|
||||||
|
use Flarum\User\User;
|
||||||
|
|
||||||
|
interface SlugDriverInterface
|
||||||
|
{
|
||||||
|
public function toSlug(AbstractModel $instance): string;
|
||||||
|
|
||||||
|
public function fromSlug(string $slug, User $actor): AbstractModel;
|
||||||
|
}
|
27
src/Http/SlugManager.php
Normal file
27
src/Http/SlugManager.php
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?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 Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
class SlugManager
|
||||||
|
{
|
||||||
|
protected $drivers = [];
|
||||||
|
|
||||||
|
public function __construct(array $drivers)
|
||||||
|
{
|
||||||
|
$this->drivers = $drivers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function forResource(string $resourceName): SlugDriverInterface
|
||||||
|
{
|
||||||
|
return Arr::get($this->drivers, $resourceName, null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,23 @@ class UserRepository
|
||||||
return $this->scopeVisibleTo($query, $actor)->firstOrFail();
|
return $this->scopeVisibleTo($query, $actor)->firstOrFail();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a user by username, optionally making sure it is visible to a certain
|
||||||
|
* user, or throw an exception.
|
||||||
|
*
|
||||||
|
* @param int $id
|
||||||
|
* @param User $actor
|
||||||
|
* @return User
|
||||||
|
*
|
||||||
|
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||||
|
*/
|
||||||
|
public function findOrFailByUsername($username, User $actor = null)
|
||||||
|
{
|
||||||
|
$query = User::where('username', $username);
|
||||||
|
|
||||||
|
return $this->scopeVisibleTo($query, $actor)->firstOrFail();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a user by an identification (username or email).
|
* Find a user by an identification (username or email).
|
||||||
*
|
*
|
||||||
|
|
36
src/User/UsernameSlugDriver.php
Normal file
36
src/User/UsernameSlugDriver.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\User;
|
||||||
|
|
||||||
|
use Flarum\Database\AbstractModel;
|
||||||
|
use Flarum\Http\SlugDriverInterface;
|
||||||
|
|
||||||
|
class UsernameSlugDriver implements SlugDriverInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var UserRepository
|
||||||
|
*/
|
||||||
|
protected $users;
|
||||||
|
|
||||||
|
public function __construct(UserRepository $users)
|
||||||
|
{
|
||||||
|
$this->users = $users;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toSlug(AbstractModel $instance): string
|
||||||
|
{
|
||||||
|
return $instance->username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fromSlug(string $slug, User $actor): AbstractModel
|
||||||
|
{
|
||||||
|
return $this->users->findOrFailByUsername($slug, $actor);
|
||||||
|
}
|
||||||
|
}
|
|
@ -66,6 +66,24 @@ class ShowTest extends TestCase
|
||||||
$this->assertEquals(200, $response->getStatusCode());
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function author_can_see_discussion_via_slug()
|
||||||
|
{
|
||||||
|
// Note that here, the slug doesn't actually have to match the real slug
|
||||||
|
// since the default slugging strategy only takes the numerical part into account
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('GET', '/api/discussions/1-fdsafdsajfsakf', [
|
||||||
|
'authenticatedAs' => 2,
|
||||||
|
])->withQueryParams([
|
||||||
|
'bySlug' => true
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -25,9 +25,10 @@ class CreateTest extends TestCase
|
||||||
$this->prepareDatabase([
|
$this->prepareDatabase([
|
||||||
'users' => [
|
'users' => [
|
||||||
$this->adminUser(),
|
$this->adminUser(),
|
||||||
|
$this->normalUser(),
|
||||||
],
|
],
|
||||||
'groups' => [
|
'groups' => [
|
||||||
$this->adminGroup(),
|
$this->adminGroup()
|
||||||
],
|
],
|
||||||
'group_user' => [
|
'group_user' => [
|
||||||
['user_id' => 1, 'group_id' => 1],
|
['user_id' => 1, 'group_id' => 1],
|
||||||
|
|
197
tests/integration/api/users/ShowTest.php
Normal file
197
tests/integration/api/users/ShowTest.php
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
<?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\Tests\integration\RetrievesAuthorizedUsers;
|
||||||
|
use Flarum\Tests\integration\TestCase;
|
||||||
|
|
||||||
|
class ShowTest extends TestCase
|
||||||
|
{
|
||||||
|
use RetrievesAuthorizedUsers;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->prepareDatabase([
|
||||||
|
'users' => [
|
||||||
|
$this->adminUser(),
|
||||||
|
$this->normalUser(),
|
||||||
|
],
|
||||||
|
'groups' => [
|
||||||
|
$this->adminGroup()
|
||||||
|
],
|
||||||
|
'group_user' => [
|
||||||
|
['user_id' => 1, 'group_id' => 1],
|
||||||
|
],
|
||||||
|
'settings' => [
|
||||||
|
['key' => 'mail_driver', 'value' => 'log'],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function admin_can_see_user()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('GET', '/api/users/2', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function admin_can_see_user_via_slug()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('GET', '/api/users/normal', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
])->withQueryParams([
|
||||||
|
'bySlug' => true
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function guest_cannot_see_user()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('GET', '/api/users/2')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(404, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function guest_cannot_see_user_by_slug()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('GET', '/api/users/2')->withQueryParams([
|
||||||
|
'bySlug' => true
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(404, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function user_can_see_themselves()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('GET', '/api/users/2', [
|
||||||
|
'authenticatedAs' => 2,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function user_can_see_themselves_via_slug()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('GET', '/api/users/normal', [
|
||||||
|
'authenticatedAs' => 2,
|
||||||
|
])->withQueryParams([
|
||||||
|
'bySlug' => true
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function user_cant_see_others_by_default()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('GET', '/api/users/1', [
|
||||||
|
'authenticatedAs' => 2,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(404, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function user_cant_see_others_by_default_via_slug()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('GET', '/api/users/admin', [
|
||||||
|
'authenticatedAs' => 2,
|
||||||
|
])->withQueryParams([
|
||||||
|
'bySlug' => true
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(404, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function user_can_see_others_if_allowed()
|
||||||
|
{
|
||||||
|
$this->prepareDatabase([
|
||||||
|
'group_permission' => [
|
||||||
|
['permission' => 'viewDiscussions', 'group_id' => 3],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('GET', '/api/users/1', [
|
||||||
|
'authenticatedAs' => 2,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function user_can_see_others_if_allowed_via_slug()
|
||||||
|
{
|
||||||
|
$this->prepareDatabase([
|
||||||
|
'group_permission' => [
|
||||||
|
['permission' => 'viewDiscussions', 'group_id' => 3],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('GET', '/api/users/admin', [
|
||||||
|
'authenticatedAs' => 2,
|
||||||
|
])->withQueryParams([
|
||||||
|
'bySlug' => true
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
82
tests/integration/extenders/ModelUrlTest.php
Normal file
82
tests/integration/extenders/ModelUrlTest.php
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<?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\Database\AbstractModel;
|
||||||
|
use Flarum\Extend;
|
||||||
|
use Flarum\Http\SlugDriverInterface;
|
||||||
|
use Flarum\Http\SlugManager;
|
||||||
|
use Flarum\Tests\integration\RetrievesAuthorizedUsers;
|
||||||
|
use Flarum\Tests\integration\TestCase;
|
||||||
|
use Flarum\User\User;
|
||||||
|
|
||||||
|
class ModelUrlTest extends TestCase
|
||||||
|
{
|
||||||
|
use RetrievesAuthorizedUsers;
|
||||||
|
|
||||||
|
protected function prepDb()
|
||||||
|
{
|
||||||
|
$userClass = User::class;
|
||||||
|
$this->prepareDatabase([
|
||||||
|
'users' => [
|
||||||
|
$this->adminUser(),
|
||||||
|
$this->normalUser(),
|
||||||
|
],
|
||||||
|
'settings' => [
|
||||||
|
['key' => "slug_driver_$userClass", 'value' => 'testDriver'],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function uses_default_driver_by_default()
|
||||||
|
{
|
||||||
|
$this->prepDb();
|
||||||
|
|
||||||
|
$slugManager = $this->app()->getContainer()->make(SlugManager::class);
|
||||||
|
|
||||||
|
$testUser = User::find(1);
|
||||||
|
|
||||||
|
$this->assertEquals('admin', $slugManager->forResource(User::class)->toSlug($testUser));
|
||||||
|
$this->assertEquals('1', $slugManager->forResource(User::class)->fromSlug('admin', $testUser)->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function custom_slug_driver_has_effect_if_added()
|
||||||
|
{
|
||||||
|
$this->extend((new Extend\ModelUrl(User::class))->addSlugDriver('testDriver', TestSlugDriver::class));
|
||||||
|
|
||||||
|
$this->prepDb();
|
||||||
|
|
||||||
|
$slugManager = $this->app()->getContainer()->make(SlugManager::class);
|
||||||
|
|
||||||
|
$testUser = User::find(1);
|
||||||
|
|
||||||
|
$this->assertEquals('test-slug', $slugManager->forResource(User::class)->toSlug($testUser));
|
||||||
|
$this->assertEquals('1', $slugManager->forResource(User::class)->fromSlug('random-gibberish', $testUser)->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestSlugDriver implements SlugDriverInterface
|
||||||
|
{
|
||||||
|
public function toSlug(AbstractModel $instance): string
|
||||||
|
{
|
||||||
|
return 'test-slug';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fromSlug(string $slug, User $actor): AbstractModel
|
||||||
|
{
|
||||||
|
return User::find(1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
@foreach ($apiDocument->data as $discussion)
|
@foreach ($apiDocument->data as $discussion)
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ $url->to('forum')->route('discussion', [
|
<a href="{{ $url->to('forum')->route('discussion', [
|
||||||
'id' => $discussion->id . (trim($discussion->attributes->slug) ? '-' . $discussion->attributes->slug : '')
|
'id' => $discussion->attributes->slug
|
||||||
]) }}">
|
]) }}">
|
||||||
{{ $discussion->attributes->title }}
|
{{ $discussion->attributes->title }}
|
||||||
</a>
|
</a>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user