mirror of
https://github.com/flarum/core.git
synced 2025-08-14 04:14:06 +02:00
Compare commits
21 Commits
tk/3787-fl
...
v1.8.2
Author | SHA1 | Date | |
---|---|---|---|
|
82e08e3fa5 | ||
|
2c4a2b8d9e | ||
|
00866fbba9 | ||
|
0d1d4d46d1 | ||
|
b1383a955f | ||
|
daeab48ae8 | ||
|
e03ca4406d | ||
|
7894c6a69b | ||
|
102e31754a | ||
|
8538f9c8f6 | ||
|
5a4bb7ccf2 | ||
|
d2a6329689 | ||
|
2bc2899a1d | ||
|
5437bf5c23 | ||
|
717af13bb1 | ||
|
e72541e35d | ||
|
577890d89c | ||
|
253a3d281d | ||
|
d27f952584 | ||
|
e5abffc75b | ||
|
d1059c1cc7 |
2
.github/workflows/frontend.yml
vendored
2
.github/workflows/frontend.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
backend_directory: ./
|
||||
js_package_manager: yarn
|
||||
cache_dependency_path: ./yarn.lock
|
||||
main_git_branch: main
|
||||
main_git_branch: 1.x
|
||||
enable_tests: true
|
||||
# @TODO: fix bundlewatch
|
||||
enable_bundlewatch: false
|
||||
|
58
CHANGELOG.md
58
CHANGELOG.md
@@ -1,5 +1,63 @@
|
||||
# Changelog
|
||||
|
||||
## [v1.8.2](https://github.com/flarum/framework/compare/v1.8.1...v1.8.2)
|
||||
### Fixed
|
||||
* suspended users can abuse avatar upload [#3890]
|
||||
* missing compat exports [#3888]
|
||||
|
||||
## [v1.8.1](https://github.com/flarum/framework/compare/v1.8.0...v1.8.1)
|
||||
### Fixed
|
||||
* recover temporary solution for html entities in browser title (e72541e35de4f71f9d870bbd9bb46ddf586bdf1d)
|
||||
* custom contrast color affected by parents (577890d89c593ae5b6cb96083fab69e2f1ae600c)
|
||||
* reply placeholder wrong positioning (253a3d281dbf5ce3fa712b629b80587cf67e7dbe)
|
||||
* (mentions) missed post mentions UI changes with lazy loading [#3832]
|
||||
* (mentions) cannot use newly introduced mentionables extender [#3849]
|
||||
* (mentions) missing slug from post mention links ([5a4bb7c](5a4bb7ccf226f66dd44816cb69b3d7cfe4ad7f7c))
|
||||
|
||||
## [v1.8.0](https://github.com/flarum/framework/compare/v1.7.1...v1.8.0)
|
||||
### Fixed
|
||||
- (a11y) reply placeholder not accessible [#3793]
|
||||
- (bbcode) highlight.js does not work after changing post content [#3817]
|
||||
- (bbcode) localize quote `wrote` string [#3809]
|
||||
- (mentions) mentions XHR fired even after mentioning is done [#3806]
|
||||
- (package-manager) available core updates cause an error in the dashboard ([fab71f2](fab71f2d01fa20ce9b3002833339dc5ea3ea6301))
|
||||
- (tags) not all tags are loaded in the permission grid [#3804]
|
||||
- (tags) tag discussion modal filters with exact matches only after first index [#3786]
|
||||
- (testing) always clear cache in integration test's tearDown [#3818]
|
||||
- `UserSecurityPage` not exported ([232618a](232618aba604ab003425df38b895208c863d3260))
|
||||
- `isDark()` utility can receive null value [#3774]
|
||||
- approving a post does not bump user `comment_count` [#3790]
|
||||
- circular dependencies disable all involved extensions [#3785]
|
||||
- color input overflowing the input box [#3796]
|
||||
- deleting a discussion from the profile does not visually remove it [#3799]
|
||||
- discussion page showing horizontal scroll on iOS [#3821]
|
||||
- empty string displayed as SelectDropdown title [#3773]
|
||||
- filter values are not validated [#3795]
|
||||
- infinite scroll not initialized for notifications on big screens [#3733]
|
||||
- notification subject discussion eager loading fails [#3788]
|
||||
- null as 2nd param in `preg_match` is deprecated [#3801]
|
||||
- unread count in post stream not visible [#3791]
|
||||
- unreadable badge icon on certain colors [#3810]
|
||||
- integrity constraint violation [#3772]
|
||||
### Changed
|
||||
- (core,mentions) limit `mentionedBy` post relation results [#3780]
|
||||
- (likes) limit `likes` relationship results [#3781]
|
||||
- Change some methods from private to protected, to be able to extend the affected classes [#3802]
|
||||
- Do not catch exceptions when testing Console commands [#3813]
|
||||
- drop usage of jquery in `install` and `update` interfaces [#3797]
|
||||
- extensibility improvements [#3729]
|
||||
- major frontend JS cleanup [#3609]
|
||||
- revert ineffective code for encoding of page title [#3768]
|
||||
- speed up post creation time [#3808]
|
||||
### Added
|
||||
- (mentions,tags) tag mentions [#3769]
|
||||
- add delete own posts permission [#3784]
|
||||
- add a trait to flush the formatter cache in tests [#3811]
|
||||
- add user creation to users list page [#3744]
|
||||
- cli command for enabling or disabling an extension [#3816]
|
||||
- conditional extenders [#3759]
|
||||
- provide old content to `Revised` event [#3789]
|
||||
|
||||
## [v1.7.1](https://github.com/flarum/framework/compare/v1.7.0...v1.7.1)
|
||||
### Fixed
|
||||
- (tags) composer tag selection modal using wrong primary max & min numbers (abc9670659426b765274376945b818b70d84848c)
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.7",
|
||||
"flarum/core": "^1.8",
|
||||
"flarum/approval": "^1.7"
|
||||
},
|
||||
"autoload": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.7",
|
||||
"flarum/core": "^1.8",
|
||||
"flarum/flags": "^1.7"
|
||||
},
|
||||
"autoload": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.7"
|
||||
"flarum/core": "^1.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.7"
|
||||
"flarum/core": "^1.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.7"
|
||||
"flarum/core": "^1.8"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.7"
|
||||
"flarum/core": "^1.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -7,7 +7,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"flarum/core": "^1.7"
|
||||
"flarum/core": "^1.8"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.7"
|
||||
"flarum/core": "^1.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.7"
|
||||
"flarum/core": "^1.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.7"
|
||||
"flarum/core": "^1.8"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.7"
|
||||
"flarum/core": "^1.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -78,15 +78,17 @@ return [
|
||||
->addInclude(['posts.mentionedBy', 'posts.mentionedBy.user', 'posts.mentionedBy.discussion'])
|
||||
->load([
|
||||
'posts.mentionsUsers', 'posts.mentionsPosts', 'posts.mentionsPosts.user',
|
||||
'posts.mentionsGroups'
|
||||
'posts.mentionsPosts.discussion', 'posts.mentionsGroups'
|
||||
])
|
||||
->loadWhere('posts.mentionedBy', [LoadMentionedByRelationship::class, 'mutateRelation'])
|
||||
->prepareDataForSerialization([LoadMentionedByRelationship::class, 'countRelation']),
|
||||
|
||||
(new Extend\ApiController(Controller\ListDiscussionsController::class))
|
||||
->load([
|
||||
'firstPost.mentionsUsers', 'firstPost.mentionsPosts', 'firstPost.mentionsPosts.user', 'firstPost.mentionsGroups',
|
||||
'lastPost.mentionsUsers', 'lastPost.mentionsPosts', 'lastPost.mentionsPosts.user', 'lastPost.mentionsGroups',
|
||||
'firstPost.mentionsUsers', 'firstPost.mentionsPosts',
|
||||
'firstPost.mentionsPosts.user', 'firstPost.mentionsPosts.discussion', 'firstPost.mentionsGroups',
|
||||
'lastPost.mentionsUsers', 'lastPost.mentionsPosts',
|
||||
'lastPost.mentionsPosts.user', 'lastPost.mentionsPosts.discussion', 'lastPost.mentionsGroups',
|
||||
]),
|
||||
|
||||
(new Extend\ApiController(Controller\ShowPostController::class))
|
||||
@@ -98,7 +100,7 @@ return [
|
||||
|
||||
(new Extend\ApiController(Controller\ListPostsController::class))
|
||||
->addInclude(['mentionedBy', 'mentionedBy.user', 'mentionedBy.discussion'])
|
||||
->load(['mentionsUsers', 'mentionsPosts', 'mentionsPosts.user', 'mentionsGroups'])
|
||||
->load(['mentionsUsers', 'mentionsPosts', 'mentionsPosts.user', 'mentionsPosts.discussion', 'mentionsGroups'])
|
||||
->loadWhere('mentionedBy', [LoadMentionedByRelationship::class, 'mutateRelation'])
|
||||
->prepareDataForSerialization([LoadMentionedByRelationship::class, 'countRelation']),
|
||||
|
||||
|
2
extensions/mentions/js/dist/forum.js
generated
vendored
2
extensions/mentions/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/mentions/js/dist/forum.js.map
generated
vendored
2
extensions/mentions/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -5,12 +5,9 @@ import TextEditorButton from 'flarum/common/components/TextEditorButton';
|
||||
import KeyboardNavigatable from 'flarum/common/utils/KeyboardNavigatable';
|
||||
|
||||
import AutocompleteDropdown from './fragments/AutocompleteDropdown';
|
||||
import MentionFormats from './mentionables/formats/MentionFormats';
|
||||
import MentionableModels from './mentionables/MentionableModels';
|
||||
|
||||
export default function addComposerAutocomplete() {
|
||||
app.mentionFormats = new MentionFormats();
|
||||
|
||||
const $container = $('<div class="ComposerBody-mentionsDropdownContainer"></div>');
|
||||
const dropdown = new AutocompleteDropdown();
|
||||
|
||||
|
@@ -118,7 +118,7 @@ export default function addMentionedByList() {
|
||||
});
|
||||
|
||||
const limit = 4;
|
||||
const overLimit = repliers.length > limit;
|
||||
const overLimit = post.mentionedByCount() > limit;
|
||||
|
||||
// Create a list of unique users who have replied. So even if a user has
|
||||
// replied twice, they will only be in this array once.
|
||||
@@ -136,7 +136,7 @@ export default function addMentionedByList() {
|
||||
// others" name to the end of the list. Clicking on it will display a modal
|
||||
// with a full list of names.
|
||||
if (overLimit) {
|
||||
const count = repliers.length - names.length;
|
||||
const count = post.mentionedByCount() - names.length;
|
||||
|
||||
names.push(app.translator.trans('flarum-mentions.forum.post.others_text', { count }));
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import type MentionFormat from '../mentionables/formats/MentionFormat';
|
||||
|
||||
export default class Mentionables implements IExtender<ForumApplication> {
|
||||
protected formats: (new () => MentionFormat)[] = [];
|
||||
protected mentionables: Record<string, (new () => MentionableModel)[]> = {};
|
||||
protected mentionables: Record<string, (new (...args: any[]) => MentionableModel)[]> = {};
|
||||
|
||||
/**
|
||||
* Register a new mention format.
|
||||
@@ -26,7 +26,7 @@ export default class Mentionables implements IExtender<ForumApplication> {
|
||||
* @param mentionable The mentionable instance to register.
|
||||
* Must extend MentionableModel.
|
||||
*/
|
||||
mentionable(symbol: string, mentionable: new () => MentionableModel): this {
|
||||
mentionable(symbol: string, mentionable: new (...args: any[]) => MentionableModel): this {
|
||||
if (!this.mentionables[symbol]) {
|
||||
this.mentionables[symbol] = [];
|
||||
}
|
||||
|
@@ -13,11 +13,14 @@ import addComposerAutocomplete from './addComposerAutocomplete';
|
||||
import PostMentionedNotification from './components/PostMentionedNotification';
|
||||
import UserMentionedNotification from './components/UserMentionedNotification';
|
||||
import GroupMentionedNotification from './components/GroupMentionedNotification';
|
||||
import MentionFormats from './mentionables/formats/MentionFormats';
|
||||
import UserPage from 'flarum/forum/components/UserPage';
|
||||
import LinkButton from 'flarum/common/components/LinkButton';
|
||||
import User from 'flarum/common/models/User';
|
||||
import Model from 'flarum/common/Model';
|
||||
|
||||
app.mentionFormats = new MentionFormats();
|
||||
|
||||
export { default as extend } from './extend';
|
||||
|
||||
app.initializers.add('flarum-mentions', function () {
|
||||
|
@@ -4,7 +4,7 @@ import TagMention from '../TagMention';
|
||||
|
||||
export default class HashMentionFormat extends MentionFormat {
|
||||
public mentionables: (new (...args: any[]) => MentionableModel)[] = [TagMention];
|
||||
protected extendable: boolean = false;
|
||||
protected extendable: boolean = true;
|
||||
|
||||
public trigger(): string {
|
||||
return '#';
|
||||
|
@@ -29,7 +29,7 @@ class LoadMentionedByRelationship
|
||||
$actor = RequestUtil::getActor($request);
|
||||
|
||||
return $query
|
||||
->with(['mentionsPosts', 'mentionsPosts.user', 'mentionsUsers'])
|
||||
->with(['mentionsPosts', 'mentionsPosts.user', 'mentionsPosts.discussion', 'mentionsUsers'])
|
||||
->whereVisibleTo($actor)
|
||||
->oldest()
|
||||
// Limiting a relationship results is only possible because
|
||||
@@ -50,6 +50,16 @@ class LoadMentionedByRelationship
|
||||
$loadable = $data->newCollection($data->posts)->filter(function ($post) {
|
||||
return $post instanceof Post;
|
||||
});
|
||||
|
||||
// firstPost and lastPost might have been included in the API response,
|
||||
// so we have to make sure counts are also loaded for them.
|
||||
if ($data->firstPost) {
|
||||
$loadable->push($data->firstPost);
|
||||
}
|
||||
|
||||
if ($data->lastPost) {
|
||||
$loadable->push($data->lastPost);
|
||||
}
|
||||
} elseif ($data instanceof Collection) {
|
||||
$loadable = $data;
|
||||
} elseif ($data instanceof Post) {
|
||||
|
@@ -9,6 +9,8 @@
|
||||
|
||||
namespace Flarum\Mentions\Formatter;
|
||||
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Http\SlugManager;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use s9e\TextFormatter\Renderer;
|
||||
use s9e\TextFormatter\Utils;
|
||||
@@ -21,9 +23,15 @@ class FormatPostMentions
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
/**
|
||||
* @var SlugManager
|
||||
*/
|
||||
private $slugManager;
|
||||
|
||||
public function __construct(TranslatorInterface $translator, SlugManager $slugManager)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
$this->slugManager = $slugManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,6 +64,12 @@ class FormatPostMentions
|
||||
$attributes['displayname'] = $this->translator->trans('core.lib.username.deleted_text');
|
||||
}
|
||||
|
||||
if ($post) {
|
||||
$attributes['discussionid'] = $this->slugManager
|
||||
->forResource(Discussion::class)
|
||||
->toSlug($post->discussion);
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
});
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.7"
|
||||
"flarum/core": "^1.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -22,7 +22,7 @@
|
||||
"source": "https://github.com/flarum/package-manager"
|
||||
},
|
||||
"require": {
|
||||
"flarum/core": "^1.5.0",
|
||||
"flarum/core": "^1.8",
|
||||
"composer/composer": "^2.3"
|
||||
},
|
||||
"require-dev": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.7",
|
||||
"flarum/core": "^1.8",
|
||||
"pusher/pusher-php-server": "^2.2"
|
||||
},
|
||||
"require-dev": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.7"
|
||||
"flarum/core": "^1.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.7"
|
||||
"flarum/core": "^1.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.7"
|
||||
"flarum/core": "^1.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.7"
|
||||
"flarum/core": "^1.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -25,4 +25,11 @@ class UserPolicy extends AbstractPolicy
|
||||
return $this->deny();
|
||||
}
|
||||
}
|
||||
|
||||
public function uploadAvatar(User $actor, User $user)
|
||||
{
|
||||
if ($actor->suspended_until && $actor->suspended_until->isFuture()) {
|
||||
return $this->deny();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
extensions/suspend/tests/fixtures/avatar.png
vendored
Normal file
BIN
extensions/suspend/tests/fixtures/avatar.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,103 @@
|
||||
<?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\Suspend\Tests\integration\api\users;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
use Laminas\Diactoros\UploadedFile;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class UploadAvatarTest extends TestCase
|
||||
{
|
||||
use RetrievesAuthorizedUsers;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->extension('flarum-suspend');
|
||||
|
||||
$this->prepareDatabase([
|
||||
'users' => [
|
||||
['id' => 1, 'username' => 'Muralf', 'email' => 'muralf@machine.local', 'is_email_confirmed' => 1],
|
||||
$this->normalUser(),
|
||||
['id' => 3, 'username' => 'acme', 'email' => 'acme@machine.local', 'is_email_confirmed' => 1, 'suspended_until' => Carbon::now()->addDay(), 'suspend_message' => 'You have been suspended.', 'suspend_reason' => 'Suspended for acme reasons.'],
|
||||
['id' => 4, 'username' => 'acme4', 'email' => 'acme4@machine.local', 'is_email_confirmed' => 1],
|
||||
['id' => 5, 'username' => 'acme5', 'email' => 'acme5@machine.local', 'is_email_confirmed' => 1, 'suspended_until' => Carbon::now()->subDay(), 'suspend_message' => 'You have been suspended.', 'suspend_reason' => 'Suspended for acme reasons.'],
|
||||
],
|
||||
'groups' => [
|
||||
['id' => 5, 'name_singular' => 'can_edit_users', 'name_plural' => 'can_edit_users', 'is_hidden' => 0]
|
||||
],
|
||||
'group_user' => [
|
||||
['user_id' => 2, 'group_id' => 5]
|
||||
],
|
||||
'group_permission' => [
|
||||
['permission' => 'user.edit', 'group_id' => 5],
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider allowedToUploadAvatar
|
||||
* @test
|
||||
*/
|
||||
public function can_suspend_user_if_allowed(?int $authenticatedAs, int $targetUserId, string $message)
|
||||
{
|
||||
$response = $this->sendUploadAvatarRequest($authenticatedAs, $targetUserId);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode(), $response->getBody()->getContents());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider unallowedToUploadAvatar
|
||||
* @test
|
||||
*/
|
||||
public function cannot_suspend_user_if_not_allowed(?int $authenticatedAs, int $targetUserId, string $message)
|
||||
{
|
||||
$response = $this->sendUploadAvatarRequest($authenticatedAs, $targetUserId);
|
||||
|
||||
$this->assertEquals(403, $response->getStatusCode(), $response->getBody()->getContents());
|
||||
}
|
||||
|
||||
public function allowedToUploadAvatar(): array
|
||||
{
|
||||
return [
|
||||
[1, 2, 'Admin can upload avatar for any user'],
|
||||
[2, 3, 'User with permission can upload avatar for suspended user'],
|
||||
[2, 2, 'User with permission can upload avatar for self'],
|
||||
[2, 4, 'User with permission can upload avatar for other user'],
|
||||
[1, 1, 'Admin can upload avatar for self'],
|
||||
[5, 5, 'Suspended user can upload avatar for self if suspension expired'],
|
||||
];
|
||||
}
|
||||
|
||||
public function unallowedToUploadAvatar(): array
|
||||
{
|
||||
return [
|
||||
[3, 3, 'Suspended user cannot upload avatar for self'],
|
||||
[3, 2, 'Suspended user cannot upload avatar for other user'],
|
||||
[4, 3, 'User without permission cannot upload avatar for suspended user'],
|
||||
[4, 2, 'User without permission cannot upload avatar for other user'],
|
||||
[5, 2, 'Suspended user cannot upload avatar for other user if suspension expired'],
|
||||
];
|
||||
}
|
||||
|
||||
protected function sendUploadAvatarRequest(?int $authenticatedAs, int $targetUserId): ResponseInterface
|
||||
{
|
||||
return $this->send(
|
||||
$this->request('POST', "/api/users/$targetUserId/avatar", [
|
||||
'authenticatedAs' => $authenticatedAs,
|
||||
])->withHeader('Content-Type', 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW')->withUploadedFiles([
|
||||
'avatar' => new UploadedFile(__DIR__.'/../../../fixtures/avatar.png', 0, UPLOAD_ERR_OK, 'avatar.png', 'image/png')
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^1.7"
|
||||
"flarum/core": "^1.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
1
framework/core/js/dist-typings/admin/compat.d.ts
generated
vendored
1
framework/core/js/dist-typings/admin/compat.d.ts
generated
vendored
@@ -122,6 +122,7 @@ declare const _default: {
|
||||
'components/TextEditorButton': typeof import("../common/components/TextEditorButton").default;
|
||||
'components/Tooltip': typeof import("../common/components/Tooltip").default;
|
||||
'components/EditUserModal': typeof import("../common/components/EditUserModal").default;
|
||||
'components/LabelValue': typeof import("../common/components/LabelValue").default;
|
||||
Model: typeof import("../common/Model").default;
|
||||
Application: typeof import("../common/Application").default;
|
||||
'helpers/fullTime': typeof import("../common/helpers/fullTime").default;
|
||||
|
2
framework/core/js/dist-typings/common/compat.d.ts
generated
vendored
2
framework/core/js/dist-typings/common/compat.d.ts
generated
vendored
@@ -86,6 +86,7 @@ import isObject from './utils/isObject';
|
||||
import AlertManagerState from './states/AlertManagerState';
|
||||
import ModalManagerState from './states/ModalManagerState';
|
||||
import PageState from './states/PageState';
|
||||
import LabelValue from './components/LabelValue';
|
||||
declare const _default: {
|
||||
extenders: {
|
||||
Model: typeof import("./extenders/Model").default;
|
||||
@@ -174,6 +175,7 @@ declare const _default: {
|
||||
'components/TextEditorButton': typeof TextEditorButton;
|
||||
'components/Tooltip': typeof Tooltip;
|
||||
'components/EditUserModal': typeof EditUserModal;
|
||||
'components/LabelValue': typeof LabelValue;
|
||||
Model: typeof Model;
|
||||
Application: typeof Application;
|
||||
'helpers/fullTime': typeof fullTime;
|
||||
|
2
framework/core/js/dist-typings/common/helpers/textContrastClass.d.ts
generated
vendored
2
framework/core/js/dist-typings/common/helpers/textContrastClass.d.ts
generated
vendored
@@ -1 +1 @@
|
||||
export default function textContrastClass(hexcolor: string | null): string;
|
||||
export default function textContrastClass(hexcolor: string | null | undefined): string;
|
||||
|
3
framework/core/js/dist-typings/forum/compat.d.ts
generated
vendored
3
framework/core/js/dist-typings/forum/compat.d.ts
generated
vendored
@@ -71,6 +71,7 @@ import BasicEditorDriver from '../common/utils/BasicEditorDriver';
|
||||
import routes from './routes';
|
||||
import ForumApplication from './ForumApplication';
|
||||
import isSafariMobile from './utils/isSafariMobile';
|
||||
import AccessTokensList from './components/AccessTokensList';
|
||||
declare const _default: {
|
||||
extenders: {
|
||||
Model: typeof import("../common/extenders/Model").default;
|
||||
@@ -159,6 +160,7 @@ declare const _default: {
|
||||
'components/TextEditorButton': typeof import("../common/components/TextEditorButton").default;
|
||||
'components/Tooltip': typeof import("../common/components/Tooltip").default;
|
||||
'components/EditUserModal': typeof import("../common/components/EditUserModal").default;
|
||||
'components/LabelValue': typeof import("../common/components/LabelValue").default;
|
||||
Model: typeof import("../common/Model").default;
|
||||
Application: typeof import("../common/Application").default;
|
||||
'helpers/fullTime': typeof import("../common/helpers/fullTime").default;
|
||||
@@ -276,6 +278,7 @@ declare const _default: {
|
||||
'components/DiscussionListItem': typeof DiscussionListItem;
|
||||
'components/LoadingPost': typeof LoadingPost;
|
||||
'components/PostsUserPage': typeof PostsUserPage;
|
||||
'components/AccessTokensList': typeof AccessTokensList;
|
||||
'resolvers/DiscussionPageResolver': typeof DiscussionPageResolver;
|
||||
routes: typeof routes;
|
||||
ForumApplication: typeof ForumApplication;
|
||||
|
2
framework/core/js/dist-typings/forum/components/AvatarEditor.d.ts
generated
vendored
2
framework/core/js/dist-typings/forum/components/AvatarEditor.d.ts
generated
vendored
@@ -72,7 +72,7 @@ export default class AvatarEditor extends Component<import("../../common/Compone
|
||||
remove(): void;
|
||||
/**
|
||||
* After a successful upload/removal, push the updated user data into the
|
||||
* store, and force a recomputation of the user's avatar color.
|
||||
* store, and force a re-computation of the user's avatar color.
|
||||
*
|
||||
* @param {object} response
|
||||
* @protected
|
||||
|
2
framework/core/js/dist/admin.js
generated
vendored
2
framework/core/js/dist/admin.js
generated
vendored
File diff suppressed because one or more lines are too long
2
framework/core/js/dist/admin.js.map
generated
vendored
2
framework/core/js/dist/admin.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
framework/core/js/dist/forum.js
generated
vendored
2
framework/core/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
framework/core/js/dist/forum.js.map
generated
vendored
2
framework/core/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -411,12 +411,23 @@ export default class Application {
|
||||
pageNumber: 1,
|
||||
};
|
||||
|
||||
const title =
|
||||
let title =
|
||||
onHomepage || !this.title
|
||||
? extractText(app.translator.trans('core.lib.meta_titles.without_page_title', params))
|
||||
: extractText(app.translator.trans('core.lib.meta_titles.with_page_title', params));
|
||||
|
||||
document.title = count + title;
|
||||
title = count + title;
|
||||
|
||||
// We pass the title through a DOMParser to allow HTML entities
|
||||
// to be rendered correctly, while still preventing XSS attacks
|
||||
// from user input by using a script-disabled environment.
|
||||
// https://github.com/flarum/framework/issues/3514
|
||||
// https://github.com/flarum/framework/pull/3684
|
||||
// This is only a temporary solution for 1.x,
|
||||
// and the actual source of the issue will be fixed in 2.x
|
||||
// Actual source of the issue: https://github.com/flarum/framework/issues/3685
|
||||
const parser = new DOMParser();
|
||||
document.title = parser.parseFromString(title, 'text/html').body.innerText;
|
||||
}
|
||||
|
||||
protected transformRequestOptions<ResponseType>(flarumOptions: FlarumRequestOptions<ResponseType>): InternalFlarumRequestOptions<ResponseType> {
|
||||
|
@@ -90,6 +90,7 @@ import isObject from './utils/isObject';
|
||||
import AlertManagerState from './states/AlertManagerState';
|
||||
import ModalManagerState from './states/ModalManagerState';
|
||||
import PageState from './states/PageState';
|
||||
import LabelValue from './components/LabelValue';
|
||||
|
||||
export default {
|
||||
extenders,
|
||||
@@ -167,6 +168,7 @@ export default {
|
||||
'components/TextEditorButton': TextEditorButton,
|
||||
'components/Tooltip': Tooltip,
|
||||
'components/EditUserModal': EditUserModal,
|
||||
'components/LabelValue': LabelValue,
|
||||
Model: Model,
|
||||
Application: Application,
|
||||
'helpers/fullTime': fullTime,
|
||||
|
@@ -28,7 +28,7 @@ export default class Badge<CustomAttrs extends IBadgeAttrs = IBadgeAttrs> extend
|
||||
view() {
|
||||
const { type, icon: iconName, label, color, style = {}, ...attrs } = this.attrs;
|
||||
|
||||
const className = classList('Badge', [type && `Badge--${type}`], attrs.className, color && textContrastClass(color));
|
||||
const className = classList('Badge', [type && `Badge--${type}`], attrs.className, textContrastClass(color));
|
||||
|
||||
const iconChild = iconName ? icon(iconName, { className: 'Badge-icon' }) : m.trust(' ');
|
||||
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import isDark from '../utils/isDark';
|
||||
|
||||
export default function textContrastClass(hexcolor: string | null): string {
|
||||
export default function textContrastClass(hexcolor: string | null | undefined): string {
|
||||
if (!hexcolor) return 'text-contrast--unchanged';
|
||||
|
||||
return isDark(hexcolor) ? 'text-contrast--light' : 'text-contrast--dark';
|
||||
}
|
||||
|
@@ -75,6 +75,7 @@ import BasicEditorDriver from '../common/utils/BasicEditorDriver';
|
||||
import routes from './routes';
|
||||
import ForumApplication from './ForumApplication';
|
||||
import isSafariMobile from './utils/isSafariMobile';
|
||||
import AccessTokensList from './components/AccessTokensList';
|
||||
|
||||
export default Object.assign(compat, {
|
||||
'utils/PostControls': PostControls,
|
||||
@@ -150,6 +151,7 @@ export default Object.assign(compat, {
|
||||
'components/DiscussionListItem': DiscussionListItem,
|
||||
'components/LoadingPost': LoadingPost,
|
||||
'components/PostsUserPage': PostsUserPage,
|
||||
'components/AccessTokensList': AccessTokensList,
|
||||
'resolvers/DiscussionPageResolver': DiscussionPageResolver,
|
||||
routes: routes,
|
||||
ForumApplication: ForumApplication,
|
||||
|
@@ -204,7 +204,7 @@ export default class AvatarEditor extends Component {
|
||||
|
||||
/**
|
||||
* After a successful upload/removal, push the updated user data into the
|
||||
* store, and force a recomputation of the user's avatar color.
|
||||
* store, and force a re-computation of the user's avatar color.
|
||||
*
|
||||
* @param {object} response
|
||||
* @protected
|
||||
|
@@ -176,4 +176,10 @@ blockquote ol:last-child {
|
||||
--contrast-color: var(--text-on-dark);
|
||||
color: var(--contrast-color);
|
||||
}
|
||||
|
||||
// This exists to prevent inheriting the contrast color from a parent element.
|
||||
// Like when a badge is inside a tag hero.
|
||||
&--unchanged {
|
||||
--contrast-color: var(--unchanged-color);
|
||||
}
|
||||
}
|
||||
|
@@ -441,7 +441,7 @@
|
||||
color: var(--muted-color);
|
||||
border-radius: 10px;
|
||||
background-color: transparent;
|
||||
width: 100%;
|
||||
width: calc(~"100% + 20px * 2");
|
||||
display: flex;
|
||||
|
||||
.Post-header {
|
||||
|
@@ -62,7 +62,7 @@ class UploadFaviconController extends UploadImageController
|
||||
]);
|
||||
}
|
||||
|
||||
$encodedImage = $this->imageManager->make($file->getStream())->resize(64, 64, function ($constraint) {
|
||||
$encodedImage = $this->imageManager->make($file->getStream()->getMetadata('uri'))->resize(64, 64, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
$constraint->upsize();
|
||||
})->encode('png');
|
||||
|
@@ -38,7 +38,7 @@ class UploadLogoController extends UploadImageController
|
||||
*/
|
||||
protected function makeImage(UploadedFileInterface $file): Image
|
||||
{
|
||||
$encodedImage = $this->imageManager->make($file->getStream())->heighten(60, function ($constraint) {
|
||||
$encodedImage = $this->imageManager->make($file->getStream()->getMetadata('uri'))->heighten(60, function ($constraint) {
|
||||
$constraint->upsize();
|
||||
})->encode('png');
|
||||
|
||||
|
@@ -21,7 +21,7 @@ class Application
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const VERSION = '1.8.0-dev';
|
||||
const VERSION = '1.8.2';
|
||||
|
||||
/**
|
||||
* The IoC container for the Flarum application.
|
||||
|
@@ -9,7 +9,6 @@
|
||||
|
||||
namespace Flarum\Queue;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandling;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Throwable;
|
||||
|
@@ -39,4 +39,15 @@ class UserPolicy extends AbstractPolicy
|
||||
return $this->allow();
|
||||
}
|
||||
}
|
||||
|
||||
public function uploadAvatar(User $actor, User $user)
|
||||
{
|
||||
if ($actor->id === $user->id) {
|
||||
return $this->allow();
|
||||
}
|
||||
|
||||
if ($actor->id !== $user->id) {
|
||||
return $actor->can('edit', $user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -87,7 +87,7 @@ class AvatarValidator extends AbstractValidator
|
||||
}
|
||||
|
||||
try {
|
||||
$this->imageManager->make($file->getStream());
|
||||
$this->imageManager->make($file->getStream()->getMetadata('uri'));
|
||||
} catch (NotReadableException $_e) {
|
||||
$this->raise('image');
|
||||
}
|
||||
|
@@ -68,13 +68,11 @@ class UploadAvatarHandler
|
||||
|
||||
$user = $this->users->findOrFail($command->userId);
|
||||
|
||||
if ($actor->id !== $user->id) {
|
||||
$actor->assertCan('edit', $user);
|
||||
}
|
||||
$actor->assertCan('uploadAvatar', $user);
|
||||
|
||||
$this->validator->assertValid(['avatar' => $command->file]);
|
||||
|
||||
$image = $this->imageManager->make($command->file->getStream());
|
||||
$image = $this->imageManager->make($command->file->getStream()->getMetadata('uri'));
|
||||
|
||||
$this->events->dispatch(
|
||||
new AvatarSaving($user, $actor, $image)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "flarum-webpack-config",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.2",
|
||||
"description": "Webpack config for Flarum JS and TS transpilation.",
|
||||
"main": "index.js",
|
||||
"author": "Flarum Team",
|
||||
|
Reference in New Issue
Block a user